Reputation: 1080
Creating multiple child components, which each can setState of its parent, makes these child components have different versions of history of the state (aka stale state)
To reproduce the error:
Another error:
All in all, I want the resulting state to have ALL the data of each components. How can I fix this?
https://codesandbox.io/s/blissful-fog-oz10p
const Admin = () => {
const [links, setLinks] = useState({});
const [newLink, setNewLink] = useState([]);
const updateLinks = (socialMedia, url) => {
setLinks({
...links,
[socialMedia]: url
});
};
const linkData = {
links,
updateLinks
};
const applyChanges = () => {
console.log(links);
};
return (
<>
{newLink ? newLink.map(child => child) : null}
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={() => {
setNewLink([
...newLink,
<AddNewLink key={Math.random()} linkData={linkData} />
]);
}}
>
Add new social media
</Button>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{ marginTop: "50px" }}
onClick={() => applyChanges()}
>
Apply Changes
</Button>
<h3>{JSON.stringify(links, null, 4)}</h3>
</div>
</>
);
};
export default Admin;
const AddNewLink = props => {
const [socialMedia, setSocialMedia] = useState("");
const [url, setUrl] = useState("");
const { updateLinks } = props.linkData;
const handleSubmit = () => {
updateLinks(socialMedia, url);
};
return (
<>
<FormControl
style={{ marginTop: "30px", marginLeft: "35px", width: "90%" }}
>
<InputLabel>Select Social Media</InputLabel>
<Select
value={socialMedia}
onChange={e => {
setSocialMedia(e.target.value);
}}
>
<MenuItem value={"facebook"}>Facebook</MenuItem>
<MenuItem value={"instagram"}>Instagram</MenuItem>
<MenuItem value={"tiktok"}>TikTok</MenuItem>
</Select>
</FormControl>
<form
noValidate
autoComplete="off"
style={{ marginBottom: "30px", marginLeft: "35px" }}
>
<TextField
id="standard-basic"
label="Enter link"
style={{ width: "95%" }}
onChange={e => {
setUrl(e.target.value);
}}
/>
</form>
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{ marginBottom: "30px" }}
onClick={() => handleSubmit()}
>
Submit
</Button>
</div>
</>
);
};
export default AddNewLink;
Upvotes: 3
Views: 1044
Reputation: 29282
The problem you are facing is caused by updateLinks()
function closing over the links
state.
When you create two AddNewLink
components, each of them is passed updateLinks()
function. Since initially, links
is an empty object, updateLinks()
function passed to both instances of AddNewLink
, has a closure over the links
variable and links
at that time refers to an empty object {}
. So when you submit the form, as far as updateLinks()
function is concerned, links
is an empty object. So when merging links
with the data passed from AddNewLink
component, only the data from the submitted form is saved in the state because links
is an empty object.
Solution:
You could use useRef
hook to access the latest value of links
inside updateLinks()
function.
const Admin = () => {
...
const linkRef = useRef();
const updateLinks = (socialMedia, url) => {
linkRef.current = { ...linkRef.current, [socialMedia]: url };
setLinks(linkRef.current);
};
...
};
Also i don't think you need more than one instance of AddNewLink
component. You could add more than one social media link in the state with only a single instance of AddNewLink
component.
Demo:
Upvotes: 3