React hooks are already released since React 16.8. Most of developers fell in love with this "new" API but also small part of them still prefer to write components in good "old" class way.
I started to use this API almost from their release and I also would consider myself as a fan of hooks. So I'm on bright side, it means that writing components without classes make the code very readable. It could also lead to less code, that means at the end of the day - less bugs.
React team has done great work and their hooks API are covering every use case what developer may need when building beautiful React applications. However when building React applications with hooks I mostly use just basic hooks like: useState, useEffect and useRef and in more complex components also useContext, useReducer and useMemo come in handy.
Ok so get down to bussines and let's see some code 😍!
Initially, when I was using useState
hook I found myself declaring many primitive state variables in single component.
For simplicity let's consider example of controlled form with few inputs like this:
function App() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
return (
<form>
<label>Name</label>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<label>Email</label>
<input
type="text"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<label>Phone</label>
<input
type="text"
value={phone}
onChange={e => setPhone(e.target.value)}
/>
</form>
);
}
export default App;
Form above is very simple example of basic hooks usage of state. Each field is stated in separate variable created by hook and this value is controlled by separate setter function.
OK but, what's wrong with it ?
I would say nothing :) Let's try to go back in time and have a look on the same component but in class approach like this:
class App extends Component {
state = {
name: "",
email: "",
phone: ""
};
render() {
return (
<form>
<label>Name</label>
<input
type="text"
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
<label>Email</label>
<input
type="text"
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
/>
<label>Phone</label>
<input
type="text"
value={this.state.phone}
onChange={e => this.setState({ phone: e.target.value })}
/>
</form>
);
}
}
export default App;
As you can see it's very similar and there is no special difference between them, just using class
keyword and render
method, right? But there is also one thing that for me was in this approach more convenient way of making components.
Yes it's the controlling of the state by only one function this.setState
and accessing the state by just one property this.state
. This small little thing was great on class components and I missed this so much in functional world of React.
Are you asking why?
Let's say you are coding component of which state is not very clear at the beginning and you are adding, renaming or deleting state properties on the go while coding.
For instance in the case of adding state properties I'd need to define new variables with their own names and setter functions. In return
I'd need to access value and use separate setter for controlling state of hook. In case of definition of more state values it gives to component very fractional and repetition look, especially with const
and useState
keywords.
Diff of adding some new state values could look like this:
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
+ const [address, setAddress] = useState("");
+ const [bio, setBio] = useState("");
If we consider this to be just the definition of component state with only five properties. In my opinion there is just a lot of repetition looking code, especially when comparing to class state definitions shown below.
state = {
name: "",
email: "",
phone: "",
+ address: "",
+ bio: "",
};
In this case the state definition has clear and very understandable structure, without repetition code. Main game changer for me is that every state property can be accessed from one place and set by one function.
Redux guys may say it's like single source of truth for the one component. That's what I like about it.
Declaring many variables and accessing them were puzzling me for quite long time until I came up with simple idea of custom hook called useSetState
.
Take a glance on it in action bellow.
function App() {
const [state, setState] = useSetState({
name: "",
email: "",
phone: ""
});
return (
<form>
<label>Name</label>
<input
type="text"
value={state.name}
onChange={e => setState({ name: e.target.value })}
/>
<label>Email</label>
<input
type="text"
value={state.email}
onChange={e => setState({ email: e.target.value })}
/>
<label>Phone</label>
<input
type="text"
value={state.phone}
onChange={e => setState({ phone: e.target.value })}
/>
</form>
);
}
export default App;
Wow 🤩! Just one hook for whole state? Controlled from one place? Without using class ? That's pretty neat !
Since the moment of definition this custom hook I just stopped using regular useState
(I'm lying... I use it for super simple components f.e. togglers etc.) and I started to use it every time I need to store some state.
This hook just brings to my code:
- less repetitive look
- better flexibility of accessing and controlling state
- easier handling of state changes
- advantages of class state in functional component.
Are you wondering how this custom hook looks like 🤔 ?
const useSetState = (initialState = {}) => {
const [state, regularSetState] = useState(initialState);
const setState = newState => {
regularSetState(prevState => ({
...prevState,
...newState
}));
};
return [state, setState];
};
It's just simple custom hook with regular useState
hook. The useState
hook holds object by default. Then in defined setState
function is calling regular setter function called regularSetState
, but instead of assigning primitive variables to state, its assigning merged object of previous state object and new state object. This behavior leads to opportunity to store many values in one state object and to be set by one function. So simple but so powerful.
Conclusion
For sure, I'm not the first who adjusted useState
hook like this. Maybe it's not even the right way to handle it, but it just works for me very well and i like it. Maybe it would work for you too.
Such good practice! I like this in the way that you change state and your component is re-rendered only once. Good job! 👍🏻