Learn Ruby on Rails Book

Setting Up React Environment

ESlint

ESLint is a static code analysis tool to quickly find problems. Many problems ESLint finds can be automatically fixed.

Run the following command from the terminal:

1yarn add -D prettier eslint babel-eslint eslint-config-prettier eslint-plugin-json eslint-plugin-react

The above command installs prettier , prettier is a code formatter than can be integrated with linters. To integrate prettier with ESlint, we install eslint-config-prettier

To setup ESlint in the project, create a file named .eslintrc.json in the project root. Paste the contents below to .eslintrc.json

1{
2  "env": {
3    "browser": true,
4    "es6": true,
5    "node": true
6  },
7  "extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
8  "settings": {
9    "react": {
10      "version": "detect"
11    }
12  },
13  "globals": {
14    "Atomics": "readonly",
15    "SharedArrayBuffer": "readonly",
16    "logger": true
17  },
18  "parserOptions": {
19    "ecmaFeatures": {
20      "jsx": true
21    },
22    "ecmaVersion": 2018,
23    "sourceType": "module"
24  },
25  "parser": "babel-eslint",
26  "plugins": ["react"],
27  "rules": {
28    "no-unused-vars": 0,
29    "no-undef": 0,
30    "indent": ["error", 2],
31    "arrow-parens": ["error", "as-needed"],
32    "semi": ["error", "always"],
33    "no-console": "error",
34    "react/prop-types": 0,
35    "import/prefer-default-export": "off"
36  }
37}

Pre-commit Hook

A Pre-commit hook can re-format your files that are marked as “staged” via git add before you commit. Make sure prettier is installed in devDependencies , then run the following command

1npx mrm lint-staged

The above command installs husky and lint-staged and also adds a configuration to pacakge.json . Paste the following contents to package.json

1"husky": {
2    "hooks": {
3      "pre-commit": "lint-staged"
4    }
5  },
6  "lint-staged": {
7    "app/**/*.{html,md,js,jsx,json,yml}": [
8      "prettier --write",
9      "eslint --fix"
10    ]
11  }

We run the prettier --write command before we run eslint --fix , since the eslint rules needs to be given precedence. Running this automatically fixes all the problems with code formatting, certain JavaScript usages etc, and then git add adds those files to staging. These are the three steps that are run onevery "staged" file before committing. In some cases you will be tempted to bypass this pre-commit hook. Please don't do that. Always fix the problems pointed out by the pre-commit hook, fix it and then make it pass.

React Router

React router is used for declarative routing in react. React Router uses a model called "dynamic routing". It means that routing takes place as your app is rendering, and not via a configuration or convention outside of the running app.

To install React Router, run the following command

1yarn add react-router-dom

Then, run the following command

1mkdir -p ./app/javascript/src/
2touch ./app/javascript/src/App.jsx

This creates App.jsx file. Paste the follow contents into App.jsx .

1import React from "react";
2import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
3const App = () => {
4  return (
5    <Router>
6      <Switch>
7        <Route exact path="/" render={() => <div>Home</div>} />
8        <Route exact path="/about" render={() => <div>About</div>} />
9      </Switch>
10    </Router>
11  );
12};
13
14export default App;

Once the components are created and routing is implemented in the following chapters, at http://localhost:3000/, we should be able to see the Home component being rendered and at http://localhost:3000/about, we should be able to see the About component being rendered. This forms the basis for react-router .

Axios

Axios is a promise based HTTP client for the browser and node.js. To install axios , run the following command

1yarn add axios

HTTP requests can be sent easily by using different methods in axios . Example: axios.get('https://httpbin.org/get') will send a GET request to the URL specified. The axios requests are mostly async in nature. Thus at BigBinary we always embed all async blocks within the try-catch blocks to ensure that the code catches any exceptions that might occur as part of the call. This is also a good practice in general which can moreover ensure an easier debugging experience for the developer.

Axios Headers and Defaults

Interceptors are methods which are triggered before the main method. A request interceptor is called before the actual call to the endpoint is made and a response interceptor is called before the promise is completed and the data is received from the callback.

1mkdir -p ./app/javascript/src/apis
2touch ./app/javascript/src/apis/axios.js

This creates a file axios.js inside apis directory. Paste the following to axios.js

1import axios from "axios";
2axios.defaults.headers = {
3    Accept: "application/json",
4    "Content-Type": "application/json",
5};

This sets the default headers for all axios HTTP requests made from the client. Similarly, axios.default.headers can be used to set Auth Tokens in request headers when creating a token-auth based application. Consider the following code snippet

