Subscribe On YouTube

DemoCode

React Hooks is a new feature which is coming with React 16.7 and is adding missing pieces of functionality to React’s functional components:

By using State Hooks it’s possible to add state to functional components

By using Effect Hooks you can add code to functional components which is executed in response to component’s lifecycle events

In this tutorial we’ll explore both, State Hooks and Effect Hooks in a very practical way. By building real-world sample application from scratch you’ll be able to understand the concept and learn how you can apply React Hook in your application.

If you like CodingTheSmartWay, then consider supporting us via Patreon. With your help we’re able to release developer tutorial more often. Thanks a lot!



You can find the details of the React Hooks proposal at https://reactjs.org/docs/hooks-intro.html:



Getting Started

To get started first let’s set up a new React project from scratch using the create-react-app script. You do not need to install create-react-app on your system, you can directly execute the script by using the npx command:

$ npx create-react-app my-react-hooks-app

Executing the command in this way is creating a new project folder my-react-hooks-app and in this folder you’ll find the initial React project setup. Change into the newly created folder:



$ cd my-react-hooks-app

Because the React Hooks feature is not released yet, we need to make sure to at least install the version 16.7.0-alpha.2 of the packages react and react-dom.

$ npm i react@next react-dom@next

After having executed this command please check in the project’s package.json file that the correct version of both package is listed in the dependencies section. Once React version 16.7 is released this addtional installation step is no longer needed.

Because we’ll also be making using a libraray named Material-UI a last installation step needs to be completed:

$ npm install @material-ui/core

This libraray is containing React components that implement Google’s Material Design. We’ll be making use of those components to implement the user interface of the sample application of this tutorial.

Finally let’s check if the default project setup is working as expected by starting the development web server:

$ npm start

Once the server is started up successfully you should be able to see the following result in the browser when accessing URL http://localhost:3000:

With this default React application in place we’re now ready to add the State Hook.

Using State Hooks

Delete the default code which is available in App.js and start with adding the following import statement on top:

import React, { useState } from 'react';

In order to be able to make use of the new State Hook feature we need to import useState from the react package. Next add the following import statements as well:

import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import CardMedia from '@material-ui/core/CardMedia'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import Icon from '@material-ui/core/Icon';

Those import statement will give us access to material design components of the Material-UI library.

Next let’s add App compoment as a functional component by using the following lines of code:

function App() { } export default App;

Now let’s add state to our functional component by using useState:

const [courses, setCourses] = useState([ { title: "Server Side Rendering with React and Redux", description: "Build React, Redux, and React Router apps using Server Side Rendering (SSR), Isomorphic, and Universal JS techniques", url: "https://codingthesmartway.com/courses/react-redux-ssr/", courseImage: 'react01.png', upvote_count: 0, downvote_count: 0 }, { title: "React - The Complete Guide", description: "Dive in and learn React from scratch! Learn Reactjs, Redux, React Routing, Animations, Next.js basics and way more!", url: "https://codingthesmartway.com/courses/react-complete-guide/", courseImage: 'react02.png', upvote_count: 0, downvote_count: 0 }, { title: "The Complete React Web Developer Course (with Redux)", description: "Learn how to build and launch React web applications using React v16, Redux, Webpack, React-Router v4, and more!", url: "https://codingthesmartway.com/courses/react-complete/", courseImage: 'react03.png', upvote_count: 0, downvote_count: 0 } ]);

Here you can see that useState is taking the array of data objects which should be put into the component’s state as an argument. What is being returned by the call of useState is the state object itself (courses) and a function which can be used to set the state (setCourses). We’re using array destructuring syntax here to split the return value and save it in courses and setCourses at the same time.

As you can see each course data object is consisting of six properties: title, description, url, courseImage, upvote_count and downvote_count. The user will be able to up or down vote each course and thereby increase the values of properties upvote_count and downvote_count. Incrementing those values means setting a new state which we can do by calling setCourse. To do so let’s add two functions: upvoteCourse and downvoteCourse to our App component:

const upvoteCourse = index => { const newCourse = [...courses]; newCourse[index].upvote_count++; setCourses(newCourse); }; const downvoteCourse = index => { const newCourse = [...courses]; newCourse[index].downvote_count++; setCourses(newCourse); };

To complete the implementation of App component let’s add the corresponding JSX code as part of the return statement:

return ( <div className="app"> <NavBar /> <div> <Grid container spacing={24} style={{padding: 24}}> {courses.map((course, index) => ( <Grid item xs={12} sm={12} lg={4} xl={3}> <Course key={index} index={index} course={course} upvoteCourse={upvoteCourse} downvoteCourse={downvoteCourse} /> </Grid> ))} </Grid> </div> <Footer /> </div> )

Here we’re iterating through the list of courses available in the component’s state property which is available via courses. The output for each course is done by another component: Course. So let’s add the implementation of that component to App.js as well:

