Table of contents
I have been working with React for over 12 months now. When i was thinking to pen down the usage of state in react, only approach available was class based state. React landscape evolved so quickly to provide another approach to handle state using hooks. Before another approach enters react world 😉 i intend to provide the usage with a simple counter example (Classic 😃)
Do you really need your component to implement a state?. I think fair answer would be 'It depends'. Dan Abramov's tweet kind of summarises it.
React class based state
Let's start by implementing simple counter in a traditional state model. So one of the classic ways that we can hold on to state is in the constructor, pass the props up to the actual component and then use this.state. There are two versions to React.setState. First one is React.setState Object version and another one is React.setState function and callback version.
// Not including imports for simplicity sake
// React.setState Object version
class Counter extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.handleDecrement = this.handleDecrement.bind(this)
this.handleIncrement = this.handleIncrement.bind(this)
this.handleReset = this.handleReset(this)
}
handleIncrement() {
this.setState({
count: this.state.count + 1
})
}
handleDecrement() {
this.setState({
count: this.state.count - 1
})
}
handleReset() {
this.setState({
counter: 0
})
}
render() {
const { counter } = this.state
return (
<div className="Counter">
<p className="count">{counter}</p>
<section className="controls">
<button onClick={this.handleIncrement}>Increment</button>
<button onClick={this.handleDecrement}>Decrement</button>
<button onClick={this.handleReset}>Reset</button>
</section>
</div>
);
}
}
somewhere in index.html we will use the component :).
<Counter />
A nicer version to the above implementation would be using this.setState with function and callback.
// Not including imports for simplicity sake
// React.setState function and callback version
class Counter extends Component {
state = {
counter: 0
}
handleIncrement = () => {
// Added max and steps props to show
// the usage of the setState functions with arguments
// (state and props)
this.setState((state, props) => {
const {
max,
step
} = props
if (state.counter >= max) return;
return {
counter: state.counter + step
}
},
() => { // This call back runs after state is set
console.log("state counter ", this.state.counter)
})
}
handleDecrement = () => {
this.setState((state) => {
return {
counter: state.counter - 1}
})
}
handleReset = () => {
this.setState({
counter: 0
})
}
render() {
const { counter } = this.state
return (
<div className="Counter">
<p className="count">{counter}</p>
<section className="controls">
<button onClick={this.handleIncrement}>Increment</button>
<button onClick={this.handleDecrement}>Decrement</button>
<button onClick={this.handleReset}>Reset</button>
</section>
</div>
);
}
}
Somewhere in index.html we will use the Counter component.
<Counter max={15} step={3} />
React hook based state
If we closely observe, in the class based component 'state' is a property on the class, and this .setState is a method. We never define those ourselves, but when we extend the class with React.Component class counter extends component, then we inherit all those helpful methods and that allows us to update the value and trigger the render, so on and so forth. These are called 'stateful components' in react. There is another way to create a component which is to define a regular javascript function and pass it few props and it did some stuff and whatever comes out on the other side was rendered to the page. These are called 'stateless components' or 'functional components'. When i started working with react was i used stateful components because it's easier to use refactor them for state if required. React16+ introduced another way to use state with functional components known as 'React hooksssssssssss!!!'
The more i use functional components more i realise that FC's are cleaner and easier to refactor over the period of time. we'll actually see that class based Counter component becomes a much simpler component as we refactor. Let's see the implementation now
import React, { useState } from 'react';
const Counter = ({ max, step}) => {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount((c) => c + 1);
const handleDecrement = () => setCount((c) => c - 1);
// Another way to write above methods
// const handleIncrement = () => setCount(count + 1);
// const handleDecrement = () => setCount(count - 1);
const handleReset = () => setCount(0)
return(
<div className="Counter">
<h5> {message }</h5>
<p className="count">{count}</p>
<section className="controls">
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
</section>
</div>
)
}
Somewhere in index.html we will use the Counter component.
<Counter max={15} step={3} />
Let's see the usage of useEffect() by adding a simple functionality to the component. Requirement is to change the title whenever the count value changes in the component.
import React, { useState } from 'react';
const Counter = ({ max, step}) => {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount((c) => c + 1);
const handleDecrement = () => setCount((c) => c - 1);
// Another way to write above methods
// const handleIncrement = () => setCount(count + 1);
// const handleDecrement = () => setCount(count - 1);
const handleReset = () => setCount(0)
// Below code will be interpreted as
// When ever the value of the count change,
// Change the page title to reflect the updated count.
useEffect(() => {
document.title = `Count: ${count}`
}, [count])
return(
<div className="Counter">
<h5> {message }</h5>
<p className="count">{count}</p>
<section className="controls">
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
</section>
</div>
)
}
Let's spice up the above example by adding one more requirement. This time we save the changes to localstorage.
import React, { useState, useEffect } from 'react';
const getStateFromLocalStorage = () => {
const storage = localStorage.getItem('counter');
if (storage) { return JSON.parse(storage).count; }
return 0;
};
const storeStateInLocalStorage = count => {
localStorage.setItem('counter', JSON.stringify({ count }));
};
// Added max and step props to show usage of FC with props.
const Counter = ({ max, step }) => {
// Instead of defaulting to 0 this time
// we fetch the value from the localStorage.
const [count, setCount] = useState(getStateFromLocalStorage());
const handleIncrement = () => {
setCount(cnt => {
if (cnt >= max) return cnt;
return cnt + step;
});
};
const handleDecrement = () => setCount((c) => c - 1);
const handleReset = () => setCount(0);
useEffect(() => {
document.title = `Counter: ${count}`;
}, [count]);
// Whenever the count is changed the value is
// pushed to localStorage
useEffect(() => {
storeStateInLocalStorage(count);
}, [count]);
return (
<div className="Counter">
<p className="count">{count}</p>
<section className="controls">
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
</section>
</div>
);
};
export default Counter;
I will wind up the article by refactoring the above implementation with a custom hook which transacts with LocalStorage.
import React, { useState, useEffect } from 'react';
// Custom hook to save to LS and fetch from LS
const useLocalStorage = (initialState, key) => {
const get = () => {
const storage = localStorage.getItem(key);
if (storage) return JSON.parse(storage).value;
return initialState;
};
const [value, setValue] = useState(get());
useEffect(() => {
localStorage.setItem(key, JSON.stringify({ value }));
}, [value]);
return [value, setValue];
};
const Counter = ({ max, step }) => {
const [count, setCount] = useLocalStorage(0, 'count');
const handleIncrement = () => {
setCount(cnt => {
if (cnt >= max) return cnt;
return cnt + step;
});
};
const handleDecrement = () => setCount((c) => c - 1);
const handleReset = () => setCount(0);
useEffect(() => {
document.title = `Counter: ${count}`;
}, [count]);
return (
<div className="Counter">
<p className="count">{count}</p>
<section className="controls">
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
</section>
</div>
);
};
export default Counter;
Wrapping up
I hope the article helps in understanding the usage of state management in pure react.
I will appreciate the constructive feedback about the article. Please share patterns and anti-patterns you have come across in your experience with management of react state in the comments section below.
Additional documentation to read about hooks is available here https://reactjs.org/docs/hooks-intro.html. Recommend the frontend masters course by Brian Holt https://frontendmasters.com/courses/intermediate-react-v2/
Top comments (0)