javascriptreactjsreact-hooksdynamically-generatedreact-modal

Show modal based on post id


I am working on my blog and am trying to implement Sanity. I was able to get my posts to show up with the json object returned from query with useState I am trying to populate my React-Modal with the correct contents based on the post I have clicked with its _id or some kind of key. I simplified the code so it wouldn't be too long:

export default function News() {
  // Json objects stored in posts 
  const [posts, setPosts] = useState([]);

  // Used to toggle Modal on and off
  const [isOpen, setIsOpen] = useState(false);
  function toggleModal() {
    setIsOpen(!isOpen);
  }

  return (
    <>
      {posts.map((posts) => (
        <div key={posts._id}>
           <h3 className="title" onClick={toggleModal}>
              {posts.title}
           </h3>
           <div">
              <a>
                <span onClick={toggleModal}>Read More</span>
              </a>
           </div>
           // Clicking on either span or a tag shows the Modal
            <Modal
               isOpen={isOpen}
               onRequestClose={toggleModal}>
               // Closes modal
              <button className="close-modal" onClick={toggleModal}>
                  <img
                    src="assets/img/svg/cancel.svg"
                    alt="close icon"/>
              </button>
              // Want to show content based on _id
              <h3 className="title">{posts.title}</h3>
              <p className="body">{posts.body}</p>
        </div>
      )
    </>
  )
}

Whenever I click on a certain post, it always toggles on the first object. Click to see gif demo

Edit: I was able to get it to work based on the answer given

  const [state, setState] = useState({ isOpen: false, postId: null });

  const openModal = React.useCallback(
    (_key) => () => {
      setState({ isOpen: true, postId: _key });
    },
    []
  );

  function closeModal() {
    setState({ isOpen: false, postId: null });
  }

And with Modal tag I added

key={post.id == state.postId}

Now every divs and tags that renders the correct content. However, I'm facing a slight issue. If I click on post[2] and it renders out post[0] content and in a blink of an eye changes to the correct content. Then when I click on post1, it renders and post[2] content and changes to the correct one. It keeps rendering the previous post. It's all in a blink of an eye, but still visible.


Solution

  • I can suggest using react hooks to solve your problem.

    You can pass a function to useCallback's return, you can then call your function normally in the render by passing params to it.

    See more: https://reactjs.org/docs/hooks-reference.html#usecallback

    import * as React from 'react';
    
    export default function News() {
      // Json objects stored in posts 
      const [posts, setPosts] = useState([]);
    
      // Used to toggle Modal on and off
      const [isOpen, setIsOpen] = useState(false);
      
      // React.useCallback.
      const toggleModal = React.useCallback((id) => () => {
          setIsOpen(!isOpen);
          console.log(`Post id: ${id}`);
      }, []);
    
      return (
        <>
          {posts.map((post) => (
            <div key={post._id}>
               <h3 className="title" onClick={toggleModal(post._id)}>
                  {post.title}
               </h3>
               <div">
                  <a>
                    <span onClick={toggleModal(post._id)}>Read More</span>
                  </a>
               </div>
               // Clicking on either span or a tag shows the Modal
                <Modal
                   isOpen={isOpen}
                   onRequestClose={toggleModal(post._id)}>
                   // Closes modal
                  <button className="close-modal" onClick={toggleModal(post._id)}>
                      <img
                        src="assets/img/svg/cancel.svg"
                        alt="close icon"/>
                  </button>
                  // Want to show content based on _id
                  <h3 className="title">{post.title}</h3>
                  <p className="body">{post.body}</p>
            </div>
          )
        </>
      )
    }