function Course({ course, index, upvoteCourse, downvoteCourse }) { return ( <div> <Card style={{maxWidth: '500px', marginBottom: '10px'}}> <CardMedia style={{height: 0, paddingTop: '56.25%'}} image={ course.courseImage } title={ course.title } /> <CardContent> <Typography variant="headline" component="h2">{ course.title }</Typography> <Typography component="p" color="textSecondary">{ course.description }</Typography> <br /> <Typography color="textSecondary">{ course.upvote_count } <Icon color="primary" onClick={() => upvoteCourse(index)}> thumb_up_alt </Icon> <span>{ course.downvote_count }</span> <Icon color="primary" onClick={() => downvoteCourse(index)}> thumb_down_alt </Icon> </Typography> </CardContent> <CardActions> <Button size="small" href={ course.url } target="_blank">Go To Course</Button> </CardActions> </Card> </div> ) }

As parameters we’re passing in course, index, upvoteCourse and downvoteCourse. The output is done by making use of Material Design components from the Material-UI library.

Finally let’s take a look at the complete code in App.js:

import React, { useState } from 'react'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import CardMedia from '@material-ui/core/CardMedia'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import Icon from '@material-ui/core/Icon'; function Course({ course, index, upvoteCourse, downvoteCourse }) { return ( <div> <Card style={{maxWidth: '500px', marginBottom: '10px'}}> <CardMedia style={{height: 0, paddingTop: '56.25%'}} image={ course.courseImage } title={ course.title } /> <CardContent> <Typography variant="headline" component="h2">{ course.title }</Typography> <Typography component="p" color="textSecondary">{ course.description }</Typography> <br /> <Typography color="textSecondary">{ course.upvote_count } <Icon color="primary" onClick={() => upvoteCourse(index)}> thumb_up_alt </Icon> <span>{ course.downvote_count }</span> <Icon color="primary" onClick={() => downvoteCourse(index)}> thumb_down_alt </Icon> </Typography> </CardContent> <CardActions> <Button size="small" href={ course.url } target="_blank">Go To Course</Button> </CardActions> </Card> </div> ) } function App() { const [courses, setCourses] = useState([ { title: "Server Side Rendering with React and Redux", description: "Build React, Redux, and React Router apps using Server Side Rendering (SSR), Isomorphic, and Universal JS techniques", url: "https://codingthesmartway.com/courses/react-redux-ssr/", courseImage: 'react01.png', upvote_count: 0, downvote_count: 0 }, { title: "React - The Complete Guide", description: "Dive in and learn React from scratch! Learn Reactjs, Redux, React Routing, Animations, Next.js basics and way more!", url: "https://codingthesmartway.com/courses/react-complete-guide/", courseImage: 'react02.png', upvote_count: 0, downvote_count: 0 }, { title: "The Complete React Web Developer Course (with Redux)", description: "Learn how to build and launch React web applications using React v16, Redux, Webpack, React-Router v4, and more!", url: "https://codingthesmartway.com/courses/react-complete/", courseImage: 'react03.png', upvote_count: 0, downvote_count: 0 } ]); const upvoteCourse = index => { const newCourse = [...courses]; newCourse[index].upvote_count++; setCourses(newCourse); }; const downvoteCourse = index => { const newCourse = [...courses]; newCourse[index].downvote_count++; setCourses(newCourse); }; return ( <div className="app"> <NavBar /> <div> <Grid container spacing={24} style={{padding: 24}}> {courses.map((course, index) => ( <Grid item xs={12} sm={12} lg={4} xl={3}> <Course key={index} index={index} course={course} upvoteCourse={upvoteCourse} downvoteCourse={downvoteCourse} /> </Grid> ))} </Grid> </div> <Footer /> </div> ) } export default App;

You should be able to see the following output in the browser:



Using Effect Hooks

After you’ve seen how to make use of useState to add state to React functional components by using hooks, let’s take a look at another hook, the effect hook.

After having added state to functional components there is another thing which is still missing when compared to class components: lifecycle methods like componentDidMount, componentDidUpdate and componentWillUnmount. To close this gap the effect hook has been introduced.

By using the Effect Hook you can run additional code after React has updated the DOM, including the first time a component is rendered.

To explore this feature in a practical example let’s use the sample application we’ve just build and add the effect hook by importing useEffect from the react package:

import React, { useState, useEffect } from 'react';

Still we’re making use of the useState hook to add state to our functional component. However as we’d like to retrieve the course data from a separate back-end service by using the effect hook in the next steps, we need to make sure to initialize an empty state by using useState(0):

const [courses, setCourses] = useState(0);

In the next step we’re ready to add the effect hook to the functional App component by adding the following code:

useEffect( () => { getCourses(); }, []);

Here we’re calling useEffect and passing in a function which is called once the component is mounted comparable to componentDidMount in a React class component. Inside that function we’re calling getCourses. The getCourses function will be implemented in the next steps to retrieve course data from a back-end (Contentful).

