Controlled components with React hooks
I love controlled components. Do you ?. Such a cliche question, but anyways let’s talk about controlled components with hooks.
They make your components flexible. They also help you keep single source of truth for your component’s data.
You never want to mix controlled and uncontrolled components. More on this here React controlled vs uncontrolled.
Controlled components are better when thinking in react mindset. That way you don’t have to think about the UI updates. React gives you the guarantee that UI shown to you is in-sync with your data in almost all the cases.
But creating controlled components and consuming them every time, comes up with some tradeoffs. You orchestrate the data in one of the higher components that renders a controlled component. Don’t worry if the prior lines were a bit confusing. Let’s understand this with a diagram
In the diagram, we have one modal having a search box and a result section. There are two data points here: Search value in the input. The data item array which is used to render cards.
This kind of modal having similar search and results is needed at multiple locations in our application.
And every place the post search logic varies. And once you have results with you, there are different logic post that. A few places you also need to set the search value programmatically, on some updates.
Now, this is definitely a made-up situation here. But this scenario is so common in real applications.
To keep this UI completely re-useable, we will go for controlled components, remember I told you that I love controlled components and by now I can assume, that you love it too.
Let’s try to write a component hierarchy for this situation.
<AnyofyourComponent>
<SearchAndResultView>
<Modal>
<SearchBox />
<ResultContainer />
</Modal>
</SearchAndResultView>
</AnyofyourComponent>
This will be the rough hierarchy, we will talk in rest of the article
I will not go very deep into controlled component. But let me give you some understanding about it.
Controlled components keeps the control of data with React. They rely on React to update UI in-sync with the data available to the component.
Read more about it here Controlled components
And then there are uncontrolled components. which takes matter into their own hand. They imperatively change the rendered result properties. Uncontrolled components
There is one more way to see uncontrolled components. Now let’s say you have a component that keeps the data with itself and the parent does not have access to it. The child component itself is in control of data and the parent can’t essentially change the child component’s state. In this scenario, the child component is not designed in a way where you can control the outcomes in the child component and it will also hamper the reusability. Opposite of it is controlled component.
So in our case, if the search state and result state is with the SearchAndResultView component, it becomes an uncontrolled component. But if the values needed to render the SearchAndResultView are coming from the parent component, we get a controlled component.
Let’s see how a parent component will use this child component.
function SearchAndResultView({ search, setSearch, result }) {
return (
<Modal>
<SearchBox search={search} setSearch={setSearch} />
<ResultContainer result={result} />
</Modal>
);
}
function AnyofyourComponent() {
const [search, setSearch] = React.useState("");
const [result, setResult] = React.useState([]);
async function onSearch(e) {
const value = e.target.value;
setSearch(value);
// do some API call
const res = await fetch("https://random-api");
setResult(res.data);
}
return (
<SearchAndResultView
search={search}
setSearch={setSearch}
result={result}
/>
);
}
Note: There are better ways of organizing the current component hierarchy, but let’s focus on only the controlled component stuff.
Notice, what all we needed to do to make use of SearchAndResultView.
We have to have two states, one for search and one for the result. In some scenarios, these kinds of definitions become so big and scattered. Next time you try to use the component, it eats away your whole time
We have to do a similar kind of thing everywhere we are using this SearchAndResultView component.
This is how fundamentally controlled components system works in reactjs. There is nothing wrong with it. But, Can we remove this repeated work?
Yes we can, that’s why this blog 😄
Now let’s observe the below snippet
function useSetUp({ initialSearch = "", initialResult = [] }) {
const [search, setSearch] = React.useState(initialSearch);
const [result, setResult] = React.useState(initialResult);
return {
search,
setSearch,
result,
setResult,
};
}
function SearchAndResultView({ search, setSearch, result }) {
return (
<Modal>
<SearchBox search={search} setSearch={setSearch} />
<ResultContainer result={result} />
</Modal>
);
}
SearchAndResultView.useSetup = useSetUp;
function AnyofyourComponent() {
const { search, setSearch, result } = SearchAndResultView.useSetup();
async function onSearch(e) {
const value = e.target.value;
setSearch(value);
// do some API call
const res = await fetch("https://random-api");
setResult(res.data);
}
return (
<SearchAndResultView
search={search}
setSearch={setSearch}
result={result}
/>
);
}
Notice, how the statements for defining states are now moved to useSetup hook. Now, every component that wants to use the SearchAndResultView doesn’t have to write the state definitions first to use the controlled component.
They can make use of the controlled component’s useSetup Hook
This way of writing controlled components have surprisingly improved my experience of working with controlled components. It’s a simple, but yet powerful way of writing components in Reactjs. This could not have been possible without React hooks.
You have complete control to change the value of the SearchAndResultView component anytime you like. This way we have all the controlled component benefits and also the ease of use.
Liked this article , then please checkout my other articles.
Thanks!