I recently started using hooks in react and I am often having this problem: I create a first big state which is used by all my component, but some of the smaller parts of my component divide this state and create their own state for simplicity.
For example
import React, { useState } from "react";
const initialFilters = {
name: "",
code: ""
};
function Filter({ value, setFilters }) {
const [tempValue, setTempValue] = useState(value);
return (
<input
value={tempValue}
onChange={e => setTempValue(e.target.value)}
onBlur={() => setFilters(tempValue)}
/>
);
}
function App() {
const [filters, setFilters] = useState(initialFilters);
const agents = [
{ name: "bob", code: "123" },
{ name: "burger", code: "3123" },
{ name: "sponge", code: "34" }
];
return (
<div>
<label>Name filter</label>
<Filter
value={filters.name}
setFilters={value =>
setFilters(filters => ({ ...filters, name: value }))
}
/>
<label>Code filter</label>
<Filter
value={filters.code}
setFilters={value =>
setFilters(filters => ({ ...filters, code: value }))
}
/>
<button onClick={() => setFilters(initialFilters)}>Reset filters</button>
<ul>
{agents
.filter(
agent =>
agent.name.includes(filters.name) &&
agent.code.includes(filters.code)
)
.map((agent, i) => (
<li key={i}>
name: {agent.name} - code: {agent.code}
</li>
))}
</ul>
</div>
);
}
export default App;
CodeSandox available here
In this example the filters work fine, but their value are not emptied when we use the button Reset.
The filters create their own states to dispatch new state only on blur, and still be controlled. I guess I could use ref here, but I use this example to showcase a simple case of state dependent on another state (and therefore on props).
How should I go about implementing this in an idiomatic React way?
As you can read in the documentation (https://reactjs.org/docs/hooks-state.html#declaring-a-state-variable), your state is created on the first render only and only then it is equal to your initial value.
You could write a custom hook useFilter
and expose your filter-reseter:
const useFilter = (value, setFilters) => {
const [tempValue, setTempValue] = useState(value);
const resetFilter = () => setTempValue(value)
return {
resetFilter,
getInputProps: () => ({
onChange: e => setTempValue(e.target.value),
onBlur: () => setFilters(tempValue),
value: tempValue,
})
}
and instead of doing:
<Filter
value={filters.name}
setFilters={value =>
setFilters(filters => ({ ...filters, name: value }))
}
/>
do this:
const setFilters = value => setFilters(filters => ({ ...filters, name: value }))
const { resetTempFilter, getInputProps } = useFilter(value, setFilters)
...
<input {...getInputProps()} />
You can use a useEffect
hook. The 1st argument is a function and the 2nd argument is an array of dependencies. When a dependency changes value, the function is executed again.
import { useEffect } from 'react';
// ....code removed....
useEffect(() => {
setTempValue(value);
}, [value]);
// ....code removed....
Sandbox with changes: https://codesandbox.io/s/kind-bogdan-ljugv
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With