I'm trying to create a reusable "Modal" component in React. The Modal component will have some input fields and a submit button and when user clicks the Submit button in the Modal. the modal should be closed and the data user entered in the input fields should be passed up to the parent component through a callback function declared in parent component. The code is working fine but i think(not sure) it's not the right solution as i had to create "React state" in both the Modal(child) component and the parent component where the Modal component is used. As you can see(sandbox code) the state is repetitive in both components and i was wondering how can i keep the state in only one place. My understanding is I'd definitely need to create a state in Modal component for the input fields to keep track of changes but i am not sure how can i use that data in parent component without creating same state
Here is a Sandbox that i created to show what i've done so far:
https://codesandbox.io/s/jovial-matsumoto-1zl9b?file=/src/Modal.js
In order to not duplicate state I would enclose the inputs in the modal in a form
element and convert the inputs to uncontrolled inputs. When the form is submitted grab the form field values and pass in the formData
callback and reset the form. Explicitly declare the button to be type="submit"
.
const Modal = (props) => {
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
props.formData({ firstName, lastName });
e.target.reset();
};
if (!props.show) {
return null;
} else {
return (
<div className="modal" id="modal">
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</div>
);
}
};
It seems the crux of your question is about the code duplication between your parent component and a modal. What I would really suggest here is to decouple the modal from any specific use case and allow any consuming parent components to pass a close handler and children components.
This keeps the state in the parent, and thus, control.
Example modal component:
const Modal = ({ onClose, show, children }) =>
show ? (
<div className="modal" id="modal">
<button type="button" onClick={onClose}>
X
</button>
{children}
</div>
) : null;
Parent:
function App() {
const [isShowModal, setIsShowModal] = useState(false);
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const showModal = (e) => {
setIsShowModal((show) => !show);
};
const closeModal = () => setIsShowModal(false);
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
setFirstName(firstName);
setLastName(lastName);
e.target.reset();
closeModal();
};
return (
<div className="App">
<h2>First Name is : {firstName}</h2>
<h2>Last Name is : {lastName}</h2>
<button className="toggle-button" onClick={showModal}>
Show Modal
</button>
<Modal show={isShowModal} onClose={closeModal}>
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</Modal>
</div>
);
}
Here it's really up to you how you want to manage the interaction between parent and modal content. I've shown using a form and form actions so your state isn't updated until a user submits the form. You can use form utilities (redux-form, formix, etc...) or roll your own management. Life's a garden, dig it.