Content
- What is state?
- What causes a component to re-render?
- How do we use state in React?
- Functional components and the
useState()
hook - Class components and
setState()
- State do's & don'ts
- Lifting state
- Conclusion
So you've just started learning and working with React. Great π! React is a fantastic JavaScript library that helps us in building interactive sites but it can difficult to grasp at first.
Coming from vanilla JavaScript into React you will be hit by all kinds of terminology such as props, state, lifecycle, components, hooks and much more. It can be overwhelming but it doesn't have to be.
State is a core concept of React. One that can be difficult to understand at first and especially difficult to master. So that is why I decided to write this article. I like React a lot and would like to help others who might be having difficulties. Let's get to it π.
What is state?
Let's consider some simple real world examples for a second. A door can either be open or closed hence in an open or closed state. It can be anything really. Think about a clock ticking over. Each time the second increases, the state of the clock changes.
In React we build our UI by creating re-usable components that we write using JavaScript(usually JSX which is a syntax extension of JavaScript). Components are able to manage their own state locally and they can be combined to form large and complex UI.
Consider a project written with vanilla JavaScript for a second. If we want to update an element normally we would have to query for the element and then do something to it to reflect the change of state. A common way to do this is by toggling certain classes that we have set up. Check it out π.
In this simple example we are creating a function to toggle the CSS class "open" every time we click on the button element. We could also say that we are toggling the state of the button which we show to the user through a change in style.
React comes into its own when we are dealing with a more complex application that has a lot of moving parts and requires a lot of state changes/management.
It is down in large part to React state in whether our components are dynamic or not. Values inside state can and often change over time as we require changes to our UI or data.
What causes a component to re-render?
React is very smart when it comes to updating the DOM(Document Object Model). It uses something called the Virtual DOM which is similar to the real DOM that we work with except it is a lightweight virtual representation of it.
Think about walking into your favorite fast food restaurant for second. You have the person working at the cash register that takes your money and we deal with this person every time we're hungry. But there's also that person in the back and this is the person making our food. The unsung hero and the one we know is there but never get to meet π€£.
Each DOM object has a corresponding virtual DOM object and React uses this virtual DOM to check for updates so that it doesn't have to directly update all of the real DOM's objects if they show no changes. This would be otherwise very inefficient.
Whenever we render a JSX element in React, the whole virtual DOM is updated which happens incredibly quickly. Next up it compares the updated virtual DOM's objects against the real DOM. It will then only make changes to the objects in the real DOM that have changed and that is when we see the changes updated on the screen. This is the reason why React is so fast.
So how do we update the DOM in React other than the initial first render π€ ?
In React a component will undergo a re-render whenever its state changes. This can be done in two ways. Firstly through a direct change to the state using the state update functions provided by React which we will soon take a look at. Secondly through a change to the components props.
Now that we have an idea of what state is in the context of React and why we need it, let's see how we can use it in our apps.
How do we use State in React?
Functional components & Class components
Before we start with some example we first have to differentiate the two different methods we have of working with state. While writing or reading about React you may have come across examples of code where components are written as JavaScript classes Class Product extends React.Component {}
or as functional components such as const Product = () => {}
.
It used to be that when we required a component to be a stateful component(meaning we want create some local state for the component) we would use classes to construct the component. And when we required a stateless(no local state required) component we would create the component as a functional component.
The reason for this is that React did not provide a way for us to control a components state when we used functional components. If we wanted the the component to be stateful then we would have to use a class component and then create a state object with this.state = {...};
which we will soon see.
In February 2019 React released a way in which we could now use state with functional components known as React Hooks. They are essentially special functions that we can use and one of these hooks allows us to control a components state without having to use classes. This does not mean that you are forced to use hooks instead of classes and vice versa. The definition provided by React fro hooks is as follows.
What is a Hook? A Hook is a special function that lets you βhook intoβ React features. For example, useState is a Hook that lets you add React state to function components. Weβll learn other Hooks later.
It is however my preference to stick to functional components when working with React similar to many others who think that they are simpler to read and write and understand exactly what is going. In fact in the official React documentation in the section Introducing Hooks - React, there is a sub-heading that says:
Classes confuse both people and machines
If you are coming from another language where classes are the norm then don't worry, you are more than welcome to use classes in your React code. React have made clear that they have no plans to remove classes from the library. I think those of us whose first programming language is JavaScript tend to prefer functional programming.
In JavaScript we also have to deal with using the this
keyword in classes which behaves differently to many other programming languages and this can lead to code that can be harder to read or follow.
To demonstrate this let's take a look at our first example of a simple stateful component. One will be written as a class and the other as a functional component so we can compare both methods but each component is otherwise the same. Don't worry to much if there's something in the code you are not sure about just yet. We'll cover the specifics about handling state very soon.
Functional Component with useState()
hook π.
Here we have a simple Button
component that requires us to use state so that we can conditionally show the the user whether it is in an "open" or "closed" state. Really contrived example but go with it π just so we can make a quick comparison.
Class Component using setState()
π.
Aside from there being less code to write in a functional component it is also for me at least easier to parse. Despite this it is important to be comfortable with both class and functional components. There are a lot of resources, code snippets and documentation for React that were written before the existence of React hooks. Therefore we want to be comfortable with both so now we'll have a look at them. Let's get into it π.
Functional components and the useState()
hook
As we briefly saw in the previous comparison we can use state in a functional component with the useState()
hook provided by React. To use this hook we call the useState()
function inside the component and pass in one argument which will be the initial value for the state. This initial value can be anything and is not restricted to being an object such as setState()
which we will see in the next section.
From calling the useState()
function we get two things back from React and we use array destructuring to deconstruct them into two variables. If you need a refresher for JavaScript destructuring then check out MDN - Destructuring Assignment. The first is called the state variable and we can give it any name we want. I suggest giving it a name that represents what the state is(e.g products, name, isOpen etc.).
The second value we get back is a function that allows up to update the state and similarly we can choose an appropriate name for it although the convention is to give it the same name as the state value but prefixed with the word "set". For Example:
const [ numbers, setNumbers ] = useState([0, 1, 2, 3]);
We can also have multiple different calls to useState()
to keep track of different values in state which may be required in a larger and more complex component. We could inlcude all of the following useState()
calls within a single component if we really desired.
Now that we know how to create some state let's put it into action. We are going to create a simple component that will output an array of numbers. We will have a button and whenever we click the button we will increment the last number of the array by 1 and output the full list with our state updates.
Here is an example of it after we have clicked the button once and therefore added the number 4 to the initial list [0, 1, 2, 3]
π.
We start by creating the functional component and calling the useState()
function.
So now we have our state value numbers
set initially to the array [0, 1, 2, 3]
that we pass in to useState
and we also have our function that will let us update the state value when something happens. So let's put it all into action.
Whenever we want to update the state we call the SetNumbers()
function in our case. Let's write a simple function that holds the logic to find the next number to add to the list and then update the state as we have defined. This will cause a component re-render and the result can then be shown to the user.
Here we access the last number of the array with array[array.length - 1]
and then we call the update state function. We pass in an array where we spread the values from the current numbers state value using the JavaScript spread syntax ...
so that we can still keep them in state. Then to the end of the array we add current last value + 1.
The final step is to make sure we return something because all React component must return some kind of React element. In the case of a functional component we can do this with the return
keyword. So let's finish our example and update the UI to show that our state is changing.
(note: In the map function below each <li>
should contain a unique key property which is explained here React - Lists and Keys)
In order to trigger the function that add adds a number to the array I have rendered a button for the user with an onClick
handler that will run our function after a click. Then we want to render our list of numbers to the page. We can do with with the JavaScript map()
function which allows us to perform some action on each element of the array and return the result of each action into a new array.
This is a very common pattern in React where we have some data(e.g result of an external API call) and we have to map it in some form to the DOM. Here we map each number into a list element by passing each number into the <li>
. Often we would have other custom components where we map the data at each index into the component.
And that's it! We have our stateful functional component that will update upon user interaction π. Let's take a look at how we would achieve the same result in a class component.
Class components and setState()
Before the introduction of React Hooks we were forced to write our stateful components using classes. React provided us with the setState()
api which allows us to request some changes to our state. I use the word request because it is not guaranteed that React will update the state changes immediately. It is possible that React will delay the update for performance reasons so attempting to read the state value immediately after a change might lead to unexpected results.
Nevertheless calling this will always lead to a component re-render as we have previously explored. It takes two arguments shown here setState(updater, [ callback ])
where updater
is a function that can take two parameters as state and props and returns the change of state (state, props) => stateChange
. The callback parameter is an optional function that gets executed after the component has re-rendered with state changes. This callback is not often used and React suggests not to use it but instead provides LifeCycle Methods which we will not cover today.
We can also choose to just pass an object as the first parameter of setState()
instead of the function and this will create a shallow merge of our new state into the state object. This just means that the values in our object will override any duplicate properties with our new values, leaving other properties unchanged and this is how we are going to update our state in our example. This is an example of the merging π.
Back to our example we start by creating our local state object in our class constructor like this π.
(Note: Don't forget to import React which is not shown in the following examples π ).
We do this by setting this.state
to an object where we can specify the properties we want to keep in state with their initial values. Similarly to a functional component we could use more state variables by adding more properties into our state object.
Next we can update our handleAddNumber
function to be suitable for a class component.
The only changes we've made here is to use the this
keyword when referencing our state value and update function so that we are referring to our Numbers Class and I have also stored the current state in a temporary variable for readability. It is also important to note that our handleAddNumber
method is created using the arrow function syntax so that we don't have to bind our function to the correct this
in the onClick
handler. Arrow functions do not have their own this
and therefore it will refer to enclosing execution context, in this case our class.
If you would like a refresher for understanding the this
keyword in JavaScript then check it out here at JavaScript Info - Object methods, "this"
Here we refer to our current state with this.state.numbers
and I have stored this in a constant for readability. In order to update the state we setState()
provided by react and pass in our new state object. Finally let's return some React Element using the React built-in render()
method.
(note: In the map function below each <li>
should contain a unique key property which is explained here React - Lists and Keys)
Once again we have to add the this
keyword to our handler function as explained and also to our state value this.state.numbers
that we are mapping to the DOM.
State Do's & Don'ts
Now that we know how to create stateful components we should consider the things to avoid when we use state in React.
Firstly it is important to know that state changes in React is asynchronous. This means that we need to be careful when calling multiple state change functions in quick succession. We will end up running into problems where we call multiple state updates inside the same cycle.
Secondly it is important that we should never try to change the state value directly using this.state.numbers = ...
but instead always use the setState()
or useState()
options function for classes or the update function provided by useState()
.
There are also rules for using React hooks such as the useState()
hook we have previously used ourselves. React provides us with a few more very useful hooks(and some less useful) that give us our functional component alternatives to using React class lifecycle methods.
The first important rule is that we don't call our hooks anywhere other than the top level. Don't call them inside loops or conditional and try to call them before your functions might experience an early return.
This is because we have to ensure that our component hooks are executed in the same order every time our component renders otherwise we will run into errors with React. If the component only sometimes executes an if
statement for example with some state update then there will be a difference in the order that the hooks were called. It is a common problem for React learners and one that will get easier to understand with time.
Secondly we cannot call out useState()
(or other hooks) or subsequent state update function outside of React Functions(this means React components or custom hooks which are just themselves functions).
For now it is good to just be aware of the important rules and get playing with state. When you run into problems you will have a better understanding of why you are receiving the error π.
Lifting State
Lifting State is a term you may have come across during your learning and it describes a solution for the flow of data through react components. What happens when we have two different components and we would like them to react(pun intended π ) to the changes in state of another component. Often we have multiple components and we want them to show some changes based on the state changes of another component.
In order to understand this further we need to know about the flow of data through our app. In React we have to pass props down the component tree from top to bottom. We cannot pass this data up from a child to a parent or from a child to a sibling. The data comes from ancestors downwards.
Consider the following example where the Child
component originally has some local state but then we realize that the ChildSibling
component also requires the same state. In React we want to limit the amount of stateful components as much as possible. The possibility of bugs increases when we work with more stateful components and if we keep re-writing the same code in different places then we are not writing efficient code.
So before we lift our state up we have this π.
Here we are just toggling the isOpen
state variable with a button click using setIsOpen(!isOpen)
which means not the current value of isOpen
. A boolean can only ever be true or false so we just flip the value when the button is pressed.
In order for both child components to utilize this state we can 'lift' it to the closest common ancestor of both components. In our case it is the Parent
component which we can see is returning both of the child components.
So what we can do is lift the state to the Parent
component declaring it only once. Then we can pass the state value as a prop to each component so that it can render something conditionally. We will also move our button to the parent component.
So let's lift it up to the Parent
and pass it down to each child.
Now you can see we define our state in the Parent
component and pass it to our children via the props object which we deconstruct inside the parameters into the variable so we don't have to bother with writing props.isOpen
. We still only have on stateful component which is great π.
It is important to not that our child components no longer have control over this state and we cannot modify the values passed down from the parent as props. They can however update in some way as a result of the parent modifying the state.
Conclusion
There is so much more to learn with regards to React and state. Try building some simple components that might be used on a website to practice with state.
Start small like a box that toggles its visibility with a button click. Maybe a clock that uses state to update itself with every second and eventually you will be building bigger and more complex examples.
When you are ready I suggest exploring the other react hooks we have available other than useState()
or if you prefer classes then check out the React lifecycle methods.
I hope you enjoyed reading the article as much as I enjoyed writing it. For more React and front-end related content you can follow me @Kieran6dev. Until next time π.
Top comments (0)