How to build a React App
I’m learning React right now, so this is a post about how to do a simple application - setup, simple code.
These are my plans:
- Build MVP for adding/searching to a list in React
- Refactor app to React-Redux (to be shown in a following post)
- Pull data from API and implement a backend
- Style app for production aesthetic
Setup: Dependencies
Babel for ES6 transpilation
npm install -g babel-cli
Live server to run our code on a live reloading server
npm install live-server
Then we’ll run these and set babel to watch and compile whenever we save.
babel . --out-dir compiled --presets=react --ignore=node_modules,compiled --source-maps inline --watch
live-server
Setup: file structure
Then we’ll organize our directories and other dependencies.
- Create folders: src/components, src/data, src/styles
- Create index files: src/index.js ./index.html
- Create package.json: make sure to include react, react-dom, jquery, babel, etc
- Index.html: dependencies like jquery and styles go in the header, component js files go at the end of body
For this example we are going to put our scripts in our HTML, but note that if we were building with webpack, we’d only need to include one script.
<html>
<head>
<script src="node_modules/jquery/dist/jquery.js"></script>
<script src="node_modules/react/dist/react.js"></script>
<script src="node_modules/react-dom/dist/react-dom.js"></script>
<script src="node_modules/lodash/lodash.js"></script>
</head>
<body>
<div id="app">
</div>
<script type="module" src="compiled/src/components/App.js"></script>
<script type="module" src="compiled/src/index.js"></script>
</body>
</html>>
Our React components go at the end of our body. It’s very important the body loads first, since we attach our app to the app div and want to be sure it loads. Note we don’t have to include all our components - just our index.js. We don’t want to needlessly import files and cause overhead.
Index.js
Index.js only needs two things for our basic app:
- Import App component
- Redner the App component to the dom
import App from './components/App.js';
ReactDOM.render(<App />, document.getElementById('app'));
App.js
Imports
App.js should import all the components it needs to directly render. For our example, we’re importing a movie list, a search field, and a field to add movies. We are also importing our example data.
import movies from '../data/exampleData.js';
import MovieList from './MovieList.js';
import Search from './Search.js';
import AddMovie from './AddMovie.js';
Constructor
Our constructor doesn’t take any props but we’ll set it up to do so anyway, and pass those props to our superclass - React.component.
In the constructor, we’ll also set our app’s initial state.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: movies,
results: movies,
found: true
};
}
componentDidMount
We don’t have a componentDidMount function in this app, but I wanted to mention if we did, it’d be executed immediately following the completion of the constructor function.
Add Movie functionality
First we create a handler for when users submit a new movie. The handler should:
- Prevent default form submit action (page reoload)
- Get the title from the input field
- Pass the title to a function to add the movie
addHandler(e) { e.preventDefault(); var title = document.getElementById("add-title").value; this.addMovie(title); }
Then we create a function to process the movie title. It should:
- Create a new movie object
- Get the current movie list from the app’s state
- Set the app’s movies state to include the new movie - this automatically causes a re-render of the App component
- Also set the app’s search result state to be the same as the movies state, since it’s assumed we should see all movies again if we were to add while viewing search results
addMovie(title) { var newMovie = { title: title }; var movieList = this.state.movies.concat(newMovie); this.setState({ movies: movieList, results: movieList }); }
Search functionality
The search should be ran on submit. First we create a handler for the submit. It should:
- Prevent event default (page reload)
- Get the search query from the input field
- Pass the query to a search function
searchHandler(e) { e.preventDefault(); var search = document.getElementById("search").value; this.searchMovies(search); }
Then we write the search function, which:
- Filters the movies state to create an array containing our search term
- Sets the results state to our filtered array
- Updates the found status (true if our filtered array has one or more movies)
The purpose of the found status is to show/hide the movie list or an error message depending on whether any matches are returned.
searchMovies(search) {
var searchArr = this.state.movies.filter(movie => movie.title.toLowerCase().indexOf(search.toLowerCase()) >= 0);
this.setState({
results: searchArr,
found: searchArr.length > 0 ? true : false
});
}
Render
Almost there. The App component needs to render its child components as JSX objects, passing in functions and states as props.
- AddMovie component has a form and will need to receive the addHandler as a prop so it can run function on submit
- Functions that are passed down need this bindings
- Search will need the submitHandler, so it will get that passed down as a prop
- MovieList needs the result state so it knows what to display (search results or all movies), and the found state so it can know whether to show/hide the list
render() { return ( <div> <div className="navbar">MovieList</div> <AddMovie addHandler={this.addHandler.bind(this)} /> <Search submitHandler={this.searchHandler.bind(this)} /> <MovieList movies={this.state.results} found={this.state.found} /> </div> ); }
Export
Lastly in our App component, we need to export the component so our index.js file can import it.
export default App;
AddMovie component
Our AddMovie component only has one prop - the addHandler, which it runs onSubmit. We’ll create a quick form and include the prop - note it’s a function definition and not an invocation. Once we’re done with the component, we export it so it can be imported by our App component.
var AddMovie = (props) => (
<form id="add-form" className="add-form" onSubmit={props.addHandler}>
<input type="text" id="add-title" placeholder="Add movie title here" />
<input type="submit" value="Add" />
</form>
);
export default AddMovie;
MovieList component
Our MovieList component needs to import the MovieEntry component, since it will display an array of MovieEntry components. It will also pass a movie object to each MovieEntry as a prop.
Be careful about one line statements using multiple lines, like with map()! This is okay to do and the multiple lines can be wrapped in parenthesis, but I’ve screwed up many times not noticing I used curly braces instead, so I wasn’t returning anything.
We also will include some styling logic to show/hide the movie list. Note that style is written as an object, rather than a string of key/value pairs. Attribute names are camelCase rather than snake-case.
import MovieEntry from './MovieEntry.js';
var MovieList = (props) => (
<div>
<div id="movie-list" className="movie-list" style={props.found === false ? {display: "none"} : {}}>
{
props.movies.map(movie => (
<MovieEntry key={movie.title.toString()} movie={movie} />
))
}
</div>
<div id="no-results" style={props.found === true ? {display: "none"} : {}}>
No movie by that name found
</div>
</div>
);
export default MovieList;
MovieEntry component
This component doesn’t need to import anything. It displays the movie title from the movie object prop it was passed from MovieList.
var MovieEntry = (props) => (
<div className="movie-entry" className="movie-entry">{props.movie.title}</div>
);
export default MovieEntry;
Search component
Finally we get to our Search component, which of course, executes a search.
There’s no magic here except to call the submitHandler onSubmit. As usual we export the component.
var Search = (props) => (
<div>
<form onSubmit={props.submitHandler}>
<input type="text" className="search" id="search" placeholder="Search..." />
<input type="submit" className="submit-search" value="Go!" id="submit"/>
</form>
</div>
);
export default Search;
And that’s it! With that done we have an MVP movie list search app.
Future iterations will use redux, APIs, and more!