Part 6 of the course: adding navigation (pages) to our application.

1-Overview

Explaining difference between single page and non-single page applications

3-Component state navigation

Created a button to add new photos inside PhotoWall. When clicked, the button is supposed to take us to a new page where we can add a new photo.

So now, we need to implement the 'multiple' page behaviour.

First we will create a new component for the new 'page' and call it 'AddPhoto' (export it and all).

In this video, he explains a certain way to do it, which does not seem to be the proper way. Ignore for now.

(4-5)-Browser Router & Link

Now we are working with the proper way of creating pages in a one page application, and the proper way of linking to them.

Start by installing:

npm install react-router-dom

Note: these might be installed by default, but anyway.

Now go to index.js, and import {BrowserRouter, Switch, Route} from 'react-router-dom'

actually, we just need the BrowserRouter here, we will use Route in a different file.

And modify your index.js code to include <BrowserRouter> tags:

Index

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Main from './components/Main';
import './styles/stylesheet.css';
import {BrowserRouter, Switch, Route} from 'react-router-dom'

ReactDOM.render(<BrowserRouter><Main/></BrowserRouter>, document.getElementById('root'));
                //make sure no spaces

Now links: Go to the PhotoWall component (where our add button is located), import Link: import {Link} from 'react-router-dom' And then change the button into Link component while specifying its as follows:

PhotoWall

import React, {Component} from 'react';
import Photo from './Photo';
import PropTypes from 'prop-types';
import {Link} from 'react-router-dom'

class PhotoWall extends Component {
    render(){
        return(
            <div>
            <Link className="addIcon" to="/AddPhoto"></Link>
            <div className="photoGrid">
                {this.props.posts.map((post, index) => <Photo key={index} post = {post}  onRemovePhoto ={this.props.onRemovePhoto}/>)}
            </div>
            </div>
        )
    }
}

PhotoWall.propTypes ={
    posts: PropTypes.array.isRequired,
    onRemovePhoto: PropTypes.func.isRequired
}

export default PhotoWall;

not finished yet, continue below:

6 - Route

So far, we have a link that is supposed to take us to a path, and we have Main inside <BrowseRouter> which prepares Main to allow for browse routing. However, we still don't have the individual pieces of UI (or pages) assigned to their respective paths.

To do that, go to Main and import: import {Route} from 'react-router-dom'

Then modify the code of Main as follows:

Main

import React, {Component} from 'react';
import Title from './Title';
import PhotoWall from './PhotoWall';
import AddPhoto from './AddPhoto';
import {Route} from 'react-router-dom'

class Main extends Component {

    constructor(){
        console.log('constructor');
        super();
        this.state = {
            posts: [{
                id: "0",
                description: "beautiful landscape",
                imageLink: "https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/i4eeacaa2dbf12d6d/version/1490299332/most-beautiful-landscapes-in-europe-lofoten-european-best-destinations-copyright-iakov-kalinin.jpg" +
                "3919321_1443393332_n.jpg"
                }, {
                id: "1",
                description: "Hello I am ghadir",
                imageLink: "https://img.purch.com/rc/640x415/aHR0cDovL3d3dy5zcGFjZS5jb20vaW1hZ2VzL2kvMDAwLzA3Mi84NTEvb3JpZ2luYWwvc3BhY2V4LWlyaWRpdW00LWxhdW5jaC10YXJpcS1tYWxpay5qcGc=" +
                "08323785_735653395_n.jpg"
                }, {
                id: "2",
                description: "On a vacation!",
                imageLink: "https://fm.cnbc.com/applications/cnbc.com/resources/img/editorial/2017/08/24/104670887-VacationExplainsTHUMBWEB.1910x1000.jpg"
                }],

                screen: 'photos'
        }

        this.removePhoto = this.removePhoto.bind(this);
        // this.navigate = this.navigate.bind(this);
    }


    removePhoto(postRemoved){
        console.log(postRemoved.description);
        this.setState((state) => ({     
            posts: state.posts.filter( post => post !== postRemoved)
        }))
    }

    // navigate(){
    //     this.setState({
    //         screen: 'addPhoto'
    //     })
    // }
    componentDidMount(){
        console.log('componentDidMount')
    }

    componentDidUpdate(){
        console.log('componentDidUpdate')
    }

