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.
Top comments (9)
I tend to implement such a hook with
useReducer
after reading @leewarrickjr's awesome article,Bridging the Gap between React's useState, useReducer, and Redux
Lee Warrick ・ Sep 14 '19
You will see that, with
useReducer
, you specify "how" to handle state changes.So you can write a
merge
function as a reducer and pass the initial state as shown below.The working code is below. (check the console output after submit).
Nice work!
Thanks, mate~! and for the post!
Question from my side, why you are not using something like that :
and whenever yo want to update the state, for example lets update the name field :
even with
const
it will behave the sameHi, thanks for your comment :) Your code solves the same problem as I'm describing in article, only without using custom hook.
I would definitely also use your implementation but still:
setState
function could happen to be repeated in many event handlers in your component. So you will need to type...state
for many times in one component.and than to be used like:
If you import this hook into many of your components, than this way still gives you option to avoid destructing of previous state like
...state
as i mentioned above.Yea.. aside from it not looking nice to always repeat the same thing, there is the issue of forgetting to spread when doing the setState .. and that results in overriding the entire object and other bugs.
I did the same. My hook's named useObjectState: The first two values of return array it's same like useState.. state and setState. I expect object as input (not intended for other data types).
The third one is spreadState - it spreads the new state object with the old one at first level of the object.
The fourth one is mergeState - that does a deep merge of the new state with the old one. Helps with nested states.
This way I am able to remain sane when dealing with large number of states. The use case is same as yours - for large form UIs.
Awesome, I have tried a similar one for updating state if the component is mounted in typescript-react,
code looks like:
Problems?
eslint-plugin
and asking for addition on the dependency array ofuseEffect
.Such good practice! I like this in the way that you change state and your component is re-rendered only once. Good job! 👍🏻