If you woke up from a coma since 2017 and can’t wait to start coding classes in React, I have some news for you. That news is, classes are dead, and functions took over.
This may be dramatic but there has been a fundamental shift in the philosophy and convention of creating components in React. The direction React is heading towards is a functional style of coding that makes class-based components unnecessary.
If you’re just waking up from a coma since 2017, you may be wondering how this is possible considering that functional components don’t have all the features that class components have. That is no longer the case with the introduction of hooks.
React Hooks are a new feature of React that allows functional components to have all the features available in class components.
So what are hooks and functional components all about? This article is a comprehensive guide to working with and understanding functional components, and that means understanding hooks.
Why did the React team create hooks?#
Hooks weren’t available when React came out, so why add them later?
The simple answer is, the React team wasn’t satisfied. They weren’t satisfied with the feedback they were seeing about their API which featured frustrations from people trying to learn the API. Issues like the confusing this keyword, life cycle methods, huge class components, and a difficult refactoring process prompted the team to think of a better solution.
The main features of React were praised but the next step was to take all that was good about React and make it better. The better way was a set of new features that attempted to solve the problems that emerged from the original way of creating components with classes.
This blog post by Dan Abramov, one of the leaders on the React team, outlines some of the main problems the team was trying to solve by introducing hooks. Here is a brief recap of those problems:
Class components often grew huge, which made it hard to test and refactor.
Duplicated logic became necessary because of the life cycle methods.
Complex patterns, like higher-order components and render props, emerged as solutions to common problems but were hard to learn.
So how do hooks and functional components solve these problems? Read on to learn about it all.
The difference between classes and functions in React#
Now that the history lesson is over, let’s learn about React development with examples.
To understand how React evolved from classes to components, let’s create a component in the two different ways and compare.
Here are two identical components. One uses the class syntax and the other uses the function syntax but they just output a div and a paragraph tag.
In this simple example, you can already see some benefits of using functional components. The class component takes 9 lines of code and 126 characters to type. The functional component takes 7 lines of code and 89 characters to type. That’s a 28.6% decrease in the number of lines and a 41.6% decrease in the number of characters typed in just a simple example!
The differences become even more profound as the complexity of the component grows.
Check out these tweets to see a more sophisticated example of how functional components can reduce the verbosity of your code.
Working with functional components#
This article isn’t about classes it’s about function so let’s get started on that.
We’ll be walking through the steps on building functional components from basic to more advanced. We’ll be covering topics like working with props, state, and the lifecycle methods involved with functional components.
Basic function components and arrow functions#
You saw a very simple example of a component above.
This is a React component that just returns a div and a paragraph tag with some text in it.
What these lines of code are doing is as follows:
Line 1: defining a function called
Line 2: begins the return statement. React components return
JSX which is HTML like syntax used by React to enable the coding style used in React which is slightly different than the original HTML syntax. To learn more about JSX check out these links: link 1, link 2.
Lines 3-5: the JSX being returned.
To use this component in your app, it would look something like this:
You have a top-level component (in this case: App) that gets rendered to the DOM by using the
ReactDOM.render() method. You import the
ExampleFunction component if it’s from another file and then add it into your App component like any other HTML element.
To define a function in a more modern way, use arrow functions. An arrow function does the same thing as a function only with different syntax.
There are some differences in the features of an arrow function compared to a regular function and according to the Moz docs, they are
ill suited as methods, and they cannot be used as constructors.
For the purposes of web development in React, arrow functions are perfectly fine for most cases.
Arrow functions allow you to implicitly return JSX which compacts your syntax even more. To implicitly return the JSX in the ExampleFunction component, your code would look like this:
The difference is rather than explicitly defining your return expression, you implicitly define it using parentheses. This accomplishes the same thing as the example above, with less code.
Adding props to your component#
It’s rare to use a static component like the one above. It’s more common to use a component that can change and update depending on some data.
One way of accomplishing this is to give
props to your components. Props are similar to parameters in functions. In React the main way of passing data between components is through props which is an object.
If you pass a prop called
name down to a component, you would access it like this:
In your parent component you would pass the props like this:
To add more props to your component just add more name and value pairs like so:
Then in your ExampleFunction component, the props will be accessible through the prop object.
To simplify your code and not have to write
props every time you want to access a key in props, you can destructure your parameter. This is a new syntax that allows you to unpack objects (like props) into their own variables. To destructure the name, email, and age from the props object in your functional component, it’ll look like this:
Since props is an object, you can represent it using curly braces. By typing the keys inside the curly braces, you destructure the object and make each key its own variable. You can then access each variable by typing out the key name. The prop object is no longer available for use in the function since it was destructured.
Adding state to your component with useState#
We went from a simple component that just returned a basic div and a paragraph tag, to a more dynamic component that accepts props. The next step is to add state to your component which allows the component to behave dynamically.
State is an essential concept in React and it’s what enables app-like behavior. You may have used state in class components but functional components work differently.
State is used in functional components by using the React hook:
useState does exactly what it sounds like it does, you get to use state in your component.
Here is a simple example of how to use
useStatein your component:
In line 1 we import the
useState method from React.
Then we change the component from implicitly returning JSX to explicitly returning our JSX because we want to add dynamic code in the component before we return anything.
This method returns an array with 2 items: the variable you’re using and the setter method. In this example our variable is called
firstName and the setter method is called
setFirstName. The useState method always follows this naming convention. The setter method is always a camel-cased version of the variable with set put in front.
If the variable was called
lastName then the setter method would be called
setLastName or if it was called
pet the method would be called
There is also a constructor in the useState method which is the first argument in the parameter. Whatever you put as the first argument is the initial value of the variable returned. In this case, it’s just an empty string so the initial value of firstName will be an empty string.
That’s how you add state to your component, next let’s see how to use it in the JSX to add dynamism.
In the paragraph tag, the usage of the firstName variable is nothing new.
In the new input tag, however, this may be new to you. What we’re doing is setting the value of the input to be the
firstName variable and updating that in the onChange method.
onChange method takes in the event which holds the values of the characters typed in the input field. Every time the input field changes, you set the value of the event target (the input) as the value for
Using and updating a string as state is pretty simple but how about an object? This is what it would look like if you were using an object as your state.
A few changes were made here compared to the previous component.
I changed the initial value from a string to an object.
I changed the way you access the state value.
I changed the way you set the state.
So for number 1, the way you set the initial value to be an object is the same way you would create any object. With curly braces and setting the key and value pairs, only you do it inside the parentheses.
For number 2, the state variable is now an object so you have to access it like an object. Since
firstName is a key in the object
name, you have to access it like
For number 3, updating an object is similar to how you would use
setState in class components. Since we don’t want to update all the values in the object when a single input is changed, we first tell
setName to retain all of its previous values by using the spread syntax which looks like
...name. Now the setter method has all the current values of
name but we still want to update
firstName so we specify the update like in the previous example.
And that is how you use useState in your component. You can also have multiple useState methods in your component. This could be helpful if you want to reduce the complexity of updating your object state by breaking up the values into its own state variable with its own setter method.
Using other data types works in the same way. With numbers, you just useState(0) and update the number like you would any other number. With booleans, you just useState(false) and update the variable to be either true or false. With arrays, it’s similar to updating objects but you have to be more precise.
Adding mounting/unmounting features with useEffect#
If you’re coming from class components you’re probably familiar with the lifecycle methods of
componentDidMount, the constructor, and other methods.
useEffect is the new way of doing what you would do in those methods, but in its own way.
useEffect gets called when the component renders, rerenders, and has clean up functionality. Let’s look at an example.
First we’re importing the
useEffect method from React and working with the same component we were before only with the
useEffect method takes 2 arguments in its parameters. The first argument is a function that runs on mount, unmount, and depending on the second argument. The second argument is an array and the values you put in the array control how often the
useEffect function runs.
Right now the
useEffect only has the first argument, a function that console.logs a message. Because there is no second argument, it runs as often as the component renders or re-renders which in this case will be when it’s mounted or when the state updates.
If we add an empty array like:
Then this function only runs once, the first time it mounts.
If we add in
name.firstName in the array then every time the
name.firstName updates, the useEffect function gets called.
name.lastName variable changes the useEffect function doesn’t get called because it’s not in the array.
Cleaning up side effects in useEffect#
The last functionality you should be aware of is what you return from useEffect. You return a function at the end of useEffect for clean up purposes. It would look something like this:
The purpose of this clean up return function is to clean up any side effects before running the next side effect or unmounting.
Side effects are variables that are defined outside of a particular scope. So everything not defined within the useEffect function would be a side effect and needs to be cleaned up before the component unmounts or some issues may arise like race conditions.
Examples of side effects commonly used in React applications would be
setInterval and window event listeners. They’re side effects because they exist in the global scope and aren’t contained within the scope of the
useEffect we’re defining a function that accepts an
e parameter and console.logs that.
Then we’re adding an event listener on the window object that listens for the mousemove event and uses that function.
In the return statement, we’re removing that event listener from the window object.
If we didn’t add the remove event listener line then the event listener wouldn’t get removed when the component unmounts. If the component remounts then it would be like calling the add event listener line twice.
And there you have it. With this knowledge, you have everything you need to work with functional components in React without missing any features from class components.
The state management API has been improved and simplified with useState.
The lifecycle methods have been improved and simplified with useEffect even though it doesn’t work the same.
There is still a lot to learn about React and its API. There are many other official hooks that weren’t covered in this post like
useCallback and others. Not to mention the hundreds of user-created hooks available on NPM. And not to mention that custom hooks can be made, limited only by your creativity.