    render() {
        console.log('render');
        return (
            <div>
                <Route exact path = '/' render={() => (
                <div>
                     <Title title={'PhotoWall'}/>
                     <PhotoWall posts = {this.state.posts} onRemovePhoto ={this.removePhoto} /*onNavigate = {this.navigate}*//>
                </div>
                )} />

                <Route path = '/AddPhoto' render={() => (
               <div>
                    <AddPhoto/>
                </div>
                )} />
              
            </div>

        )
    }
}

export default Main

In fact, you can further modify the render of Main and make it like this:

Main Render

    render() {
        console.log('render');
        return (
            <div>
                <Route exact path = '/' render={() => (
                <div>
                     <Title title={'PhotoWall'}/>
                     <PhotoWall posts = {this.state.posts} onRemovePhoto ={this.removePhoto} /*onNavigate = {this.navigate}*//>
                </div>
                )} />

                <Route path = '/AddPhoto' component = {AddPhoto}/>
              
            </div>

        )
    }

So since AddPhoto is a single component, we can Route to it just by calling its name (wheareas above, when we had a path to something with multiple components, we had to do it differently).

It is best however to always have one root component containing everything in the page, and link to it in the second concise way.

Dont' forget to add exact path to the default ('/') path.

9-UI set up for the AddPhoto Page

Just created the html and added css. Here is the html:

AddPhoto

import React, {Component} from 'react';


class AddPhoto extends Component {

    render(){
        return(
            <div>
                <h1>Photowall</h1>
                <div className ="form">
                    <form>
                        <input type="text" placehoder="Link"></input>
                        <input type="text" placehoder="Description"></input>
                        <button>Post</button>
                    </form>

                </div>


            </div>
        )
    }

}


export default AddPhoto

10-Adding Photos

Now we want to alter the AddPhotos page in a way that allows me to enter an image link and a description, and they will be added to the original array of posts that is currently in Main.

In AddPhoto we created a method called handleSubmit (and added a constructor to the class, so that we can bind this to our method), and created a call to the function on form submission.

The handleSubmit function prevents the default behavior of page refresh, it also checks for whether inputs were empty, and it is also supposed to call a function that updates the original posts array in state. To do this, we have to create that function in Main, where the array lives.

So go to Main and create a function addPhoto, seen below. Now we need to pass it props. However, the way we are currently calling AddPhoto components (through the Route) does not allow for passing props, so we returned it the way it was (non-concise way seen above) to be able to pass the method in props.

We passed the method in props. We created a 'post' object inside handleSubmit (made from the submitted infor) and then called addPhoto(post) - which will add the created post to the array (seee cooooodeeeee).

We also used something called history + history.push in the Router part, which allows a go back to previous page once a certain action has been completed.

AddPhoto

import React, {Component} from 'react';


class AddPhoto extends Component {

    //we are mostly declaring a constructor here just to bind 'this' to handleSubmit
    constructor(){
        super();
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    //note: on form submissions, we get a page refresh. 
    //we don't like page refresh.
    //That's why we caught the event in the argument of handleSubmit and prevented it.
    handleSubmit(event){
        event.preventDefault();
        //in the below, target refers to the submitted form7
        //elements refer to elements inside the form
        //we can access individual elements through their names
        const imageLink = event.target.elements.link.value;
        const description = event.target.elements.description.value;
        const post = {
            id:0,
            description: description,
            imageLink: imageLink
        }
        if (description && imageLink){
            this.props.onAddPhoto(post); //post here is the object created above
        }
    }

    render(){
        return(
            <div>
                <h1>Photowall</h1>
                <div className ="form">
                    <form onSubmit = {this.handleSubmit}>
                        <input type="text" placehoder="Link" name='link'></input>
                        <input type="text" placehoder="Description" name='description'></input>
                        <button>Post</button>
                    </form>

                </div>


            </div>
        )
    }

}


export default AddPhoto

Main

import React, {Component} from 'react';
import Title from './Title';
import PhotoWall from './PhotoWall';
import AddPhoto from './AddPhoto';
import {Route} from 'react-router-dom'

class Main extends Component {

