A bit new to React here.
While developing a personal project based on React, I often came up against scenarios where I needed child components to update state passed down to it by a parent, and have the updated state available in both child and parent components.
I know React preaches a top-down flow of immutable data only, but is there an elegant way of solving this issue?
The way I came up with is as shown below. It gets the job done, but it looks and feels ugly, bloated, and it makes me think I'm missing something obvious here that a well-established framework like React would've definitely accounted for in a more intuitive way.
As a simple example, assume that I have a couple of nested components:
Root
Author
Post
Comment
And that I need each child component to be able to modify the state such that it is also accessible to its parent component as well. A use case might be that you could interact with the Comment component to edit a comment, and then interact with a SAVE button defined in the Root component to save the entire state to a database or something like that.
The way I presently handle such scenarios is this:
const Root = ({}) => {
const [data, setData] = React.useState({
author: {
name: 'AUTHOR',
post: {
content: 'POST',
comment: {
message: 'COMMENT'
}
}
}
});
const onEditAuthor = value => {
setData({ author: value });
}
const onSave = () => {
axios.post('URL', data);
}
return <>
<Author author={data.author} onEditAuthor={onEditAuthor} />
<button onClick={() => onSave()}>SAVE</button>
</>
}
const Author = ({ author, onEditAuthor }) => {
const onEditPost = value => {
onEditAuthor({ name: author.name, post: value });
}
return <Post post={author.post} onEditPost={onEditPost} />
}
const Post = ({ post, onEditPost }) => {
const onEditComment = value => {
onEditPost({ content: post.content, comment: value });
}
return <Comment comment={post.comment} onEditComment={onEditComment} />
}
const Comment = ({ comment, onEditComment }) => {
return <input defaultValue={comment.message} onChange={ev => onEditComment({ message: ev.target.value })} />
}
When you change the Comment, it calls the Post.onEditComment() handler, which in turn calls the Author.onEditPost() handler, which finally calls the Root.onEditAuthor() handler. This finally updates the state, causing a re-render and propagates the updated state all the way back down.
It gets the job done. But it is ugly, bloated, and looks very wrong in the sense that the Post component has an unrelated onEditComment() method, the Author component has an unrelated onEditPost() method, and the Root component has an unrelated onEditAuthor() method.
Is there a better way to solve this?
Additionally, when the state finally changes, all components that rely on this state are re-rendered whether they directly use the comment property or not, as the entire object reference has changed.
I came across https://hookstate.js.org/ library which looks awesome. However, I found that this doesn't work when the state is an instance of a class with methods. The proxied object has methods but without this reference bound properly. I would love to hear someone's solution to this as well.
Thank you!