Module 3 Tutorial

August 02, 2017


In this tutorial we are are going to learn how to build an application that allows users to post topics and upvote/downvote the topics.

Step 1: Breaking up the application into components

  • A section that contains an input field(red)
  • A section that contains a submit button(brown)
  • A component that contains a list of posts(orange)
    • A subcomponent that represents a post(blue)
      • A sub-subcomponent that represents a square button(red)
      • A sub-subcomponent that represents text (green)

Step 2: Creating the PostList Component

Creating the PostButton Component

To start off we are going to create the component that represents the square buttons that are a part of each of the posts. We are going to add some style to it and display its label property.

function PostButton(props){
    var style = {
        width:24,
        height:24
    }
    return (
        <button style = {style}>{props.label}</button>
    )
}

Add a <div> tag to the HTML file and give it an id attribute equal to “root”.

Test the PostButton component by rendering it to the page.

ReactDOM.render(
    <PostButton/>,
    document.querySelector("#root")
)
Creating the PostText Component

Next, we are going to create the component that represents the text areas that are a part of each of the posts. We are going to add some style to it and display its label property. Its width will vary based on its width property.

function PostText(props){
    var style = {
        border:"1px solid black",
        width: props.width
    }
    return (
        <div style = {style}>{props.text}</div>
    )
}

Test the PostText component by rendering it to the page.

ReactDOM.render(
    <PostText width ={50} text="Test"/>,
    document.querySelector("#root")
)
Creating the Post Component

Next, we are going to create the component that represents the posts that go in the list of posts. We are going to add some styling to make it display its subcomponents horizontally. We are also going to pass in a title and score property down to its PostText components.

function Post(props){
    var style = {
        display:"flex"
    }
    return (
        <div style = {style}>
            <PostButton label = "x"/>
            <PostText text = {props.title} width = {200}/>
            <PostButton label = "+" />
            <PostText text = {props.score} width = {20}/>
            <PostButton label = "-"/>
        </div>
    )
}

Test the Post component by rendering it to the page.

ReactDOM.render(
    <Post title="Post Title" score = {0}/>,
    document.querySelector("#root")
)
Creating the PostList Component

Next, we are going to create the component that represents the list of posts. We are going to accomplish this by using the map() method to generate a Post component for each item in the componets postList property array. We will wrap all of the Post components in a <ol> tag.

function PostList(props){
    return (
        <ol>
        {
            props.postList.map((item,index) => 
                <Post key = {index} 
                      title = {item.title} 
                      score = {item.score}
                />
             )
         }
        </ol>
    )  
}

Test the PostList component by rendering it to the page with some test data.

ReactDOM.render(
    <PostList postList = {[1,2,3,4,5]}/>,
    document.querySelector("#root")
)

Step 3: Creating a Controlled Component from the input field

To start off we are going to create an App component that will hold all of the other components.

class App extends React.Component{
    constructor(props){
        super(props)
    } 
    render(){
        return (
            <div>
                App
            </div>
        )
    }
}

Test the App component by rendering it to the page.

ReactDOM.render(
    <App/>,
    document.querySelector("#root")
)

Next, we are going to initialize the App component’s state. The App component will have two state attributes: one to hold the value of the input element and one to hold the array of post data.

Add this to the constructor() method:

    this.state = {value:"", items : []}

Next, we are going to add an input element and make it a controlled component by tyings its value to the component state. We will accomplish this by declaring a handleChange() method that updates the components state whenever the input element’s value is changed. We also have to bind the handleChange method to the App component so that the method refers to the right place.

class App extends React.Component{
    constructor(props){
        super(props)
        this.state = {value:"", items : []}
    } 
    handleChange(event){
        this.setState({value:event.target.value})
        console.log(this.state.value)

    }
    render(){
        return (
            <div>
                <input value = {this.state.value} onChange = {this.handleChange.bind(this)}/>
            </div>
        )
    }
}

Test the input field to make sure that it is tying its value back to the component state by typing in some characters and viewing the console.

Step 4: Adding values to the Post List

Next, are are going to add the functionality to add items to the state array.

To start, we are going to declare an event handler called addItem() to the App component. The method first makes a copy of the current items state array by using the slice() array. Then it takes in the value state attribute and truncates it to 20 characters using the substring() method. Then it creates an object containing the truncated string as a title and the value 0 as its score and adds it to the copied items array. Then it sorts the copied items array in descending order of score. Lastly, it updates the state to equal the sorted copied items array and sets the value state attribute back to an empty string.

