skip to Main Content

I want to create a flexible modal wrapper where I can put interactive components within. In this case, I want to put an input into the modal that would affect the overall state of the form.

The form:

  const [formInfo, setFormInfo] = useState({});
  const [modalMetadata, setModalMetadata] = useState({});
  const [modalContents, setModalContents] = useState({});
  const [modalShow, setModalShow] = useState(false);

...
        return <ModalWrapper modalIsOpen={modalShow} closeModal={() => {setModalShow(false)}} contents={modalMetadata}>
            {modalContents}
          </ModalWrapper>
          <button onClick={
            setModalShow(true);
            setModalMetadata({
              contentLabel: "",
              title: "Are you sure?",
              buttons: [
                {
                  onClick: () => setModalShow(false),
                  content: "Cancel"
                },
                {
                  onClick: () => submitForm(),
                  content: "Confirm"
                }
              ],
            })}

            setModalContents(<div><h4>blahblahblah</h4>
              {inputTitles.map((k) => <div key={"field-"+ k}>
                  <div>
                  <input 
                    type='text'
                    onChange={i => {
                      console.log(i.target.value);
                      console.log(formInfo)
                      console.log({...formInfo, ["field-" + k]: i.target.value})
                      setFormInfo( formInfo => {return {...formInfo, ["field-" + k]: i.target.value}})
                    }}
                  />
                  <span></span>  
                  </div>
                  <label>${k}</label>
                </div>)}
             </div>);



}>

The wrapper:

import Modal from 'react-modal';    


export default function ModalWrapper({ modalIsOpen, closeModal, contents, children }) {
  
    return (
      <div>
        <Modal
          isOpen={modalIsOpen}
          onRequestClose={closeModal}
          contentLabel={contents.contentLabel}
        >
          <h2>{contents.title}</h2>
            {children}
          <div>
            {(contents?.buttons ?? []).map((i, idx) => <button className={i.className} key={"button-" + idx} onClick={i.onClick}>{i.content}</button>)   }

          </div>
        </Modal>
      </div>
    );
}

(CSS and irrelevant variables removed for brevity)

While most the debugging console statements are returning expected values and the buttons are working as planned, I’ve noticed that formInfo is not being updated. What is the reason behind setFormInfo not working as expected in the modal contents?

I looked online and I found multiple examples of this mechanic working (1, 2), where they would pass a React hook through the child of another component, but I couldn’t replicate the error I was having. Would someone with a better understanding of React children and the nature of React hooks know what’s wrong?

[2] I attempted to add onChange attributes to the examples given as a standalone input element and the Avatar element, but it works as planned.

2

Answers


  1. Chosen as BEST ANSWER

    I think that I found the answer: the issue is that I've been using useState with interactive HTML components. It's apparently highly not recommended to use HTML within a state; and when I ended up moving the HTML into an object and kept the state limited to the key of said object to access it, I got much better results.

    Here's what it kind of meant in my code:

      const [modalMetadata, setModalMetadata] = useState({});
      const [modalContentsKey, setModalContentsKey] = useState("");
      const [modalShow, setModalShow] = useState(false);
    
    
      const modalContents = {
        "": <div></div>,
        confirmation: <div><h4>blahblahblah</h4>
                  {inputTitles.map((k) => <div key={"field-"+ k}>
                      <div>
                      <input 
                        type='text'
                        onChange={i => {
                          console.log(i.target.value);
                          console.log(formInfo)
                          console.log({...formInfo, ["field-" + k]: i.target.value})
                          setFormInfo( formInfo => {return {...formInfo, ["field-" + k]: i.target.value}})
                        }}
                      />
                      <span></span>  
                      </div>
                      <label>${k}</label>
                    </div>)}
                 </div>
    
      }
    
    ...
            return <ModalWrapper modalIsOpen={modalShow} closeModal={() => {setModalShow(false)}} contents={modalMetadata}>
                {modalContents[modalContentsKey]}
              </ModalWrapper>
              <button onClick={ () => {
                setModalShow(true);
                setModalMetadata({
                  contentLabel: "",
                  title: "Are you sure?",
                  buttons: [
                    {
                      onClick: () => setModalShow(false),
                      content: "Cancel"
                    },
                    {
                      onClick: () => submitForm(),
                      content: "Confirm"
                    }
                  ],
                });
                setModalContentsKey("confirmation");
              }}
             />
    

  2. The working of states in react is a little bit confusing many a times. The useState hook updates the state variable whose updated value will be available to the component in its next render.

    For example, if you do

    const [text, setText] = useState("");
    
    setText("text");
    console.log(text);
    

    the console.log will print the value of text as an empty string. In the next render you will be able to see "text" in the console.

    The best way to set state variables to be passed to child components is to set them in the useEffect hook.

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