A Contentful account can be created at https://www.contentful.com/:

You should also notice that we’re using a second parameter when calling the useEffect function. As the second parameter just an empty array is passed into the call of the function. This is needed to make sure that the hook is only activated once, at mount. The reason why we’re getting this behavior when using an empty array as the second parameter is the fact the the effect hook will look through the array and only run the effect if one of those values has changed. Because there are no values supplied in the array it will only run once.

You can also use the effect hook and supply value on the array:

useEffect( () => { // run once and every time the value of var1 changes }, [var1]);

In this case the function passed to useEffect is run once and every time the value of var1 is changing.

It’s also possible to skip the second argument. In this case it will run on every render, including the first render of this component.

Ok, so let’s move on to the back-end part. The function getCourses() is called inside the effect hook and the task is to retrieve course data from a back-end. As a back-end we’ll be using Contentful. However you’re free to use any other back-end of your choice to retrieve data which is relevant for your use case.

To access Contentful let’s add the Contentful JavaScript Libraray to our project by executing the following command:

$ npm i contentful

The connection to the back-end is then established by adding the following lines of code to App.js:

import * as contentful from 'contentful'; const SPACE_ID = '...'; const ACCESS_TOKEN = '...'; const client = contentful.createClient({ space: SPACE_ID, accessToken: ACCESS_TOKEN });

Please note: the string values which are assigned to SPACE_ID and ACCESS_TOKEN needs to be retrieved from your Contentful back-end.

With the connection established we’re now ready to add the implementation of the getCourses function as part of the App functional component:

const getCourses = () => { client.getEntries({ content_type: 'course' }) .then((response) => { setCourses(response.items) console.log(courses) }) .catch((error) => { console.log("Error occurred while fetching Entries") console.error(error) }) };

The list of courses is retrieved from Contentful and then put into the state by using: setCourses(response.items) .

The next step is to add the return statement with the corresponding JSX code included:

return ( <div className="app"> <NavBar /> <div> { courses ? ( <Grid container spacing={24} style={{padding: 24}}> {courses.map((course, index) => ( <Grid item xs={12} sm={12} lg={4} xl={3}> <Course key={index} index={index} course={course} /> </Grid> ))} </Grid> ) : "No courses found" } </div> <Footer /> </div> )

The implementation of Course component needs to be added to App.js as well:

function Course({ course, index }) { return ( <div> <Card style={{maxWidth: '500px', marginBottom: '10px'}}> <CardMedia style={{height: 0, paddingTop: '56.25%'}} image={ course.fields.courseImage.fields.file.url } title={ course.fields.title } /> <CardContent> <Typography variant="headline" component="h2">{ course.fields.title }</Typography> <Typography component="p" color="textSecondary">{ course.fields.description }</Typography> </CardContent> <CardActions> <Button size="small" href={ course.fields.url } target="_blank">Go To Course</Button> </CardActions> </Card> </div> ) }

Here is again the full code of App.js:

import React, { useState, useEffect } from 'react'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import CardMedia from '@material-ui/core/CardMedia'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import NavBar from './components/NavBar'; import Footer from './components/Footer'; import * as contentful from 'contentful'; const SPACE_ID = '...'; const ACCESS_TOKEN = '...'; const client = contentful.createClient({ space: SPACE_ID, accessToken: ACCESS_TOKEN }); function Course({ course, index }) { return ( <div> <Card style={{maxWidth: '500px', marginBottom: '10px'}}> <CardMedia style={{height: 0, paddingTop: '56.25%'}} image={ course.fields.courseImage.fields.file.url } title={ course.fields.title } /> <CardContent> <Typography variant="headline" component="h2">{ course.fields.title }</Typography> <Typography component="p" color="textSecondary">{ course.fields.description }</Typography> </CardContent> <CardActions> <Button size="small" href={ course.fields.url } target="_blank">Go To Course</Button> </CardActions> </Card> </div> ) } function App() { const [courses, setCourses] = useState(0); useEffect( () => { getCourses(); }, []); const getCourses = () => { client.getEntries({ content_type: 'course' }) .then((response) => { setCourses(response.items) console.log(courses) }) .catch((error) => { console.log("Error occurred while fetching Entries") console.error(error) }) }; return ( <div className="app"> <NavBar /> <div> { courses ? ( <Grid container spacing={24} style={{padding: 24}}> {courses.map((course, index) => ( <Grid item xs={12} sm={12} lg={4} xl={3}> <Course key={index} index={index} course={course} /> </Grid> ))} </Grid> ) : "No courses found" } </div> <Footer /> </div> ) } export default App;

Conclusion

With React State And Effect Hooks functional components will become more powerful and are no longer limited when compared to class components. This means that it will be easier to learn and understand React for new developers. The code which is needed to create React application is less complex and simpler to understand when everything is implemented by using functional components.

However, if you’re used to class components and would like to keep working with classes in React there is no need to quit doing so as the framework is supporting both ways of creating components in the future.