1import axios from "axios";
2
3export const setAuthHeaders = (setLoading = () => null) => {
4    axios.defaults.headers = {
5        Accept: "applicaion/json",
6        "Content-Type": "application/json",
7        "X-CSRF-TOKEN": document
8            .querySelector('[name="csrf-token"]')
9            .getAttribute("content"),
10    };
11    const token = localStorage.getItem("authToken");
12    const email = localStorage.getItem("authEmail");
13    if (token && email) {
14        axios.defaults.headers["X-Auth-Email"] = email;
15        axios.defaults.headers["X-Auth-Token"] = token;
16    }
17    setLoading(false);
18};

setAuthHeaders is a function invoked from App.jsx . It then sets the page to loading and the function sets default headers from X-Auth-Email and X-Auth-Token . These tokens will then be sent with every corresponding request from the client. Once the headers have been set, setLoading(false) removes the page loader and renders the desired component.

JS-Logger

Our ESlint config does not allow us to use console.log inside of our app. You can read more about why we should avoid console.log in general over here. This is how we have added a rule inside of our ESlint config that marks all console statements as errors:

1"no-console": "error"

However, during API calls to server or while communicating with another external service, errors are bound to occur. In such cases, we might want to log the errors that are caught from our try-catch block. One can use js-logger in such cases. js-logger is a lightweight JavaScript Logger that has zero dependencies.

To install js-logger , run the following command

1yarn add js-logger babel-plugin-js-logger

To initialize js-logger , run the following command from root of project:

1mkdir -p ./app/javascript/src/common
2touch ./app/javascript/src/common/logger.js

Inside logger.js , paste the following

1export const initializeLogger = () => {
2    /* eslint no-undef: "off"*/
3    require("js-logger").useDefaults();
4};

Add "js-logger", to plugins key in babel.config.js so as to make logger function work.

1  plugins: [
2
3      "js-logger",
4      'babel-plugin-macros',
5      '@babel/plugin-syntax-dynamic-import',
6      isTestEnv && 'babel-plugin-dynamic-import-node',
7      '@babel/plugin-transform-destructuring',
8      .....
9
10  ]
11

Then, invoke initializeLogger() from App.jsx as follows

1import React, { useEffect } from "react";
2import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
3
4const App = () => {
5  useEffect(() => {
6    /*eslint no-undef: "off"*/
7    initializeLogger();
8    logger.info("Log from js-logger");
9  }, []);
10
11  return (
12    <Router>
13      <Switch>
14        <Route exact path="/" render={() => <div>Home</div>} />
15        <Route exact path="/about" render={() => <div>About</div>} />
16      </Switch>
17    </Router>
18  );
19};
20
21export default App;

On running your server, you would see that js-logger would have successfully logged the message in console.

Directory Structure

Let's move our previously created apis , commons and components folders to a new folder called src within the javascript folder. This is done so as to handle all the javascript/client code base much more elegantly and have more control over it. Once done, we need to change the contexts. To do so, open the file app/javascript/packs/application.js and replace the following line

1require("@rails/ujs").start(); 
2require("@rails/activestorage").start(); 
3require("channels"); 
4import "../stylesheets/application.scss"; 
5var componentRequireContext = require.context("src", true); 
6var ReactRailsUJS = require("react_ujs"); 
7ReactRailsUJS.useContext(componentRequireContext); 
8

The require.context inserted into packs/application.js is used to load components. If you want to load components from a different directory, override it by calling ReactRailsUJS.useContext. If require fails to find your component, ReactRailsUJS falls back to the global namespace

Aliases

Aliases allows us to create aliases to import or require certain modules more easily. Instead of doing

1import authAPI from "../../apis/auth";

We can define an alias for the apis folder so that it can be accessed from any component. To do so, run the following command

1touch ./config/webpack/custom.js

Paste the following to custom.js

1module.exports = {
2    resolve: {
3        alias: {
4            apis: "src/apis",
5            common: "src/common",
6            components: "src/components",
7        },
8    },
9};

Then, add the following lines to config/webpack/development.js to include the custom configuration we created to resolve aliases.

1process.env. NODE_ENV = process.env. NODE_ENV || "development"; 
2
3const environment = require("./environment"); 
4const customConfig = require("./custom"); 
5environment.config.merge(customConfig); 
6module.exports = environment.toWebpackConfig(); 
7

Similarly, add the same lines to config/webpack/production.js and config/webpack/test.js.

Now, you can easily import from apis folder without having to resolve the entire path each time.

1import authAPI from "apis/auth";

Now let's commit these changes:

1git add -A
2git commit -m "Set up react environment"

References: