Can you pass hooks as a prop value from an useEffect in React.js?

Viewed 45

I'm new to react and think I'm not understanding the concept of a useEffect. I have an array of objects in a .js file called cardData. I use this data to automatically generate cards and each card generates a button with the id based on that data when mapped. In this component, I'm trying to open a modal that will compare the id from an onMouseOver event to the data from the array of objects and find the object with the same id, so that I only render the content based on which cards button is clicked.

function Portfolio(){
     
    const [modalOpen, setModalOpen] = useState(false);
    const close = () => setModalOpen(false);
    const open = () =>  setModalOpen(true) ;
    
    
    const [letterClass, setLetterClass] = useState("text-animate");
    const [ mouseId, setMouseId ] = useState(1);
    // const modalData = useRef([]);
    const [modalData, setModalData] = useState([]);
    

    useEffect(() => {
        setTimeout(() => {
          return setLetterClass('text-animate-hover')
        }, 3000)
      }, [])

      function handleMouseOver(event) {
        setMouseId(event.target.id)
      }

      function handleData(data){
        setModalData(cardData.find(obj => obj.id === data))
      }

If I hardcode a number into the handleData prop, it works, but when I set the prop equal to my mouseId, it returns undefined. The error shows: Cannot read properties of undefined (reading 'title').

    useEffect(() => {
        handleData(mouseId)
    }, [mouseId])
    

    function handleClick() {
        if (!modalOpen) {
            open();
        } else if (modalOpen) {
            close();
            
        }
    }

    return (<>
    <div className="port-content">
        <div className="container portfolio-page">
            <div className="port-text-zone">
                <h1>
                <AnimatedLetters idx={15} letterClass={letterClass} strArray={["M","y"," ","p","o","r","t","f","o","l","i","o","."]}/>
                </h1>
                <p>Since I'm such a versatile junior, I've worked on many different types of projects. My projects include design, front-end development and back-end development.</p>
            </div> 
        </div>
        <GeneratedCards id={handleClick} mouse={handleMouseOver}/>
        
    </div>
    {modalOpen && <><Modal 
    modalOpen={modalOpen} 
    handleClose={close} 
    />
            <ModalContent
                 title={modalData.title}
                 content={modalData.description}
                 desc1={modalData.modalDescription1}
                 desc2={modalData.modalDescription2}
                 desc3={modalData.modalDescription3}
                 projectLink={modalData.title} 
            /></>
    }
        <Loader type="ball-grid-beat" />
    </>)
}

export default Portfolio;

I think I'm getting this error either due to a useEffect not being able to pass data over to functions through props, or my data is set after I handle the click and the modal will not be able to display the content, because the specific object is not set in the before the onClick. Here is the github link. https://github.com/HenryFord1998/my-portfolio-site/tree/refactor-and-Update

2 Answers

the mistake is from here const [modalData, setModalData] = useState([]); where u set initial value of modalData to empty array, instead const [modalData, setModalData] = useState({}); so useEffect is run after the component is finish render, thats why your modal component get undefined, because it try to use value from modal data array like an object

u need to guard it before modalData is ready, u can use like this

{modalOpen &&  (
<>
<Modal 
    modalOpen={modalOpen} 
    handleClose={close} 
    />
     {modalData && (
<ModalContent
                 title={modalData.title}
                 content={modalData.description}
                 desc1={modalData.modalDescription1}
                 desc2={modalData.modalDescription2}
                 desc3={modalData.modalDescription3}
                 projectLink={modalData.title} 
            />
)}
    </>
)}

Issue

The main issue is that the id properties in the cardData array are numbers, but the mouseId state is updated to a string type by event.target.id.

function handleMouseOver(event) {
  setMouseId(event.target.id); // <-- string type
}

When trying to find a matching cardData object a strict (===) check is used.

function handleData(data){
  setModalData(cardData.find(obj => obj.id === data)) // <-- type mismatch
}

Since the types don't match they can never be strictly equal. Array.prototype.find returns undefined if no match is found via the predicate function.

Solution

Use a type-safe comparison. I suggest converting to string types.

function handleData(data){
  setModalData(cardData.find(obj => String(obj.id) === String(data)))
}

And in the case that a match still isn't found the modal should have a an additional guard on it.

{modalOpen && modalData && (
  <>
    <Modal 
      modalOpen={modalOpen} 
      handleClose={close} 
    />
    <ModalContent
      title={modalData.title}
      content={modalData.description}
      desc1={modalData.modalDescription1}
      desc2={modalData.modalDescription2}
      desc3={modalData.modalDescription3}
      projectLink={modalData.title} 
    />
  </>
)}
Related