Reputation: 397
I need to use ref
to get a grandchild component's state, and I customized it by using useImperativeHandle
, the whole code is simplified
here.
function App() {
const ref = useRef(null);
return (
<div className="App">
<button
onClick={() => console.log(ref.current.name, ref.current.age)}
>click me</button>
<FilterFather ref={ref} />
</div>
);
}
const FilterFather = (_, ref) => {
const filter1Ref = useRef(null);
const filter2Ref = useRef(null);
useImperativeHandle(ref, () => ({
name: filter1Ref.current.name,
age: filter2Ref.current.age,
}))
return (
<>
<Filter1 ref={filter1Ref}/>
<Filter2 ref={filter2Ref} />
</>
)
}
export default forwardRef(FilterFather);
const Filter1 = (props, ref) => {
const [name, setName] = useState('lewis')
useImperativeHandle(ref, () => ({
name
}), [name])
return (
<>
<div>
name:
<input
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
</>
)
}
const Filter2 = (props, ref) => {
const [age, setAge] = useState(18)
useImperativeHandle(ref, () => ({
age
}), [age])
return (
<>
<div>
age:
<input
value={age}
onChange={e => setAge(e.target.value)}
/>
</div>
</>
)
}
export default {
Filter1: forwardRef(Filter1),
Filter2: forwardRef(Filter2),
}
one layer of forwardRef
and useImperativeHandle
works fine, two layers went wrong
Upvotes: 4
Views: 5994
Reputation: 16309
Your imperative handle in FilterFather
is not required. It doesn't add/remove anything to the handle. You can just forward it directly:
const FilterFather = (_, ref) => {
return <Filter ref={ref} />;
};
Also there is a problem with it because it will not update correctly.
useImperativeHandle(ref, () => ({
name: filterRef.current.name,
age: filterRef.current.age,
}), [filterRef]) // this will not update correctly
You passed filterRef
as a dependency but filterRef
is static and will not change even when filterRef.current.name
or filterRef.current.age
changes.
Note that useImperativeHandle
is not supposed to be used to read state from child components. It is actually discouraged to use any kind of imperative methods in react except there is no other way. This is most of the time the case if you work with 3rd party libraries that are imperative in nature.
EDIT:
Given your updated code the react way to do that is to lift state up. It doesn't require any refs:
export default function App() {
const [values, setValues] = useState({
name: "lewis",
age: 18
});
const handleChange = useCallback(
(key, value) => setValues(current => ({ ...current, [key]: value })),
[]
);
return (
<div className="App">
<button onClick={() => console.log(values.name, values.age)}>
click me
</button>
<FilterFather values={values} onChange={handleChange} />
</div>
);
}
const FilterFather = props => {
return (
<>
<Filter label="Name" name="name" {...props} />
<Filter label="Age" name="age" {...props} />
</>
);
};
const Filter = ({ label, name, values, onChange }) => {
const handleChange = useCallback(e => onChange(name, e.target.value), [
name,
onChange
]);
return (
<>
<div>
<label>{label}:</label>
<input value={values[name]} onChange={handleChange} />
</div>
</>
);
};
Upvotes: 3