Using State in React. Class-Based vs Functional Components
May 27, 2020
In this article, using simple examples I hope to explain the basics of how you can use state within the two different types of components in React.
As the title may have given away, within React we have Class-Based and Functional components. While both of these types will ultimately render your component, how you define and mutate your state within these types is different.
Class-Based Components
Class-Based components (as the name may hint at) are Javascript classes. The important piece to remember here is when you define a Class-Based component, it needs to extend the React Component
class. This is because the Component
class contains certain methods we need access to, the most important one being the render
method. This method is how React is able to, well.. render our component.
During your journey through React you may hear these types of components called; containers, stateful or smart components.
Defining a Class-Based Component
You will see these Class-Based components defined 1 of 2 ways.
- By importing the
Component
class along with React
import React, { Component } from 'react'
class ClassBasedComponent extends Component {
render() {
return <h1>I'm a Class-Based Component</h1>
}
}
export default ClassBasedComponent
- Or you can just import React by itself and gain access to the
Component
class by usingReact.Component
import React from 'react'
class ClassBasedComponent extends React.Component {
render() {
return <h1>I'm a Class-Based Component</h1>
}
}
export default ClassBasedComponent
Defining State
How we define state inside of a Class-Based component is by using the reserved keyword state
and assigning it a value. This value is a Javascript object and it represents our initial state. There is nothing fancy or special about this object. We can store any type of data we want within this object; arrays, booleans, integers, etc., just like with objects outside of React
Within the body of our class but outside of the render
method, I'm going to define our state
and set a single property, fontColor
and assign it the value red.
import React, { Component } from 'react'
class ClassBasedComponent extends Component {
state = {
fontColor: 'red',
}
render() {
return <h1>I'm a Class-Based Component</h1>
}
}
export default ClassBasedComponent
We can then use this fontColor
property to style our <h1>
using an inline-style, making it red.
There are other ways of styling your React components, this method is just simpler for these examples.
import React, { Component } from 'react'
class ClassBasedComponent extends Component {
state = {
fontColor: 'red',
}
render() {
const style = {
color: this.state.fontColor,
}
return (
<div id="App">
<h1 style={style}>I'm a Class-Based Component</h1>
</div>
)
}
}
export default ClassBasedComponent
When you start your dev server, you should now see this component rendered to your page.
Mutating State
When we want to change something in our state (mutate it), you may have heard people say, "don't mutate your state directly". This means for example, we wouldn't want to do something like this.
this.state.fontColor = 'green'
React will not acknowledge this and will also give you a console warning. To properly mutate our state, we instead want to use a method that is made available to us via the Component
class we discussed earlier. This method is called setState
.
The setState
method takes in an object as an argument. Since we are attempting to update our state, this object should contain all of our new state values. setState
will take this object and merge it with our current state object. Merge being the important word here. More on this later.
Let's now add a method (function) to our component and call it changeColor
. The purpose of this method is simple. Update the value of our fontColor
property to "green" by passing an object to the setState
method.
This method will go inside the body of our class but outside the render
method. We need to use this.setState
since we are referring to our class which contains the setState
method.
import React, { Component } from 'react'
class ClassBasedComponent extends Component {
state = {
fontColor: 'red',
}
changeColor = () => {
this.setState({
fontColor: 'green',
})
}
render() {
const style = {
color: this.state.fontColor,
}
return (
<div id="App">
<h1 style={style}>I'm a Class-Based Component</h1>
</div>
)
}
}
export default ClassBasedComponent
Let's add a button to our component that we'll use to call our changeColor
method.
Inside our render
method, underneath of our <h1>
, add a button and set its text to "Change Font Color".
Our return statement should now look like this. A <h1>
with the text "I'm a Class-Based Component" and a button that says "Change Font Color" wrapped with an outer <div>
.
return (
<div id="App">
<h1 style={style}>I'm a Class-Based Component</h1>
<button>Change Font Color</button>
</div>
)
When you ask React to render more than one JSX element, you need to enclose these elements inside another element like a simple
<div>
. Giving the outer<div>
an id of "App" is not important for this example.
In order for this button to actually call our method, we need to pass changeColor
as a reference to the button's onClick
listener. And again since we are within a class we need to use this
to properly call our method.
<button onClick={this.changeColor}>Change Font Color</button>
Notice the camel case syntax here for 'onClick'. Remember, this is JSX, not HTML and there are some small yet very important differences that can and will trip you up at times.
We should now be able to click our button which will change the fontColor
property in our state to contain the value green instead of red. This change to our state triggers React to search the DOM and update the areas that need to reflect our new state. Since we are using state to assign our color, the <h1>
should change to green.
Functional Components
Functional components (again, as the name may hint at) are simply Javascript functions. You may hear this type called; presentational, stateless or dumb components.
Defining a Functional Component
You may also see these types defined in multiple ways. The two most common ways I've seen are by writing a normal Javascript function or the approach preferred by most, an Arrow Function.
- With a normal Javascript function, you simply define your function using the
function
keyword. Then from this function return the JSX that you want rendered.
// Regular Function
import React from 'react'
function FunctionalComponent() {
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
- Or again the more popular and preferred way, using an Arrow Function. Here the body of the function is still the same. We are just returning the JSX that we want React to render.
// Arrow Function
import React from 'react'
const FunctionalComponent = () => {
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
Defining State
Prior to React v16.8, state was only available to us via Class-Based components. But with the v16.8 update, React introduced a new feature called React Hooks. This provided a way for Functional components to contain their own state by using a hook called useState
.
Don't let the term hooks overwhelm you. They are just a library of functions made available to you by React.
We first need access to this useState
hook. This can be done in much the same way as we gained access to the Component
class from React.
- By importing
useState
along with React
import React, { useState } from 'react'
const FunctionalComponent = () => {
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
- Or by using
React.useState
import React from "react"
const FunctionalComponent = () => {
React.useState(...)
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
Using the useState
hook
This is where we see our first major difference between Class-Based and Functional components. How we set our initial state.
useState
gets treated like a function and it takes one argument, any form of data, it doesn't need to be an object. I can be an integer, boolean, strings etc. This differs from state
and setState
where we needed to provide an object.
These are all valid state definitions using useState
// string
useState('red')
// integer
useState(100)
// boolean
useState(true)
// object
useState({
fontColor: 'red',
})
For consistency, I will continue using an object with the fontColor
property and setting its initial state to red. I will also import useState
instead of using React.useState
.
import React, { useState } from 'react'
const FunctionalComponent = () => {
useState({
fontColor: 'red',
})
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
When useState
gets called, two elements are returned to us
- The current state
- A function that allows us to update this state
These elements are returned in the form of an array. This allows us to take advantage of array destructuring, giving us access to these elements and allowing us to give them meaningful names.
I have another post that walks you through destructuring both arrays and objects.
Let's update our code slightly to destructure our useState
.
import React, { useState } from 'react'
const FunctionalComponent = () => {
const [myState, setMyState] = useState({
fontColor: 'red',
})
return <h1>I'm a Functional Component</h1>
}
export default FunctionalComponent
Here we are assigning the first element returned, our state, to the variable myState
. And assigning the second element returned, the function that will allow us to update our state, to the variable setMyState
.
Using the returned state
Now that our state is defined, let's use it to make our <h1>
red, just like we did for our Class-Based example, using inline-styling.
Our style
constant should be inside of our function body but outside of our return statement.
import React, { useState } from 'react'
const FunctionalComponent = () => {
const [myState, setMyState] = useState({
fontColor: 'red',
})
const style = {
color: myState.fontColor,
}
return <h1 style={style}>I'm a Functional Component</h1>
}
export default FunctionalComponent
Remember, we are no longer in a class and our state now lives in the variable myState
. So we cannot use this.state
to access our different state properties. Instead, we need to use the name we gave our state element, myState
and use dot notation to target the property we want, fontColor
.
If however we had decided to use a string for our color instead of an object, that solution could look something like this.
We pass useState
a string, "red". Then to pull this information from our state we can just use the variable myState
which contains our string, "red".
import React, { useState } from 'react'
const FunctionalComponent = () => {
const [myState, setMyState] = useState('red')
const style = {
color: myState,
}
return <h1 style={style}>I'm a Functional Component</h1>
}
export default FunctionalComponent
At this point we should now have our Functional component being rendered like so.
Updating our state
To update our state, we use the function that was returned to us from useState
, which we assigned to setMyState
.
This function, just like useState
, takes in any form of data. The function will take this data and replace your old state with this new state data. Replace being the important word in this scenario. Again, more on this soon.
Let's create a function and call it changeColor
just like before. The syntax to define our changeColor
function needs to be altered slightly. Again, we are no longer in a class so we are now defining a function instead of a method. In order to define a function within a function we either need to use the function
keyword or use an arrow function assigned to a constant. I am going to use the arrow function approach.
Let's also add our button needed to call this function.
import React, { useState } from 'react'
const FunctionalComponent = () => {
const [myState, setMyState] = useState({
fontColor: 'red',
})
const style = {
color: myState.fontColor,
}
const changeColor = () => {
setMyState({
fontColor: 'green',
})
}
return (
<div>
<h1 style={style}>I'm a Functional Component</h1>
<button>Change Font Color</button>
</div>
)
}
export default FunctionalComponent
We again need to pass this function to the onClick
listener on our button, however we don't use this
. We just need to supply the name of our function, changeColor
.
<button onClick={changeColor}>Change Font Color</button>
We now have a Functional component using the useState
hook in order to set its initial state and update its state.
Ok.. what about this Merge and Replace??
Remember when I said setState
merges your new state with the old and how useState
replaces your old state with your new state. This is where we see the second major difference.
Example of setState vs useState
Let's say we had a second property in our state object for font weight.
{
fontColor: "red",
fontWeight: 600,
}
If we call setState
and pass it an object that just contains the fontColor
property, changing it to "green". setState
will only update our fontColor
property and leave fontWeight
as is. Merging these two states together.
If we use useState
and pass the same object that just contains fontColor
, our fontWeight
property would be removed since useState
replaces your old state. When we use useState
we need to explicitly send all of our state data even if it is not receiving an update.
// state after `setState` call
{
fontColor: "green",
fontWeight: 600,
}
// state after `useState` call
{
fontColor: "green",
}
Multiple useState calls
Thankfully there is a way around this and it is the third major difference. We can use useState
as many times as we want within our Functional components creating multiple instances of state. Unlike in our Class-Based components where we can only call state
once.
So to avoid either sending redundant data to useState
or having our data deleted, we just split our state across multiple useState
calls.
const [myColor, setMyColor] = useState({
fontColor: 'red',
})
const [myWeight, setMyWeight] = useState({
fontWeight: 600,
})
Now we can use the setMyColor
function and only pass it the data needed to change our color to green. Our fontWeight
will remain intact since it lives in a different state instance.
Let's wrap this up..
As I was writing this I felt that certain areas deserved a little more detail and explanation. So this article definitely turned out longer than I planned. If you stuck with me till the end, you're awesome.
And like always, if you ran into any issues, questions or there is something I missed. Please feel free to reach out to me via Twitter and I'll be happy to help!
Thanks!