Add to App component:

    addItem(){
        var itemsCopy = this.state.items.slice()
        var truncatedString = this.state.value.substring(0,20);
        itemsCopy.push({"title":truncatedString,"score":0})
        itemsCopy.sort((a,b)=>{
          return b.score - a.score;
        })
        this.setState({items:itemsCopy,value:""})
    }

Now that the addItem() event handler has been created, we need to create a submit button that will call the addItem() method when it is clicked. We will add it below the input element in the render() method of the App component.

Edit the button element in the App render() method:

    <button onClick = { () => this.addItem()}>Submit</button>

Now lets add the PostList component inside the render() method of the App component. We will supply its postList attribute with the this.state.items array that contains all of the post data.

Add to the App render() method:

    <PostList postList = {this.state.items}/>

Test the App component by typing something in the input field and pressing the submit button. A post should be added to the PostList with a title equal to the string entered in the input field.

Step 5: Updating the post score

Next, we are going to add the functionality to update the post score when the + or - buttons are pressed.

To start, add a method called updateScore() to the App component. The updateScore() method should make a copy of the the items state attribute by using the slice() method. It will then reference a specific index of the copied items array and update that items score based on the val argument. The copied items array is then sorted and the state is set to equal the sorted copied array.

Add to App component:

    updateScore(index,val){
        var itemsCopy = this.state.items.slice()
        itemsCopy[index].score += val
        itemsCopy.sort((a,b) => {
            return b.score - a.score
        })
        this.setState({items:itemsCopy})
    }

The updateScore() method needs to be passed down all the way to the button elements.

Pass the updateScore() method into the PostList component. Do not forget to bind the updateScore() method to the App component before passing it in.

Edit in App render() method:

    <PostList postList = {this.state.items}
                updateScore = {this.updateScore.bind(this)}  
    />

Create two attributes on the Post component that is rendered in the PostList component. The first will be an attribute named incrementScore that calls updateScore(index,1) which increments the score of the specified index in the items state array by 1. The second will be an attribute named decrementScore that calls updateScore(index,-1) which decrements the score of the specified index in the items state array by 1.

Edit in PostList component:

    <Post key = {index} 
            title = {item.title} 
            score = {item.score}
            incrementScore = {() => props.updateScore(index,1)}                         
            decrementScore = {() => props.updateScore(index,-1)} 
    />

Next, create a handleClick attribute on the + and - PostButtons. The + PostButton should have its handleClick attribute set equal to incrementScore, while the - PostButton should have its handleClick attribute set to equal decrementScore.

Edit in Post component:

    <PostButton label = "+" handleClick = {props.incrementScore}/>
    <PostButton label = "-" handleClick = {props.decrementScore}/>

Lastly, edit the button element in the PostButton component to call handleClick() when it is clicked. This should make the button either increment or decrement its post’s score.

Edit in PostButton component:

    <button style = {style} onClick = { () => props.handleClick()}>{props.label}</button>

Test the + and - buttons by to see if they increment and decrement their post’s scores. The PostList should automatically sort itself by descending post score whenever a post score is updated.

Step 6: Removing posts

Next, we are going to add the functionality to remove posts.

To start, we are going to add a method called removeItem() to the App component. The removeItem() method will make a copy of the items state array by using the slice() method. It will then remove the specified index using the splice() method and then sort the array by descending score. It will then update the state to equal the sorted copied items array.

Add to App component:

    removeItem(index){
        var itemsCopy = this.state.items.slice()
        itemsCopy.splice(index,1);
        itemsCopy.sort((a,b) => {
            return b.score - a.score
        })

        this.setState({items:itemsCopy})
    }

The removeItem() method needs to be passed down all the way to the button elements.

Pass the removeItem() method into the PostList component. Do not forget to bind the removeItem() method to the App component before passing it in.

Edit in App component render() method:

    <PostList postList = {this.state.items}
                updateScore = {this.updateScore.bind(this)}  
                removeItem = {this.removeItem.bind(this)}
    />

Next, add a removeItem attribute in the Post component that is rendered in the PostList component.

The removeItem attribute should call the removeItem() method that was passed in with the PostList’s properties.

Edit in PostList component:

    <Post key = {index} 
            title = {item.title} 
            score = {item.score} 
            incrementScore = {() => props.updateScore(index,1)}                         
            decrementScore = {() => props.updateScore(index,-1)} 
            removeItem = {() => props.removeItem(index)}/>

Next, create a handleClick attribute on the “x” PostButton. The “x” PostButton should have its handleClick attribute set equal to the removeItem() method that was passed in with PostButton’s properties.

Edit in Post component:

    <PostButton label = "x" handleClick = {props.removeItem}/>

Test the “x” button to see if the posts are able to be removed.