Exploring React — Higher Order Components and Redux
This is the follow up from my previous article, read through it here. We’ll continue building our my-media application and learn some cool React stuff like Higher Order Components and Redux.
Higher Order Components
I like to call this guy super component. Just like super man has some out of the world abilities, a higher order component has some special functionality that it passes onto another component which is wrapped inside it. That component can thus perform some additional functions.
Remember we talked about Routes in the previous article? I want to let you in on a little secret now. Go to Posts.js and let’s take in the props as an argument to our Posts function. But hey, we didn’t pass in any props did we? Of course we didn’t. These props are by default provided by the React Router when we added routes to these components. They have some additional information so let’s see this information first
//Posts.js
export default function Posts(props){
console.log(props);
....
}
If you go down to the history object you can see a push method with an argument path. We can use this push method on the history object inside the props to navigate to any route that we have specified. Let’s try this out
//Posts.js
setTimeout(()=>{props.history.push('/comments')},2000)
Let’s see this in action
As you can see, when we go the Posts section, after 2 seconds we are automatically routed to the Comments section. Let’s try to do this inside our Navbar Component too
//Navbar.js
setTimeout(()=>{props.history.push('/comments')},2000)
Now we get an error
Why so? If we log out the props on the console inside Navbar.js we get an empty object. So the router didn’t send any props by default to the Navbar? Yes, it didn’t because the router never encountered the Navbar in the first place. It only passed props to those components which had a route associated with it or which were rendered through the Route Component.
So how do we tackle this problem?
We can solve this by using a higher order component called withRouter which is present inside the React Router Module and we can use it by wrapping our Navabar inside this higher order component.
//Navbar.js
import {Link, NavLink, withRouter} from 'react-router-dom';
Before moving forward we need to refactor our component a little. I’ll use an arrow function and use the export declaration separately at the end
//Navbar.js
...
const Navbar=(props)=> {...
}
export default Navbar;
Now let’s wrap this component inside the withRouter component by passing in the former as an argument
//Navbar.js
export default withRouter(Navbar);
Let’s check it out now
Works like a charm! Now that we have an overview of higher order components, let’s go implement our own custom higher order component
Creating custom Higher Order Component
Inside the Components folder we’ll create another one and call it HOC. All our custom higher order components will go in here. Inside this we’ll create a higher order component called randomTextColor.js
Components > HOC > randomTextColor.js
What we basically want to do with this component is take in the wrapped component as a parameter, generate some JSX with a div having a class of <randomcolor>-text and wrap that component inside it.
Now all we need to do is wrap our comments component inside it.
Let’s see this in action now
Great! You’ve got the hang of Higher Order Components now. You will rarely ever implement a custom Higher Order Component of your own since we mostly work on the ones React provides to us.
JSON Placeholder API
It’s time to fill our remaining components with some dummy content. We’ll be using the JSON Placeholder API to get this dummy content from so head over to their website . It provides some fake REST API content that we can use in our application by hitting some end points. Let’s look at these endpoints
Type this in your browser https://jsonplaceholder.typicode.com/comments and click on the JSON View Icon
You can see how this extension helps us see the JSON more clearly by indenting it properly.
https://jsonplaceholder.typicode.com/posts We will use this endpoint to get some random posts made by the user
The next endpoint we’re grabbing data from is https://jsonplaceholder.typicode.com/comments which will get us some comments made by the user’s friends
We’ll hit https://jsonplaceholder.typicode.com/users to get us a list of users which we will filter through to add some to our favorites section
And finally we’ll get the photos from https://jsonplaceholder.typicode.com/photos and display it under the photos section.
Let’s quickly populate our Components with some data now
Populating Components with Dummy Data
Since we’ll be making a GET requests in all of our components, we’ll have to refactor our components to make them a container component. We’ll also import axios so we can use it under componentDidMount() and add that to the state object of that component. This is pretty similar to what we’ve done already and you can use it as a practice exercise by writing out all the three parts yourself- refactoring code to contain state, making GET request using axios and rendering the state on the UI.
While we’re on it, under the posts, comments and favorites section let’s also add the functionality of removing items from the page. We have done this before in our todo-app and we can easily do it by firing a function inside the component itself
Let’s check it out on the browser now
Great! Our components have some data. Let’s do this bit for the remaining components as well
Cool, now we have all our components set up. We can also remove items from these components and it was really a cakewalk coding all this! Let’s see how our whole application looks now
Amazing! It wasn’t that hard coding all this, was it? Let’s move forward and learn some stuff about Redux.
Introduction to Redux
Remember how we associated data inside a component to it’s state? We said that state is a temporary storage for our data that the component can access. In our application so far we have several components which are managing their data through their own state independently. The user’s details inside the Profile component has nothing to do with the posts made by the user, the comments the user received or the favorites of that user. If you compare it to any general social media you can easily observe the sharing of data between different components unlike what we have implemented in our application.
Yes we can link the data between these components by passing methods as props which can in turn access the state or simply passing all the state of App.js as props to all the other components but it would make more sense to have some centralized data store where we could keep all our data and any component which requires it, grabs data from this central state.
Enter Redux. The central data store for all the data of our application. Any component can grab data from this store by subscribing to changes, and update it’s state by dispatching an action to the reducer. The biggest advantage of Redux is that it makes state management super simple and we’ll see how.
Let’s quickly understand how Redux works. We will first create a central data store which will have it’s own state. This is the central state where all our data will go. When a component asks for data, the store sends the data across through props. We receive these props inside our component, use a higher order component to match it to our state and use this data. When we alter this data, we send back an action or precisely, we dispatch an action. This dispatched action is received inside a Reducer which updates the central state.
We’ll be following the above process to create a central data store which will store all our comments inside it’s state. Let’s see this process in detail now.
Adding Redux to our application
Open up your terminal and we’re going to install two packages, the Redux package itself which will help us with the central data store and React Redux, which will allow us to use all the features of Redux with React.
npm install redux react-redux
Alright so first of all we need to create that central data store and a good place to do this would be index.js
//index.js
import { createStore } from 'redux';
...
const store=createStore();
We have created a store and now we need to tell our React application to interact with our store. This is where the React Redux package comes into play. It helps us associate the store with our react application
//index.js
import { Provider } from 'react-redux';
We will nest our App tag inside our Provider and pass in the store as a property. This tells React that for our App Component (which is essentially our application), the central store for all the data is present inside the store property of the Provider.
//index.js
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
Cool. Remember, to interact with this store we need something called a Reducer. So let’s create that
src > reducers > appReducer
As your application gets bigger, you might need more reducers so it’s always better to have all your reducers in a single folder inside the src folder
//appReducer.js
const appReducer=(state,action)=>{
return state;
}
Our reducer will take two parameters: the state and the action. The state is how the central data store tracks and maintains data and the action will represent what changes we need to make to our state.We will return that state at the end of the reducer function. Makes sense?
//appReducer.js
const initState={comments: [
{id:'1', email:'jimmy@james.com', body:'Some super cool stuff I just discovered'},{id:'2', email:'sadhash@kaafiSad.com', body:'amazon is the biggest rainforest in the world, right?'},{id:'3', email:'gogli@goggles.com', body:'Cant stop wondering where the stars go in the morning'},{id:'4', email:'fuzzysid@fuzzy.com', body:'boring stuff'} ]}const appReducer=(state=initState,action)=>{}export default appReducer;
The first time our function runs, the state does not exist so we pass an initial hardcoded state, could be an empty object too as the default value parameter. Also we need to export our reducer and import it back inside index.js and pass this reducer inside the createStore() function
import appReducer from './reducers/appReducer';
...const store=createStore(appReducer);
...
Alright so let’s quickly summarise what we have done till now. We installed redux and react-redux, we want to create a central store for all our comments of our comments component and using redux we created that store using an in built method createStore() and referenced that store to a variable store. To allow React to interact with this store, we import a Provider and pass the store property to it. Our store, much like a component, will track the central data through it’s own state. Any changes made to this store will be made by a Reducer, so we created a Reducer, fired our reducer function inside it passing the state and action as parameters and returned this state at the end. Since initially the state is nonexistent, we pass in the initial state as default value parameter to our reducer function. At the end we exported this reducer function, imported it inside index.js and passed it as an argument to the createStore() function.
I hope it makes sense somewhat. Don’t worry if this process doesn’t come in friendly just yet, the more you code it, it’ll naturally settle in your mind.
Next thing we need to do is connect our components to this store so they can interact with it, grab data from it, subscribe to changes etc.
Refactoring the Comments Component
We’re storing the comments onto the state of the central store so let’s get rid of axios, the state and componentDidMount() method inside our Comments.js so delete all of that.
To connect the Comments Component to the Redux Store, we’ll import and use a higher order component which let’s us do that from the react-redux package
import { connect } from 'react-redux';
This connect is actually a function and we’ll invoke it to get our higher order component
export default connect()(Comments);
We invoke connect() which returns us a higher order component where we then pass in our Comments Component as a parameter much like we’ve done earlier when we created our own custom higher order component
Recall that we still need the data in our comments component. The comments component is asking for it’s comments and the way we give that data is through props. The way we do this is taking the data from the store and mapping it to the props of our component. Let’s do that
//Comments.js
...
const mapStoreToProps=(state)=>{return{ comments: state.comments }}export default connect(mapStoreToProps)(Comments);
Now wherever you have used state inside the render method change it to props since we’re getting this data from the props and the state for this component does not exist.
Let’s check this out in the browser now
Great! Our Comments Component now grabs data from our central store. Let’s hook up our delete buttons with the actions now
Dispatching Actions
On deleting a comment we need to update the centrals state by removing that comment from the state object. In Redux terminology, we need to dispatch an action. When we dispatch an action we specify two things: the type of action and the optional payload. In this case the type of action could be say, DELETE_COMMENT since the type needs to be highly descriptive and the payload could be the id of that comment that we need to remove.
First we need to dispatch an action from the component. Just like we matched our store to the props, we can also match our dispatches to props
const mapDispatchToProps=(dispatch)=>{ return{ deleteComment: (id)=>{ dispatch({type: 'DELETE_COMMENT', id: id}) } } }export default connect(mapStoreToProps,mapDispatchToProps)(Comments);
Now we have added the dispatch to our props. Let’s use it now
//Comments.js...
<button className="btn #0d47a1 blue darken-4" onClick={()=>{this.handleDelete(comment.id)}}>Delete</button>
...
Inside this handleDelete() method we’ll fire our dispatch
//comments.js...
handleDelete=(id)=>{
this.props.dispatch(id)
}
...
Now we need to set up our dispatch inside our reducer where we’ll check for the received id, filter out the array and update the state
const rootReducer=(state=initState,action)=>{if(action.type==='DELETE_POST'){ let newPosts=state.posts.filter((post)=>{ return post.id!==action.id;}) return{ ...state, posts: newPosts } }return state;}
*Fingers Crossed* Let’s check it out now…
Superb! We have successfully connected our comments to the redux store. Our application works exactly the same before and now instead of storing the comments onto the state, we’re storing them in a central data store that Redux provides us. Try storing the posts, favorites and photos here too as an exercise, shouldn’t be hard at all!
Exploring further
You learnt a lot today and this is what every React.js developer first learns before diving into making more complex applications. However, you can make changes in this app to make it more dynamic. You can add a post input and connect it to our redux store so the user can write posts and see them dynamically. You can add login and signup features to this application and connect it to a database like Firebase. There’s a whole bunch of stuff you can do with React now! See you soon. :)