Form in React
This section discuss how to implements a form in React.
Allow User to Create Movie
It would be nice if our users can create a movie themselves.
Prerequisite: API Service for Create Movie
Before you can allow user to create movie, first of all the backend API Service must be able to support that.
Luckily our movies API support that. To able to create a movie, install this Restlet Client, which is a Chrome extension to allow you to make API calls.
After the extension is installed,
- open the extension by clicking the icon
- select “POST” in the METHOD dropdown.
- enter the following URL in the URL bar:
https://react-intro-movies.herokuapp.com/movies - add the following content in the BODY field (change it to your favourite movie):
{ "name": "More Than Blue", "releaseDate": "2018-12-27" } - click send

Now when you load your app, you should be see your movie is added.
Add Ajax Call Function to Make the POST Request
Now that we know the actual AJAX call works by using tools, let’s proceed to do that with our code.
Create a createMovie function in api.js that will make the request
export const createMovie = (movie) =>
axios.post('https://react-intro-movies.herokuapp.com/movies', movie).then((res) => res.data);
- axios allows you to makes API call with specific method, e.g.
get,post,putetc., corresponding to our REST call methods. - the second parameter of
axios.postis the body of the data that you want to submit.
Create Form Component
Now that we have function to make the API call, let’s create the form component.
Add a file movie-form.js with the following content:
import * as React from 'react';
import { createMovie } from './api';
export const MovieForm = () => {
const [name, setName] = React.useState('');
const [releaseDate, setReleaseDate] = React.useState('');
const handleSubmit = (ev) => {
ev.preventDefault();
createMovie({
name,
releaseDate,
}).then(() => {
setName('');
setReleaseDate('');
});
};
return (
<div className="movie-form">
<form onSubmit={handleSubmit}>
<legend>Create Movie</legend>
<div className="field">
<label htmlFor="name" className="label">
Name
</label>
<input
className="input"
value={name}
id="name"
name="name"
onChange={(ev) => setName(ev.target.value)}
required
/>
</div>
<div className="field">
<label htmlFor="releaseDate" className="label">
Release Date
</label>
<input
className="input"
value={releaseDate}
id="releaseDate"
name="releaseDate"
type="date"
onChange={(ev) => setReleaseDate(ev.target.value)}
required
/>
</div>
<div className="button-container">
<button type="submit" className="submit-button">
Create
</button>
</div>
</form>
</div>
);
};
- we declare two states:
nameandreleaseDatefor the 2 values for the form. - the state value is passed to the
valueattribute of the input, while the state setter is called in theonChangecallback. - we define a
handleSubmitfunction, which will be passed toonSubmitprops of the form element. When form is submitted, we will callcreateFormwith the state. We callevent.preventDefaultbecause by default form submission will cause a page refresh, and we doesn’t want that.
Add MovieForm Into App
Now import MovieForm and include it in App component:
import { MovieForm } from './movie-form';
// existing code
function App() {
const [moviesShown, toggleShowMovies] = useToggle(false);
const { movies, isLoading } = useMovieData();
return (
<div>
<TitleBar>
<h1>React Movie App</h1>
</TitleBar>
<div className="container">
<div>
<div className="button-container">
<Button onClick={toggleShowMovies}>{moviesShown ? 'Hide' : 'Show'} Movies</Button>
</div>
{moviesShown && (
<BusyContainer isLoading={isLoading}>
{movies.map((movie) => (
<Movie name={movie.name} releaseDate={movie.releaseDate} key={movie.id} />
))}
</BusyContainer>
)}
</div>
</div>
<div>
<MovieForm />
</div>
</div>
);
}
Now try to use the form, you can see the page is making the AJAX call, and after you refresh the page, the new movie will be there!
Refresh Movie List after Submission
Currently the movie list is not updated after you create the new movie, which is a bug.
Therefore, we need to somehow let me App component know that the MovieForm has created a record, and it should refresh the list.
We can achieve this by passing a callback from App to MovieForm.
-
Update
useMovieDatacustom hook inAppto returnloadMoviesData:function useMovieData() { const [movies, setMovies] = React.useState([]); const [isLoading, setIsLoading] = React.useState(true); const loadMoviesData = () => { setIsLoading(true); loadMovies().then((movieData) => { setMovies(movieData); setIsLoading(false); }); }; React.useEffect(loadMoviesData, []); return { movies, isLoading, loadMoviesData, }; } -
Pass
loadMoviesDatafunction toMovieFormcomponent asonSubmitSuccessprops;function App() { const [moviesShown, toggleShowMovies] = useToggle(false); const { movies, isLoading, loadMoviesData } = useMovieData(); return ( <div> <TitleBar> <h1>React Movie App</h1> </TitleBar> <div className="container"> <div> <div className="button-container"> <Button onClick={toggleShowMovies}>{moviesShown ? 'Hide' : 'Show'} Movies</Button> </div> {moviesShown && ( <BusyContainer isLoading={isLoading}> {movies.map((movie) => ( <Movie name={movie.name} releaseDate={movie.releaseDate} key={movie.id} /> ))} </BusyContainer> )} </div> </div> <div> <MovieForm onSubmitSuccess={loadMoviesData} /> </div> </div> ); } -
In
MovieForm, callonSubmitSuccesswhencreateMovieajax succeeds:export const MovieForm = ({ onSubmitSuccess }) => { const [name, setName] = React.useState(''); const [releaseDate, setReleaseDate] = React.useState(''); const handleSubmit = (ev) => { ev.preventDefault(); createMovie({ name, releaseDate, }).then(() => { onSubmitSuccess(); setName(''); setReleaseDate(''); }); }; // existing code };
Now the movie list should be updated once you submit create movie!
Extract Form State to Custom Hook
Let’s extract out some code in MovieForm:
import * as React from 'react';
import { createMovie } from './api';
// highlight-start
const useMovieForm = () => {
const [name, setName] = React.useState('');
const [releaseDate, setReleaseDate] = React.useState('');
return {
setName,
setReleaseDate,
values: {
name,
releaseDate,
},
};
};
// highlight-end
export const MovieForm = ({ onSubmitSuccess }) => {
const { values, setName, setReleaseDate } = useMovieForm(); // highlight-line
const handleSubmit = (ev) => {
ev.preventDefault();
createMovie(values).then(() => {
onSubmitSuccess();
setName('');
setReleaseDate('');
});
};
return (
<div className="movie-form">
<form onSubmit={handleSubmit}>
<legend>Create Movie</legend>
<div className="field">
<label htmlFor="name" className="label">
Name
</label>
<input
className="input"
value={values.name}
id="name"
name="name"
onChange={(ev) => setName(ev.target.value)}
required
/>
</div>
<div className="field">
<label htmlFor="releaseDate" className="label">
Release Date
</label>
<input
className="input"
value={values.releaseDate}
id="releaseDate"
name="releaseDate"
type="date"
onChange={(ev) => setReleaseDate(ev.target.value)}
required
/>
</div>
<div className="button-container">
<button type="submit" className="submit-button">
Create
</button>
</div>
</form>
</div>
);
};
- we extract out the two form input states into
useMovieFormcustom hook, and use that inMovieFormcomponent.
Do It: Create Movie Form
- Create
MovieFormcomponent will will make API call to backend viacreateMoviefunction. - Include
MovieForminAppand make sure creation is working. - Enhance application to auto refresh movie list when creation is success.
- Extract out form data to
useMovieFormcustom hook.
