Why React?
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:
- Reactive updates
- Using components instead of templates
- Extreme efficiency due to its virtual DOM
- Suitability for SEO due to its isomorphic nature
- Good developer tools for exploring virtual DOM
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.
Main Advantages
JSX gives the ability to write your markup in Javascript - Reusability and safety are the main advantages of using JSX to React over using plain HTML. JSX is a statically typed and mostly type-safe programming language. Because of that, many errors that we usually make in HTML will be caught during the compilation process. It also offers to debug features at the compiler level.
Virtual DOM, which enhances performance and efficiency by minimizing expensive updates to the DOM - React utilizes a virtual DOM. This makes it very fast. When the UI changes, React compares the old state to the new state in memory, and then it updates the DOM in the least expensive way. This saves resources and makes React notably faster than many of today's popular alternatives.
Isomorphic Rendering, which allows you to render your components on both client and the server - With the power of virtual DOM, React can even support rendering both on the client and the server. This is critical for isomorphic apps and really makes React different from any other traditional libraries and framework that are very DOM oriented, like Angular or Knockout. Isomorphic rendering can increase perceived loading performance. It avoids repeating code on the client and the server, and it offers a simple path to search engine optimization.
Unidirectional data flows, which make your application less error prone by handling all your data flows in a single direction using a centralized dispatcher - React is merely a view layer, so it doesn't have an opinion on how to handle data flows. We need some way to manage data and state across the whole application. It is almost universally agreed upon that React is best complemented by the Flux application architecture. Today the most popular Flux implementation is Redux. Redux is an implementation of the unidirectional data flow pattern. It avoids the traditional MVC pattern and instead introduces one-way data flows. This makes complex applications easier to predict by avoiding complex interactions that can occur between multiple views and view models in the traditional MVC.
Main Disadvantages
The library is made only for UI and requires other libraries to handle other parts of the application.
There is no standard regarding the app structure, as opposed to frameworks like AngularJS - The developer has to find his own way of managing different parts of the application. This can cause problems in the future if the app structure is ineffective. To avoid these problems, the developer would have to research popular app structures from third parties, reading blogs and analyzing code on GitHub. Since there are rarely detailed guidelines, the developer would need to find his own style and learn from his own experience.
React is mainly written using JSX and ES6, transpiled using Babel and built and packaged using Webpack and npm - The developer would need to learn how to use these tools to be effective with React. It requires significant time to understand the inner workings and processes and if one skips learning those things, it can cause problems in the long run when different errors start to show up.
The process of learning how to think in React can be difficult since it features a different approach to UI design using reusable components - If the developer is used to writing apps with design patterns like MVC, it can be difficult to adapt to this approach.
How to Start with React?
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.
Tools to combine with React
The best choice is to start using the most popular tools in the stack. One well-proved combination is:
Babel
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.Npm scripts
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
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
ESLint is the most popular javascript linter. It forces javascript best practices and has some compile time error checking.
Developing your first application in React
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:
- Installing Node You will need to install Node on your machine if you haven't already. The easiest way is through the installer available here.
Creating the root project folder with package.json
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.
- Creating the entry files for our app 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>
- Setting up the server 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).
- Setting up Webpack 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.
- Setting up Babel 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.
Setting up ESLint
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.Creating npm scripts
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.
Setting up the initial app structure
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.Building container and presentation components
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.
- React router configuration 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>
- Introducing Redux 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:
There is only one global state in a Redux application which represents the state of the whole application. That state can be changed only by dispatching an action.
When an action is dispatched, all reducers are called, but just the one that handles that specific action type should return a new state (a completely new object with some properties modified). Others should return an unmodified application state. Reducers are pure functions. You can read an explanation on why reducers must be pure functions here.
All reducers are combined into a single root reducer, to group together all the properties of the application state.
The store brings together reducers, actions and any optional middleware. Here we will use Thunk middleware to be able to return the function instead of an object from action, so we can have async calls in our actions. Redux store passes down actions to reducers, which receive old state and return a new state. Then Redux store updates its state. Every time the store state changes, it calls a function which triggers the rendering of your component.
The provider is a Redux component that should wrap up the whole application and pass down the store to all children.
After making an introduction to Redux, we can now start with adding Redux to our app.
- The configuration of the Redux store 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.
Wrapping up the application with the Provider component
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>
);
}
}
Adding the API
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.Creation of Redux actions and reducers
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.
- Handling async calls with Thunks 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.
Conclusion & Next Steps
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:
- The ability to use JSX instead of plain HTML;
- Virtual DOM, which enhances performance and efficiency by minimizing expensive updates to DOM;
- The isomorphic rendering which allows you to render your components on both the client and the server;
- Unidirectional data flow and centralized application state.
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.
Original blog post: React - Why and How?
Top comments (0)