Illustration Sara Harvey-Patrick
I recently added a modal to a React app for an educational start-up. The modal offers users a text field to create a new TypeScript object, and selection of the new or existing objects from the database to display.
The feature looked something like this below (the code is here on CodeSandbox). I spun up a toy program to make sure I understood the process, since it was one of my bigger jobs during a front end development internship.
Jump to:
Modal with MUI and hooks
Use Formik, MUI text field to create object, add to select
Pass data from child to parent component, callback example
Components
React modal with MUI and hooks
Back to top
The user clicks “add dog,” and a modal opens to show a text field. The code below works, with the lines relevant to this section highlighted.
AddDogField.tsx
import { useState } from "react";
import { Dialog, DialogContent, TextField } from "@material-ui/core";
import { modalStyles, AddDogContainer, DogButton } from "./AddDogStyles";
import { useFormik } from "formik";
interface AddDogFieldProps {
onCreateDog: (name: string) => Promise<void>;
//setting the event type in an interface passed to const AddDogField
handleClickOpen: () => any;
handleClose: () => any;
}
interface DogFormValues {
dogName: string;
}
export const AddDogField = (props: AddDogFieldProps) => {
// here, state values store the initial state of the modal
const [modalOpen, setModalOpen] = useState(false);
const formik = useFormik({
initialValues: {
dogName: ""
},
onSubmit: async (
values: DogFormValues,
{ resetForm }: { resetForm: () => void }
) => {
if (values.dogName.length > 0) {
await props.onCreateDog(values.dogName);
resetForm();
}
}
});
// custom functions to open and close the modal
function handleClickOpen() {
setModalOpen(true);
}
function handleClickClose() {
setModalOpen(false);
}
// styles come from the AddDogStyles file (on CodeSandbox)
const classes = modalStyles();
return (
<AddDogContainer>
// button to open modal
<DogButton onClick={handleClickOpen}>Add dog</DogButton>
// more info on the Dialog component from MUI
<Dialog
open={modalOpen}
onClose={handleClose}
classes={{
container: classes.modalPlacement,
paper: classes.dialogPaper
}}
fullWidth={true}
maxWidth={"xs"}
>
<DialogContent>
<form onSubmit={formik.handleSubmit}>
<TextField
id="dogName"
name="dogName"
type="dogName"
variant="standard"
placeholder="Dog's name"
fullWidth
style={{ fontSize: "14px" }}
value={formik.values.dogName}
onChange={formik.handleChange}
/>
<DogButton type="submit" style={{ margin: "10px 0px" }}>
Submit
</DogButton>
</form>
// button to close modal
<DogButton onClick={handleClose}>Close</DogButton>
</DialogContent>
</Dialog>
</AddDogContainer>
);
};
Helpful tutorials and docs on modals:
1. Build a Simple React Modal with React, Eden Ella — without hooks
2. Modal Components in React Using Custom Hooks, James Dietrich — hooks, toggle
Use React Formik, MUI text field to create object, add to select
Back to top
The user opens the modal, and sees a text field for entering a dog’s name. The steps are highlighted in yellow, extra notes are in blue, with some modal code removed for clarity.
Child: AddDogField.tsx
import { useState } from "react";
import { useFormik } from "formik";
// parent callback function called by props (more on this in the next section)
interface AddDogFieldProps {
onCreateDog: (name: string) => Promise<void>;
}
interface DogFormValues {
dogName: string;
}
export const AddDogField = (props: AddDogFieldProps) => {
// useFormik (good for simple forms) vs Formik, Jared Palmer on GitHub
const formik = useFormik({
// initialValues (here, dogName), tells Formik which key of "values" to update, Formik
initialValues: {
dogName: ""
},
// 2. The Formik onSubmit handler is passed the form value (the new dog's name), along with FormikBag methods and props
onSubmit: async (
// The values are assigned a string type through DogFormValues
values: DogFormValues,
// "either reset the form after submission, or imperatively reset the form," (Form Foundations).
{ resetForm }: { resetForm: () => void }
) => {
if (values.dogName.length > 0) {
// 3. onCreateDog() is a callback function defined in the parent component, DogList, and passed here via props (interface addDogFieldProps, above). More on this in the next section.
await props.onCreateDog(values.dogName);
// examples of FormikBag: methods that begin with set, resetForm, any props passed to wrapped component
resetForm();
}
}
});
return (
<AddDogContainer>
<DogButton onClick={handleClickOpen}>Add dog</DogButton
<Dialog>
<DialogContent>
// 1. The user enters a name, and it triggers handleSubmit()
<form onSubmit={formik.handleSubmit}>
<TextField
id="dogName"
name="dogName"
type="dogName"
variant="standard"
placeholder="Dog's name"
fullWidth
style={{ fontSize: "14px" }}
value={formik.values.dogName}
onChange={formik.handleChange}
/>
<DogButton type="submit" style={{ margin: "10px 0px" }}>
Submit
</DogButton>
</form>
<DogButton onClick={() => handleClose()}>Close</DogButton>
</DialogContent>
</Dialog>
</AddDogContainer>
);
};
Pass data from React child to parent functional component, example
Back to top
Parent: DogList.tsx (full code)
import { useState } from "react";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import { AddDogField } from "./AddDogField";
import { DogListContainer, ListContainer } from "./AddDogStyles";
interface Dog {
id: number;
name: string;
}
export const DogList = () => {
const [dogs, setDogs] = useState<Dog[]>([]);
const [selectedDog] = useState<number | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const handleClickOpen = () => {
setModalOpen(true);
};
const handleClose = () => {
setModalOpen(false);
};
// 4. Returns the selected dog's name (the value of <MenuItem value={dog.name}>)
const onSelectDog = (event: any) => {
const selectedDogName = event.target.value;
document.getElementById("showName").innerHTML = selectedDogName;
};
// 1. callback function, onCreateDog(), is passed as a prop to the child component to get data from the child's form (the new dog's name) to update the parent's state. It creates a Dog object using the name, and saves it to "dogs." State variable "setDogs" calls the useState hook, which preserves the list of names between re-renders.
const onCreateDog = async (name: string) => {
const newDog: Dog = { name };
const newDogList = [...dogs, newDog];
setDogs(newDogList);
};
return (
<div>
<DogListContainer>
<ListContainer>
// 3. dogs.map returns a list of all dogs for selection
<Select value={selectedDog} onChange={onSelectDog}>
{dogs.map((dog: Dog) => (
<MenuItem value={dog.name}>{dog.name}</MenuItem>
))}
</Select>
</ListContainer>
// 2. passing the callback function to the child as a prop
<AddDogField
onCreateDog={onCreateDog}
/>
</DogListContainer>
</div>
);
};
As a new React developer with an object oriented mindset, I thought of child components as complete programs plugged into the parent. Objects are powerful, and inheritance is automatic, right? It was hard to wrap my mind around data flow, writing code to pass data between the child and parent, and lifting state without Redux.
Passing data from functional child to parent
Importing a child component, like my <AddDogField />
, is the parent engaging with the child. And, here, the parent passes the onCreateDog callback function to the child as a prop, so the child’s data (the new dog’s name) can ultimately update the parent’s state: “A new Dog(name) is born.”
The callback function lifts state up (React docs) to the closest common ancestor (DogList) between the components that use or change this state (both parent DogList and child AddDogField). So, data is still flowing unidirectionally, a tenant of React, from parent to child, and to the view.
Unidirectional flow in React means that users trigger actions in the view, the actions update state, and changes flow back down to the child components and view. So, it’s generally best to update state in response to a DOM event like onClick, or an effect like useEffect.
Helpful posts on passing data between components:
1. Passing Data Between a Parent and Child in React, Jasmine Gump
2. Callback Functions in React, Jason Arnold
3. Unidirectional Data Flow in React, Flaviocopes
Thanks for reading! More about my front end development internship at Mucktracker.com, here. If this post can be improved, DM me on Twitter at @SaraHarvy.