    constructor(){
        console.log('constructor');
        super();
        this.state = {
            posts: [{
                id: "0",
                description: "beautiful landscape",
                imageLink: "https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/i4eeacaa2dbf12d6d/version/1490299332/most-beautiful-landscapes-in-europe-lofoten-european-best-destinations-copyright-iakov-kalinin.jpg" +
                "3919321_1443393332_n.jpg"
                }, {
                id: "1",
                description: "Hello I am ghadir",
                imageLink: "https://img.purch.com/rc/640x415/aHR0cDovL3d3dy5zcGFjZS5jb20vaW1hZ2VzL2kvMDAwLzA3Mi84NTEvb3JpZ2luYWwvc3BhY2V4LWlyaWRpdW00LWxhdW5jaC10YXJpcS1tYWxpay5qcGc=" +
                "08323785_735653395_n.jpg"
                }, {
                id: "2",
                description: "On a vacation!",
                imageLink: "https://fm.cnbc.com/applications/cnbc.com/resources/img/editorial/2017/08/24/104670887-VacationExplainsTHUMBWEB.1910x1000.jpg"
                }],
        }

        this.removePhoto = this.removePhoto.bind(this);
        this.addPhoto = this.addPhoto.bind(this);

    }


    removePhoto(postRemoved){
        console.log(postRemoved.description);
        this.setState((state) => ({     
            posts: state.posts.filter( post => post !== postRemoved)
        }))
    }

    addPhoto(postSubmitted){
        this.setState(state => ({
            posts: state.posts.concat([postSubmitted])  //we returned postSubmitted as an array. But why not push it normally?
        }))
    }
    componentDidMount(){
        console.log('componentDidMount')
    }

    componentDidUpdate(){
        console.log('componentDidUpdate')
    }

    render() {
        console.log('render');
        return (
            <div>
                <Route exact path = '/' render={() => (
                <div>
                     <Title title={'PhotoWall'}/>
                     <PhotoWall posts = {this.state.posts} onRemovePhoto ={this.removePhoto} />
                </div>
                )} />
                
                <Route path = '/AddPhoto' render={({history}) => (
               <div>
                    <AddPhoto onAddPhoto={(addedPost)  =>{
                       this.addPhoto(addedPost);
                       history.push('/');
                    }}/>
                </div>
                )} />

            </div>

        )
    }
}

export default Main

When testing make sure the url of image ends with something like a .jpg. You can check this website: https://homepages.cae.wisc.edu/~ece533/images/

Also , you had some issue with the placeholders not appearing. See into that.

11-Wrapping up

We want to display the newly entered photo first (so sort posts acc to newly entered), and we will make use of the posts IDs to do that -> we will put into them the number of milliseconds since 1970.

So now, we will make the id property to become a date. Go back to the posts array and change the string ids into numbers (0, 1, 2) for consistency.

Now in the AddPhoto component, when creating the new post object, put a new Date [actually, id: Number(new Date())] instead of the 0 we had previsouly.

Now, we have to sort the array of posts by ID when we render it. This is done in the PhotoWall component, so alter the contents of the div element with className 'photoGrid' (see below).

AddPhoto / only object code

  const post = {
            id: Number(new Date()),
            description: description,
            imageLink: imageLink
        }

Main / only posts array code

 posts: [{
                id: 0,
                description: "beautiful landscape",
                imageLink: "https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/i4eeacaa2dbf12d6d/version/1490299332/most-beautiful-landscapes-in-europe-lofoten-european-best-destinations-copyright-iakov-kalinin.jpg" +
                "3919321_1443393332_n.jpg"
                }, {
                id: 1,
                description: "Hello I am ghadir",
                imageLink: "https://img.purch.com/rc/640x415/aHR0cDovL3d3dy5zcGFjZS5jb20vaW1hZ2VzL2kvMDAwLzA3Mi84NTEvb3JpZ2luYWwvc3BhY2V4LWlyaWRpdW00LWxhdW5jaC10YXJpcS1tYWxpay5qcGc=" +
                "08323785_735653395_n.jpg"
                }, {
                id: 2,
                description: "On a vacation!",
                imageLink: "https://fm.cnbc.com/applications/cnbc.com/resources/img/editorial/2017/08/24/104670887-VacationExplainsTHUMBWEB.1910x1000.jpg"
                }],

PhotoWall

import React, {Component} from 'react';
import Photo from './Photo';
import PropTypes from 'prop-types';
import {Link} from 'react-router-dom'

class PhotoWall extends Component {
    render(){
        return(
            <div>
            <Link className="addIcon" to="/AddPhoto"></Link>
            <div className="photoGrid">
                {this.props.posts
                .sort(function(x, y){
                    return y.id - x.id
                })
                .map((post, index) => <Photo key={index} post = {post}  onRemovePhoto ={this.props.onRemovePhoto}/>)}
            </div>
            </div>
        )
    }
}

PhotoWall.propTypes ={
    posts: PropTypes.array.isRequired,
    onRemovePhoto: PropTypes.func.isRequired
}

export default PhotoWall;