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
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:
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
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.