J'ai un composant comme celui-ci :
const MyInput = ({ defaultValue, id, onChange }) => {
const [value, setValue] = useState(defaultValue);
const handleChange = (e) => {
const value = e.target.value;
setValue(value);
onChange(id, value);
};
return (
<div>
<input type="text" value={value} onChange={handleChange} />
</div>
);
};
Je veux qu'il se comporte comme un composant non contrôlé, mais je veux qu'il ait une valeur initiale que nous pouvons définir.
Dans mon composant parent, cela fonctionne bien,
export default function App() {
const [formState, setFormState] = useState({});
const handleChange = (id, value) => {
console.log(id, value, JSON.stringify(formState)); // For debugging later
setFormState({ ...formState, [id]: value });
};
return (
<div className="App">
<pre>{JSON.stringify(formState, null, 2)}</pre>
<MyInput id="1" defaultValue="one" onChange={handleChange} />
<MyInput id="2" defaultValue="two" onChange={handleChange} />
<MyInput id="3" defaultValue="three" onChange={handleChange} />
<MyInput id="4" defaultValue="four" onChange={handleChange} />
<MyInput id="5" defaultValue="five" onChange={handleChange} />
</div>
);
}
mais le problème est que la valeur de formState
ne reflète pas les valeurs sur le MyInputs
jusqu'à ce qu'ils déclenchent un événement onchange.
Donc ok, je peux juste ajouter un hook useEffect 'component did mount' sur MyInput pour déclencher un onChange lorsque le composant se monte pour la première fois.
const MyInput = ({ defaultValue, id, onChange }) => {
useEffect(() => {
onChange(id, defaultValue);
}, []);
const [value, setValue] = useState(defaultValue);
const handleChange = (e) => {
const value = e.target.value;
setValue(value);
onChange(id, value);
};
return (
<div>
<input type="text" value={value} onChange={handleChange} />
</div>
);
};
Mais cela ne se comporte pas comme nous le souhaitons. Chacune des MyInputs appelle onChange lorsqu'elle est montée pour la première fois, mais la référence à formState
est périmé pour le gestionnaire principal onChange dans chaque cas.
Par exemple, la sortie du journal de la console lors du premier chargement de la page est la suivante :
1 one {}
2 two {}
3 three {}
4 four {}
5 five {}
J'ai donc pensé que l'utilisation d'une référence pourrait résoudre ce problème. par exemple :
export default function App() {
const [formState, setFormState] = useState({});
const formStateRef = useRef(formState);
useEffect(() => {
formStateRef.current = formState;
}, [formState]);
const handleChange = (id, value) => {
console.log(
id,
value,
JSON.stringify(formState),
JSON.stringify(formStateRef)
);
setFormState({ ...formStateRef.current, [id]: value });
};
return (
<div className="App">
<pre>{JSON.stringify(formState, null, 2)}</pre>
<MyInput id="1" defaultValue="one" onChange={handleChange} />
<MyInput id="2" defaultValue="two" onChange={handleChange} />
<MyInput id="3" defaultValue="three" onChange={handleChange} />
<MyInput id="4" defaultValue="four" onChange={handleChange} />
<MyInput id="5" defaultValue="five" onChange={handleChange} />
</div>
);
}
Ça ne marche pas. Il semble que tous les gestionnaires de changement déclenchent avant useEffect sur formState a la possibilité de mettre à jour la ref.
1 one {} {"current":{}}
2 two {} {"current":{}}
3 three {} {"current":{}}
4 four {} {"current":{}}
5 five {} {"current":{}}
Je suppose que je pourrais directement muter la référence dans la fonction handleChange, et la définir ?
export default function App() {
const [formState, setFormState] = useState({});
const formStateRef = useRef(formState);
const handleChange = (id, value) => {
console.log(
id,
value,
JSON.stringify(formState),
JSON.stringify(formStateRef)
);
formStateRef.current = { ...formStateRef.current, [id]: value };
setFormState(formStateRef.current);
};
return (
<div className="App">
<pre>{JSON.stringify(formState, null, 2)}</pre>
<MyInput id="1" defaultValue="one" onChange={handleChange} />
<MyInput id="2" defaultValue="two" onChange={handleChange} />
<MyInput id="3" defaultValue="three" onChange={handleChange} />
<MyInput id="4" defaultValue="four" onChange={handleChange} />
<MyInput id="5" defaultValue="five" onChange={handleChange} />
</div>
);
}
Ça marche. Mais cette utilisation d'arbitres me met un peu mal à l'aise.
Quelle est la méthode standard pour réaliser quelque chose comme ça ?