skip to Main Content

Say I have a parent component for a profile page called Profile, and a child components called Post. I would like to introduce a functionality that updates the content of a post if a user chooses.

I understand why its better to have the ID of the post I am editing in the parent component rather than inside the child Post component, because I would like to only allow for editing one post at a time. Storing the ID of the post I am editing allows me to only edit a post at a time, the one that is in the state.

However I am not sure to go from here using React’s best practices, specifically with the newContent I would like to be the new content of the post being edited.

Should the <input/> content being typed by the user, be stored in a state inside the parent or child component?

If I store for example [newContent, setNewContent] inside the Parent, I would have to send the setNewContent state updater to the child, and update the state on change with e.target.value.
If I instead store it inside the child component, I would just send the current state of newContent inside the onEdit() function passed to the Post component, but then it gets confusing on where or how to then set the newContent(null) after the change has been made. Should it be set to null after the user clicks submitEdit button? inside the updatePostContent function after the post request has been made? (this gets confusing too)

export default function Profile() {
  const [posts, setPosts] = useState([]);
  const [editingPostId, setEditingPostId] = useState(null);

  const updatePostContent = async (id, newContent) => {
    // HTTP put request to update post, and then updates the posts state with updatedPost
  };

  return (
    <div>
      <h1>Profile Page</h1>

      <p>posts: </p>
      {posts.map((post) => (
        <Post
          key={post.id}
          post={post}
          isEditing={editingPostId === post.id}
          onEdit={(newContent) => {
            updatePostContent(post.id, newContent);
          }}
          cancelEdit={() => setEditingPostId(null)}
        />
      ))}
    </div>
  );
}

export function Post({ post, isEditing, onEdit }) {
  return (
    <div>
      <h1>{post.id}</h1>
      <p>{post.content}</p>

      {/* if is editing */}
      {isEditing && (
        <>
          <input type="text" onChange={(e) => console.log(e.target.value)} />
          <button onClick={() => onEdit(newContent)}>submit edit</button>
          <button onClick={cancelEdit}>cancel editing</button>
        </>
      )}
    </div>
  );
}


Just not sure what the correct "React" way of doing this would be. My intuition tells me theres no need to store the newContent state inside the child, since only one post can be edited a time, theres no need for the child component to store whatever is being typed by the input inside itself, since that would mean multiple child components would have a un-used states inside it when it is not really needed since only one post is being edited at once.

What would you guys recommend?

2

Answers


  1. Here’s my two cents:

    TLDR;
    Each component should be aware of its contents/values. That is the purpose of state in React. So, the child should have ownership of newContent. Once the value is submitted, the value should go back to the parent which it supplies back to the child in case of a ‘display the content’ or ‘edit post’ scenario.

    Now, to the descriptive part:
    In this case, the Post component should be aware of the user input that is associated with it, in case it needs to perform some operations on the user input value. Once submitted, the value should be passed back to the Profile component along with the post’s identifier through a callback. The callback method would then store the submitted content against the respective identifier. Prior to the callback, you can clear up the input field on the Post component.

    In case of a ‘list the posts content’ or ‘edit post’ scenario, the content is passed to the Post component as a prop.

    Note: You can also consider to skip storing newContent value in the state of the Post component altogether and directly pass the input value to the callback.

    Hope this helps.

    Login or Signup to reply.
  2. This is how I would see it:

    Option 1

    If the original state (array with posts) is in the parent, I would update the state in the parent and pass the updatePostContent as a prop to your component where you have your update "form".

    const Parent = () => {
      const [posts, setPosts] = useState([]);
      const updatePostContent = async (id, content) => {
        // your logic here
      } 
      return (
        <div>
          <h1>Profile Page</h1>
    
          <p>posts: </p>
          {posts.map((post) => (
            <Post
              key={post.id}
              post={post}
              updatePost={updatePostContent}
            />
          ))}
        </div>
      );
    }
    
    const Post = ({ post, isEditing, updatePost }) => {
      const inputRef = useRef(null);
    
      const submitChagne = () => {
        // handle validation first, for example
        updatePost(post.id, inputRef.current.value);
      }
      return (
        <div>
          <h1>{post.id}</h1>
          <p>{post.content}</p>
    
          {/* if is editing */}
          {isEditing && (
            <>
              <input type="text" ref={inputRef} />
              <button onClick={submitChange}>submit edit</button>
              <button onClick={cancelEdit}>cancel editing</button>
            </>
          )}
        </div>
      );
    }
    
    

    In general I discourage prop drilling (extensive use of props through multiple components) but if it’s just a direct parent, I don’t see why not.

    Option 2

    If you have to go through a lot of components (prop drilling), I would suggest to use a useContext hook.

    Here is a SO answer that explains the context hook a little: React – useContext

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search