Now Available React Programming: The Big Nerd Ranch Guide
Front-End ReactBased on our React Essentials course, this book uses hands-on examples to guide you step by step through building a starter app and a complete,...
Editor’s Note: The API used by this blog series is no longer available. As a result, you will not be able to run this application locally, but you can read the code and explanations to see how to build a robust real-world frontend app data layer.
This post is the second part of an 8-part series going in-depth into how to build a robust real-world frontend app data layer. See the previous parts here:
In this post we’ll set up the React/Redux app that will serve as the basis for our data layer enhancements in future posts. We’ll also see some patterns for organizing your components and Redux code that can be helpful for production applications. Along the way, we’ll build out our user interface using the React Materialize library for UI components. React Materialize isn’t necessary for any of the data layer content we’ll be getting into, but it is necessary to make this sample app something that will be pleasant to look at!
If you have a working knowledge of React and Redux then all of this should be familiar to you. If you have uncertainty about anything in this post, we recommend spending some time in the React Docs or Redux Docs so you’ll have a good understanding of the basics of the app.
If you like, you can download the app as of the end of the post.
Create a new app with create-react-app
:
$ create-react-app react-data-layer
Next, let’s set up the ability to do imports using absolute paths within the project. This will make it easier for us to have a deeper folder structure. To do this in a create-react-app
app, just add an .env
file at the root of your project and define the following variable:
NODE_PATH=src/
Let’s also clear out some of the default content that comes with the app, so we can start with a clean slate. Delete the following files:
Then remove the reference to index.css
from index.js
:
import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker';
And replace the contents of src/App.js
with the following:
import React from 'react'; const App = () => ( <div className="App"> Hello, world! </div> ); export default App;
In public/index.html
, change the page title to reflect the fact that our app will be for tracking Video Games:
-<title>React App</title> +<title>Video Games</title>
Next, install Redux, its React bindings, and the devtools extension:
$ yarn add redux react-redux redux-devtools-extension
We will also be using Lodash for some utility functions. We could get away without it, but for a real production app it can make your code a lot simpler and easy to follow. Be sure to use the -es
version so that Webpack can include only the necessary functions in your bundle:
$ yarn add lodash-es
Finally, let’s install a UI component library to give us a nice look and feel. Any one should be fine; we’ll go with React Materialize:
$ yarn add react-materialize@^2.6.0
According to React Materialize’s readme, we need to add a few style and script tags directly to our index.html:
<title>Video Games</title> + <link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + <link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css" rel="stylesheet"> </head> ... To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> + <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script> </body> </html>
If you run yarn start
now, you should see a “Hello World” message and no console errors.
Next, let’s set up our main reducer, to store the list of video games. Under the src
directory, create a store
directory. We’re actually going to subdivide our Redux store into several substores, so create a directory under store
called games
. Finally, create store/games/actions.js
and enter the following:
export const ADD_GAME = 'ADD_GAME'; export const addGame = (title) => { return { type: ADD_GAME, title, }; };
We export both the action name and an action creator function that will make dispatching our action from React easier.
Now let’s create a store/games/reducers.js
and define the reducer:
import { combineReducers } from 'redux'; import { ADD_GAME, } from './actions'; const initialData = [ 'Fallout 3', 'Final Fantasy 7', ]; export function games(state = initialData, action) { switch (action.type) { case ADD_GAME: return [action.title, ...state]; default: return state; } } export default combineReducers({ games, });
For now, since we aren’t yet connected to a web service, we define some initial data so we can see our app working. The web service will affect the structure of the data for our video game records, so we don’t want to think through that right now; instead, we just store the titles of the video games. We assign it to a standalone initialData
variable so it’s easy to identify at a glance. In the reducer itself, when the ADD_GAME
action is dispatched, we prepend the new game’s title onto the list of game titles.
Using combineReducers()
for a single reducer may seem unnecessary, but it’s because we’re going to add additional reducers into this file in the future. And if you think that’s unnecessary, wait until you see us do it again! We want one central reducer that includes our games
reducer, as well as any other future reducer groupings we create. Make a src/store/reducers.js
file and enter the following:
import { combineReducers } from 'redux'; import games from './games/reducers'; export default combineReducers({ games, });
Next, create a src/store/index.js
file and set up the Redux store:
import { createStore } from 'redux'; import { devToolsEnhancer } from 'redux-devtools-extension'; import rootReducer from './reducers'; const store = createStore( rootReducer, devToolsEnhancer(), ); export default store;
We include the Devtools Extension so we’ll be able to inspect the state and actions of our store in the browser.
Next, let’s provide the store to our app. In App.js
add the following:
import React from 'react'; +import { Provider } from 'react-redux'; +import store from 'store'; const App = () => ( - <div className="App"> - Hello, world! - </div> + <Provider store={store}> + <div className="App"> + Hello, world! + </div> + </Provider> );
With this, our basic store is set up. Since it’s not yet hooked up to our app you won’t see any differences in what the app displays, but if you like you can check the state of the store in Redux Dev Tools.
Next, let’s display this data in a component. Under src
, create a components
folder to store all of our components. Under components
, create a GameList
folder. We’ll be splitting the game list into several subcomponents, so we go ahead and group them in a folder.
First, under the GameList
directory, create GameList.js
. This will be a plain React component that isn’t aware of Redux:
import React from 'react'; import { Collection, CollectionItem, } from 'react-materialize'; const GameList = ({ games, }) => { return <div> <Collection header="Video Games"> { games.map((game) => ( <CollectionItem key={game}>{game}</CollectionItem> )) } </Collection> </div>; }; export default GameList;
Where will the games
prop come from? In a separate file we’ll create the Redux container component, which connects to Redux, retrieves the data, and provides it to GameList
. We’ll put this in a new components/GameList/index.js
file. This allows the rest of the app to simply import components/GameList
and get the Redux container component.
import { connect } from 'react-redux'; import { pick } from 'lodash-es'; import GameList from './GameList'; function mapStateToProps(state) { return pick(state.games, [ 'games', ]); } export default connect(mapStateToProps)(GameList);
React Redux’s connect()
function is the normal way to use React Redux. We create a mapStateToProps
function to indicate which state to make available as props. We use Lodash’s pick
function to extract the keys we want from the state.games
object; this will help us keep our mapStateToProps
succinct when we add more keys in the future.
Notice that this file doesn’t have any references to React itself; we just import the GameList
component and wrap it in a call to connect()
.
Now we can import this Redux container component in App.js
. We’ll use React Materialize to add some layout to the app as well, to center it on larger screen sizes:
import store from 'store'; +import { Col, Row } from 'react-materialize'; +import GameList from 'components/GameList'; const App = () => ( <Provider store={store}> - <div className="App"> - Hello, world! - </div> + <Row> + <Col s={12} m={10} l={8} offset="m1 l2"> + <GameList /> + </Col> + </Row> </Provider> );
With this, our app should be working. Run it and see that we can see the video game titles in a simple HTML list.
Next, let’s set up the ability to add a new game from the UI.
All we need for a form to do this is a text field and an “Add” button. Even so, there’s a nontrivial amount of logic. So let’s keep it in a separate AddGameForm
component. Because it’ll only be referenced from within the GameList
, it’s convenient to put it in the components/GameList
folder as components/GameList/AddGameForm.js
:
import React, { useState } from 'react'; import { Button, Col, Input, Row, } from 'react-materialize'; const AddGameForm = ({ onAddGame }) => { const [newGameTitle, setNewGameTitle] = useState(''); const handleChangeText = (event) => { setNewGameTitle(event.target.value); } const handleAddGame = (event) => { event.preventDefault(); onAddGame(newGameTitle); setNewGameTitle(''); } return <form onSubmit={handleAddGame}> <Row> <Input label="New Game Title" value={newGameTitle} onChange={handleChangeText} s={12} m={10} l={11} /> <Col s={12} m={2} l={1}> <Button>Add</Button> </Col> </Row> </form>; } export default AddGameForm;
This is just a normal, simple React form. We make the input a controlled component so we can get access to its data. When the Add button is clicked, we clear out the text field, and send the entered title to an onAddGame
function passed in as a prop.
Note that we’re using a functional component with the useState()
hook. This form works just as well as a class component with setState()
–feel free to write the form using the class approach if you prefer.
Depending on how you’ve used Redux in the past, you may be wondering why we’re storing this data in component state instead of in the Redux store. But just because you’re using Redux, it doesn’t mean all your data needs to be stored in Redux. There is an overhead involved in dispatching Redux actions, so it can be more efficient to store component-local data in the component itself.
It can be helpful to think in terms of two types of data: business data and application data. Data related to the “business” or “domain” or use of the app should be stored in Redux for permanence, but data that is application-specific (like in-progress form content or whether a dropdown is open or closed) can be stored in components. In our app in particular, we will eventually be persisting our Redux store in between loads of the app, so any data we want to keep persistent should go in Redux, and any data we don’t care about persisting (like the state of an in-progress form) can be kept in component state and discarded.
Next, let’s make the addGame
action creator we defined earlier available to our Redux container. Make the following changes in components/GameList/index.js
:
import { connect } from 'react-redux'; import { pick } from 'lodash-es'; +import { + addGame, +} from 'store/games/actions'; import GameList from './GameList'; ... +const mapDispatchToProps = { + addGame, +}; + -export default connect(mapStateToProps)(GameList); +export default connect(mapStateToProps, mapDispatchToProps)(GameList);
Now we can display our AddGameForm
as part of the GameList
and pass it the addGame
action:
Collection, CollectionItem, } from 'react-materialize'; +import AddGameForm from './AddGameForm'; const GameList = ({ games, + addGame, }) => { return <div> + <AddGameForm onAddGame={addGame} /> <Collection header="Video Games"> { games.map((game) => ( <CollectionItem key={game}>{game}</CollectionItem>
Switch back to your browser; the app should have already reloaded, displaying the AddGameForm
. Enter a new game and see it appear in the list.
With that, our basic app functionality is done. We haven’t done anything fancy yet; this is just a straightforward React/Redux app, with a little bit of opinion around folder structure thrown in. In the next post we’ll connect it to a backend, and things will start to get more complex.
Based on our React Essentials course, this book uses hands-on examples to guide you step by step through building a starter app and a complete,...
Svelte is a great front-end Javascript framework that offers a unique approach to the complexity of front-end systems. It claims to differentiate itself from...
Large organizations with multiple software development departments may find themselves supporting multiple web frameworks across the organization. This can make it challenging to keep...