;
React is a library for building web components developed by Facebook. React handles a very specific set of concerns, packaging, composing and rendering HTML components. Because of this focus, React is small in size and makes it easy to create complex UI by composing many simple components together.
While there are many libraries with similar purposes and capabilities like Angular, Vue or Ember, React is one more option.
React has certain benefits over its competitors:
React gained much popularity after the announcement that its competitor Angular is not going to build Angular 2 with backwards compatibility. At that time, many people turned to investigate other libraries and found React as a good alternative to Angular. Today many popular websites are using React. Some of those are Facebook, Instagram, Wordpress, Airbnb, IMDb, Netflix, Dropbox, Docker and many others.
In the past, many projects, especially startups, hesitated to use React because they found its "BSD + Patents" license to be too restrictive (you can read a brief explanation on this topic here). After Apache Foundation banned the usage of "BSD + Patents" licensed software in their open source projects, Facebook decided to change React's license to MIT in September 2017. This made React acceptable for use and even more popular in the front-end world.
After you decide to use React and learn some of the main concepts, you are ready to start developing. The first step is to set up the environment and choose between various available tools supporting React.
The best choice is to start using the most popular tools in the stack. One well-proved combination is:
Babel is an ES6 to ES5 transpiler. Although you can use ES5 to work with React, using ES6 is highly recommended. If you want to use the complete set of ES6 functionalities, new globals such as Promise, or new native methods like String.padStart, Babel won't be sufficient. You will need Babel polyfil to complement it. Additionally, if you want to use some javascript experimental features like class properties or decorators, you will need one of Babel presets. Currently, there are five preset stages marked from 0 to 4. You can find a list of experimental features for each stage in Babel online documentation.
React community has largely embraced using npm scripts instead of popular task runners like Gulp/Grunt. Npm scripts are simple and easy to learn. They remove the extra layer of abstraction in the build process, have less dependence on external plugins and are simpler to debug.
Webpack is the most popular module bundler in the React community. It has hot reloading built in. When we combine it with React and Redux hot reloading capabilities, it is really powerful. Webpack won't generate any physical files but will create bundles in memory that it will serve to the browser. The great thing about Webpack is that you can instruct it to handle different types of files, not just javascript. Some example: CSS, SASS, LESS, images, fonts etc. By benefiting from convention over configuration, we don't have to write much code to get a lot of power in our build process.
ESLint is the most popular javascript linter. It forces javascript best practices and has some compile time error checking.
Note: In the next section, you can follow along by cloning my movie app repository from github. The link is here. When developing your first application in React, you will need to go through some common mandatory steps.
Setting up the environment with tools mentioned in the previous section
Setting up the environment often comes with tricky issues, due to a lot of interdependent frameworks/libraries, incompatibilities and breaking changes in various versions. All dependencies that I will use will be the newest version at the moment. I will describe the setup in a few steps:
You will need to install Node on your machine if you haven't already. The easiest way is through the installer available here.
package.json file will list all the dependencies you will need for your first React app. After creating package.json, you will need to run `npm install` to install all the listed dependencies. In the first section, there is a section for dependencies you must have for your core React app. In the second section, there is a section for development dependencies (modules and packages necessary only for development). They are used just to enrich the development environment.
We should create the files index.html and index.js in the root of our project. These files will be the starting point for your app. For now, index.js can stay empty and index.html should have only two lines in the body tag:
<div id="app"></div>
<script src="/bundle.js"></script>
I will use web pack-dev-server and will set it up in srcServer.js file in the tools folder of my app. The main part of this file is the construction of WebpackDevServer which receives our web pack configuration and an object with a few config properties.
var server = new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true
});
By setting hot and historyApiFallback properties to true, we indicate that we want to use web pack hot reload capabilities and HTML5 History API. In the rest of the file, we configure our server to serve index.html as a response to any request and configure it to listen on some random port (in this case 6001).
Webpack is configured through the webpack.config.js file, which should be put in the root of our project.
Here we will define our entry point as index.js. Prior to our app entry point, we added the dev server and the hot reload server to support web pack hot reload capabilities. The output should be defined just to simulate the creation of physical files. In the development environment, web pack bundles our source files in memory and serves it to the browser. In the production environment, it outputs the physical files.
In the plugin section, we added the plugin for hot module replacement. This is required by hot reloader to enforce browser refresh when React components change. Detailed steps for hot reloader setup are provided here. Also, we defined the list of loaders to tell Webpack which types of files we want it to handle for us. In our case, it will handle .js, .css, and a few font types required by Bootstrap.
Babel is configured through the .babelrc file in the root of the project. It will be really short in our case:
{
"presets": [
"react", [
"env",
{
"modules": false
}
]
],
"plugins": [
"react-hot-loader/babel"
]
}
For Babel to be able to transpile React specific syntax, we added the react preset, and the env preset for it to be able to transpile ES6 experimental features. Webpack 2 has built-in support for ES6 modules, so we tell Babel not to transpile ES6 modules. This was a requirement for Webpack hot reloader to work.
ESLint is configured through the .eslintrc file in the root of the project. There we define what will cause errors and warnings when we build our app. The main goal is to enforce the best coding practices in our project.
Here we do a few tasks in parallel. We run our server, which is configured to transpile and bundle our source code, lint our code and watch for lint errors and warnings in case of file changes. To achieve this, we will add a script section to the package.json file.
"scripts": {
"start": "npm-run-all --parallel open:src lint:watch",
"open:src": "node tools/srcServer.js",
"lint": "node_modules/.bin/esw src",
"lint:watch": "npm run lint -- --watch",
"build": "webpack --config webpack.config.js"
}
Now we should be able to run our app with the `npm start` command.
App structure is a matter of personal preference. I prefer to put all my source code in the src folder. There I have a separate folder for the React components, that will have an inner folder for each app feature inside. In the src folder, I will also have folders for API, Redux store, actions and reducers.
React applications will mainly be a set of different reusable components. There are two types of components in React, container and presentation components.
Container components have little or no markup and are used for passing data and actions down to their children. They subscribe to Redux state, so they are stateful. When using Redux, they are typically created using Redux's connect function. So all of the container components are connected to the Redux store. The connect function receives two parameters, props and actions that we want to be exposed on a component. The container component will re-render just when that specific data we passed in connect function changes. You can check out MovieDetailsPage.js as one example of a container component in my app here.
Presentation components are stateless and they are mostly just markup. Presentation components receive function and data they need from their parent container component through props. All they usually need is the render function to define their markup. They are not aware of Redux. Movie.js is one example of a presentation component in my app here.
It is very easy to setup React routing. In our root component App.js, we will nest the Router component from the react-router-dom module, and inside it, we will list all the possible routes in our app. Additionally, we will redirect the empty path to /movies path. So our routes will look like this:
<Router>
<div className="container-fluid">
<Header />
<Switch>
<Route exact path="/movies" component={MoviesPage} />
<Route path="/movies/:id" component={MovieDetailsPage} />
<Route path="/tv-shows" component={TvShowsPage} />
<Redirect from="/" to="/movies" />
</Switch>
</div>
</Router>
Now we introduce Redux to our app. Redux is a predictable state container for JavaScript applications. Redux provides an easy way to centralize the state of the application. It can be used with any view library, not necessarily React. Redux helps you build applications in different environments (client, server or native). They run consistently and are easy to test.
There are some core principles in Redux you should be aware of:
After making an introduction to Redux, we can now start with adding Redux to our app.
In Redux there is only one immutable store. On the other hand, in Flux we have multiple stores, each one with a different area of domain data. Redux store is created using createStore Redux function. We will create a separate file for configuring our store configureStore.js. There we configure the store in the configureStore function this way:
export default function configureStore(initialState) {
return createStore(rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant()));
}
While creating the store, we pass in the root reducer, the initial state of our app and the result of applyMiddleware function. The root reducer (index.js file in reducers folder) is just a combination of all the reducers in our app, so it looks like this:
const rootReducer = combineReducers({
loadMoviesError,
moviesAreLoading,
movies,
loadMovieDetailsError,
movieDetailsAreLoading,
movie
});
Middlewares are optional. ReduxImmutableStateInvariant is used to produce an error if we change our store state directly. By using it, we will lower the chance of unintentional updates, because the Redux store should be updated just by dispatching an action. Thunk is a middleware Redux library for handling async calls.
We pass the created store to the React-Redux Provider component, which we saw in our top-level component App.js. This is how React-Redux connects our app to the Redux store and makes Redux store available to all the app components.
Now our root component App looks like this:
const store = configureStore();
class App extends React.Component {
render() {
return (
<Provider store={store}>
{/* routes definition missing */}
</Provider>
);
}
}
If you don't have the real backend to work with, you can create a mock API to simulate the API async calls. In this example, we have both movieApi and mockMovieApi in the API folder. In API, we need HTTP calls and use the axios library which returns a Promise when doing an HTTP request.
Every action returns an object that must contain a type of property. The rest of the object can be anything serializable.
Example of an action is:
export const loadMoviesSuccess = (movies) => {
return {
type: actions.movie.LOAD_MOVIES_SUCCESS,
movies
};
};
For each action type, we should have at least one corresponding reducer.
Actions are synchronous in Redux and must return an object. To handle async calls in our actions, we have to include one of the middleware libraries for async calls. Two of the most popular ones are Redux Thunk and Redux Saga. Redux Thunk allows us to return a function from the action creator instead of an object. Redux Saga deals with async calls by using ES6 generators. Both have their pros and cons, but Redux Thunk is simpler to learn and to use, so I will use it here.
Here is a Thunk for loading movies:
export const loadMovies = () => {
return (dispatch) => {
dispatch(moviesAreLoading(true));
MovieApi.getPopularMovies()
.then((response) => {
dispatch(moviesAreLoading(false));
return response;
})
.then((movies) => {
dispatch(loadMoviesSuccess(movies));
})
.catch((error) => {
dispatch(loadMoviesError(true));
});
};
};
A Thunk always returns a function that receives a dispatch. After a successful API async call for loading movies, we dispatch an action loadMoviesSuccess. Then we have to create a corresponding reducer for producing a new state after successful movies load. It will simply return whatever was retrieved from our API. Here it is:
export const movies = (state = [], action) => {
switch (action.type) {
case actions.movie.LOAD_MOVIES_SUCCESS:
return action.movies;
default:
return state;
}
};
Now we completed building our first React & Redux application. You can run this app by calling `npm start` command in the terminal.
We learned how to create a simple application to React & Redux. We have a corresponding github repository here, so you can clone and play with it.
We saw that React & Redux have many advantages:
Also bear in mind that the learning curve of React & Redux is steep, and the best way to learn is to experiment by yourself.
The next step would be to learn best practices and code patterns for React & Redux. That way, we can avoid common issues and pitfalls people already invented a solution for. Also, this will help your application development and application growth run smoother.