In this article, we will go through the whole process of building simple front-end application using React from the ground up.
- First of all, we will go through setup process, how to setup boiler-plate for your application using all of the necessary libraries (plugins),
- After that, we will start building our app.
Before you continue reading this article, I highly recommend reading JavaScript bases.
Our application will be simple ToDo application (real creative, I know). It will have list of all todos on the home page, form for adding a new todo, actions for marking todo as finished (or unfinished), deleting todo, and some filters for the global list of todos. That is the main plan, if something else comes across in building process, this plan can be extended to support some more features.
Setting up application boiler-plate
For creating application boiler-plate we will use command line tool create-react-app which will generate all necessary project structure for us, along with babel for compiling ES6 syntax, webpack as a development server and few more useful libraries (plugins).
First, we need to install this tool. That can be done through npm
. Open your terminal and type:
npm install -g create-react-app
Position yourself into a folder in which you want to create the application and simply do following:
create-react-app react-todo
cd react-todo
npm start
After installation is finished, and you start application open your browser and go to localhost:3000, you should see something screen similar (or same) as the picture below (background color is changed inside src/index.css
for better distinction between image background and site background, that's why the background is light blue #eaf8ff
).
That is pretty much it. Pretty simple, right? We don't need any additional configuration for our test project, but if you want something customized check documentation for create-react-app
tool, there you should find what you need.
Adding custom libraries
We will use redux in our application, so we have to install it. Redux provides a clean concept for building (not only) react applications. It is based on three principles:
- The state of your whole application is stored in an object tree within a single store. (Single source of truth)
- The only way to change the state is to emit an action, an object describing what happened. (State is read-only)
- To specify how the state tree is transformed by actions, you write pure reducers. (Changes are made with pure functions)
You can find more information about principles in official documentation. More about redux usage in practice will be described later in implementation section, for now, just note that we need that for our application.
Along with redux, we will need react-router, for routing (we are using v4, currently most recent one). And few helper modules (libraries, plugins) like prop-types, immutable etc.
To install all these packages needed for our application, position yourself into application folder and type:
npm install --save redux react-redux react-router react-router-dom immutable prop-types
Redux configuration
Next, we need to configure our application to work with redux. First, we will change src/index.js
. So for now, we will only change what is necessary, we need to add few imports (Provider from react-redux, createStore from redux and our application reducer). In code this would look something like:
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import appReducer from './reducers';
We won't modify other imports for now. If you try to run this now, you will get an error (Failed to compile. Error: ENOENT: no such file or directory .../src/reducers.js
). So we need to create our main reducer file. Let's put it in the root directory (src
). Create new file src/reducers.js
, and for now, it will create empty reducer which we will extend later.
// src/reducers.js
import { combineReducers } from 'redux';
const appReducer = combineReducers({
// here will go real reducers
});
export default appReducer;
Ok, now if we run it, everything goes smooth, but still, we get some warnings in the console:
Line 3: 'Provider' is defined but never used no-unused-vars
Line 4: 'createStore' is defined but never used no-unused-vars
Line 6: 'appReducer' is defined but never used no-unused-vars
Don't panic, it is just a reminder that we have unused variables defined, and we will resolve it in a moment.
Next, we want to create store
object with our application reducer as a state (More about this topic you can find on the link), and to pass that store to Provider
(wrapper) component. That wrapper component will wrap our App
component (just for now). O.K, let's do that.
// creating store with our application reducer as state
let store = createStore(appReducer);
ReactDOM.render(
// wrapping our App component inside Provider
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
As you can see, nothing has changed, application compiles, and runs, no warning are shown, and it doesn't look like anything had been done. But it is done, we now have connected redux into our application, which means that we can subscribe any component to application state (reducer), and pull any data from it. All of this will make more sense later when real example comes along, for now, you have to trust me that this was worth it.
Adding routes (react-router v4)
At this point, we will change base architecture a little. Each new separate part (part can be interpreted as component, but I used word part because it doesn't have to be one component, it can be subtree of components, each using its children or some common components), will have its own folder with Component, actions folder, reducers folder, constants file, tests folder, optionally assets folder and other subcomponents folders. So our new architecture will look something like this:
src
|-- components
| |-- Home
| | |-- actions
| | | |-- ...
| | |-- assets
| | | |-- images
| | | | |-- logo.svg
| | | |-- styles
| | | | |-- Home.css
| | |-- reducers
| | | |-- ...
| | |-- tests
| | | |-- Home.test.js
| | |-- Home.jsx
| | |-- constants.js
| |-- NotFound
| | |-- assets
| | | |-- styles
| | | | |-- not-found.css
| | |-- NotFound.jsx
| |-- Root
| | |-- styles
| | | |-- index.css
| | |-- Root.jsx
| |-- common
| | |-- ...
|-- utils
| |-- ...
|-- index.js
|-- reducers.js
Comparing this to the previous structure you may notice that App
component is extracted into a separate folder and renamed to Home
, with subfolders for each unity. So the test is moved into tests, style into styles, new folders are created for actions and reducers (which will be empty for now), and new file constants.js
(also empty). We will follow this kind of architecture through the whole example. Also, Home.js (App.js)
, renamed as Home.jsx
, that is just convection we follow, you are not obligated to do that, but it is recommended, each JavaScript file which contains HTML like tags inside javascript code is marked as "[.jsx]"(https://facebook.github.io/react/docs/jsx-in-depth.html#why-jsx) instead of simple ".js". Also, two new components are created (separated in own directory) Root
and NotFound
.
index.js changes
Let's start at index.js
. We need to change import from App.js
to Root.jsx
. New import will be:
// src/index.js
import Root from './components/Root/Root';
Remove line that includes index.css
(and move css file into src/components/Root/assets/style
).
And also we want to change rendering, not to render App
component, but Root
. So our render will look like this:
// src/index.js
ReactDOM.render(
<Provider store={store}>
<Root />
</Provider>,
document.getElementById('root')
);
Everything else stays same. Next let's take a look at the Root
component.
Root.jsx
// src/components/Root/Root.jsx
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
// custom components
import Home from '../Home/Home';
import NotFound from '../NotFound/NotFound';
// custom styles
import './assets/styles/index.css';
// here would go some application default layout, if it exist
// in our case just simple router
const Root = () => (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="*" component={NotFound} />
</Switch>
</Router>
);
export default Root;
As we can see, our root component contains our routing logic, for now, we just have one route "/" which leads to Home
component, and any other links to NotFound
which we will take a look at next. Also note that this component includes index.css
(it is the same style which was included in index.js
), which means that every subcomponent (child component) will have access to styles defined here inside. So, in that file we would add any additional global CSS. <Switch>
component ensures that only one <Route>
component will be rendered (first match), see docs.
Root component class is written as a dumb component. There is no class
keyword. It is a simple function which returns HTML like content. You should always start writing your components as dumb ones, change them to class only when you find yourself in need of internal component state (should be avoided) or some lifecycle methods.
To conclude Root
component is our main layout which every "page" of our application will have (in our case it doesn't have any specific layout, but it may/should have some common layout which all "pages" share).
NotFound.jsx
This is a simple component which simulates 404 Not found status in our front-end routing.
// src/components/NotFound/NotFound.jsx
import React from 'react';
import './assets/styles/not-found.css';
const NotFound = () => (
<div className="centered-context">
<h1> Page not found </h1>
</div>
);
export default NotFound;
/* src/components/NotFound/assets/styles/not-found.css */
.centered-context {
display: flex;
justify-content: center;
align-items: center;
min-height: 100%;
}
Here we don't need to analyze anything, it's pretty straight forward.
App.js (Home.jsx)
Finally, we need to modify App
component. First, all files are renamed and moved to appropriate locations (check file structure scheme). Everything else stays same, only imports are changed so that they match new file structure. Affected imports are shown in following code snippet.
// src/components/Home/Home.jsx
import logo from './assets/images/logo.svg';
import './assets/styles/home.css';
The results
If we start our application, the base screen will stay same, looks like nothing is changed. New thing added to the application is "Not found" page, which will appear if try to access any URI different than "/". For example, if you try to access localhost:3000/asdf you will get something like this:
Conclusion
That is all for part 1 of Building simple react app. We have started from ground-zero and configured our application, added redux and react-router, and defined custom code organization scheme which will be used through the whole application. In next part, we will start adding real functionality, new actions and reducers, and start building new container and presentational components. Stay tuned for Building simple React app (Part 2).
Originally published at Kolosek blog.
Top comments (2)
Great article for beginner!! I learn how to use route now :)
Glad to hear that :)