Search
⌘K
    to navigateEnterto select Escto close

    Setting up React environment

    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.

    The App.jsx component will act as the entry point for all our rendering.

    Let's also add loading state into this component so that in upcoming sections we can set the axios headers synchronously.

    Paste the follow contents into App.jsx:

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

    As starters, we will use this file to set out Axios headers.

    Rails by default adds CSRF counter measures for all requests.

    Thus even for AJAX calls, we will have to provide a CSRF token, so that Rails will understand that the request is trust worthy.

    In the upcoming chapters, when we reach authentication part, we need a scalable mechanism by which we can send the authentication token to our backend server with each request.

    For all these cases, setting the Axios headers is the solution.

    Paste the following to axios.js:

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

    setAuthHeaders is a function invoked from App.jsx.

    The CSRF token will be set in the X-CSRF-TOKEN header.

    It then sets the page to loading and the function sets default headers from X-Auth-Email and X-Auth-Token.

    The email and token will come into action once we reach the chapter dealing with authentication.

    These tokens will then be sent with every request to the backend server.

    The setLoading function is used to make sure that our App.jsx component is only rendered once these headers are set.

    We usually show a page loader while waiting for this process to be completed.

    Once the headers have been set, setLoading(false) removes the page loader and renders the desired component.

    Now we need to invoke this setAuthHeaders method from our entry point, which is App.jsx.

    Add the following code to App.jsx:

    1import React, { useEffect, useState } from "react";
    2import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
    3import { setAuthHeaders } from "apis/axios";
    4// previous code if any
    5
    6const App = () => {
    7  const [loading, setLoading] = useState(true);
    8  // previous code if any
    9
    10  useEffect(() => {
    11    setAuthHeaders(setLoading);
    12  }, []);
    13
    14  if (loading) {
    15    return <h1>Loading...</h1>;
    16  }
    17
    18  // previous code without changes
    19};

    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.

    The following line is how we have added a rule inside of our ESlint config that marks all console statements as errors:

    1{
    2  "no-console": "error"
    3}

    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      "js-logger",
    3      'babel-plugin-macros',
    4      '@babel/plugin-syntax-dynamic-import',
    5      isTestEnv && 'babel-plugin-dynamic-import-node',
    6      '@babel/plugin-transform-destructuring',
    7      ...
    8  ]

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

    1// previous imports
    2import { initializeLogger } from "common/logger";
    3
    4const App = () => {
    5  const [loading, setLoading] = useState(true);
    6
    7  useEffect(() => {
    8    /*eslint no-undef: "off"*/
    9    initializeLogger();
    10    setAuthHeaders(setLoading);
    11    // logger.info("Never use console.log");
    12    // logger.error("Never use console.error");
    13  }, []);
    14
    15  if (loading) {
    16    return <h1>Loading...</h1>;
    17  }
    18
    19  return (
    20    <Router>
    21      <Switch>
    22        <Route exact path="/" render={() => <div>Home</div>} />
    23        <Route exact path="/about" render={() => <div>About</div>} />
    24      </Switch>
    25    </Router>
    26  );
    27};
    28
    29export default App;

    On running your server, you would see that js-logger won't log any of the messages in the browser console even if the logger statements are uncommented. The reason is that we haven't mounted the App.jsx component into our Rails view pipeline yet.

    We will talk more the mounting and how to do that in the chapter for API based architecture.

    Since no routes or components have been set in the Rails pipeline, it will by default show a welcome page that it comes shipped with.

    Currently there is no use case of logging from App.jsx. Thus we suggest to consider the above mentioned logger statements as examples only.

    CSRF token authenticity verification error

    If you ever encounter the ActionController::InvalidAuthenticityToken error, then it means that your Axios requests are not sending the CSRF token while making requests to Rails server.

    Thus the first point to check would be axios.js file and check whether code to set default headers have been added or not. Compare your axios.js file with the code given in the previous section.

    Once that is verified, the next step is to check App.jsx, and verify that setAuthHeaders(setLoading) has been invoked from useEffect.

    Please refer the previous sections in this chapter itself to understand how to set Axios headers.

    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 without having to do relative import.

    To do so, run the following command:

    1touch ./config/webpack/alias.js

    Paste the following into alias.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/environment.js to include the custom configuration we created to resolve aliases:

    1const { environment } = require("@rails/webpacker");
    2
    3const aliasConfig = require("./alias");
    4environment.config.merge(aliasConfig);
    5
    6module.exports = environment;

    Adding this to environment.js makes it available in all environments like development, production etc.

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

    Example:

    1import authAPI from "apis/auth";

    Now let's commit these changes:

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

    References

    Previous
    Next