DEV Community

Sadhan Sarker
Sadhan Sarker

Posted on • Updated on

Journey for React & Redux, in a TDD Way

πŸ”° What is React?

React is a UI library built by Facebook. React gives us the ability to logically think about our frontend sites and apps.

πŸ”° What is Redux?

Redux is used mostly for application state management. Another way of looking at this - it helps you manage the data you display and how you respond to user actions.

Alt Text

Redux data flow (Image: Tanya Bachuk)

πŸ”° What is TDD?

Test-driven development (TDD), is an evolutionary approach to development which combines test-first development. where you write a test before you write just enough production code to fulfill that test and refactoring.

πŸ”° React Benefits are:

  • Strong community.
  • Can be made quickly.
  • Are easy to understand.
  • Clean & reuse programming.
  • Allow us to logically see the flow of data.
  • Scale well with small and large teams.
  • Transfer knowledge from desktop to mobile apps.

πŸ”° Recommended Knowledge (Prerequisites)

  • Knowledge of HTML & CSS.
  • Knowledge of JavaScript and ES6.
  • Some knowledge about the DOM.
  • Some knowledge about Node & npm.
  • Knowledge of basic Command line.

πŸ”° Knowledge of JavaScript and ES6

We need basic knowledge about ES6. Primarily 5 main syntax updates need to know, that are used heavily in React.

πŸ“— 1.let and const in addition to var:- Use const or let instead of var

var message = 'Hello! world';       // ES5 Expression
let message = 'Hello! world';       // ES6 Expression
const message = 'Hello! world';     // ES6 - const like constant or final
Enter fullscreen mode Exit fullscreen mode

πŸ“— 2.Arrow Functions (=>):- is compact alternative to a regular function expression

// ES5 Expression
function getGreetings() {
  return 'Hello! From JavaScript.';
};

// ES5 Expression
function addNumbers(a, b) {
  return a + b;
}

// ES6 Expression
const getGreetings = () => {
  return 'Hello! From JavaScript.';
};

// ES6 Expression
const addNumbers = (a, b) => a + b;

// Or, ES6 Expression
const addNumbers = (a, b) => {
    return a + b;
};
Enter fullscreen mode Exit fullscreen mode

πŸ“— 3.Classes:-

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

class App extends React.Component {
  render() {
    const text = "Hello! World";
    return (
      <div>Message is: {text}</div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“— 4.Destructuring:-

Object destructuring and array destructuring are very easy ways to simplify our JavaScript code.

Object Destructing

// create an object
const product = {
    name: 'Item 1',
    price: 200
};

// we can access object
let name = product.name;
let price = product.price;

// we can destructuring that object like below
let { name, price } = product;

// Module Import Issue,
import ReactDOM from 'react-dom';           // no destructuring
import { render } from 'react-dom';         // with destructuring
Enter fullscreen mode Exit fullscreen mode

Array Destructuring

// create an array
const product = ['item 1', 'item 2'];

// access without destructuring
let product1 = product[0];
let product2 = product[1];

// access with destructuring
let [p1, p2] = product;
Enter fullscreen mode Exit fullscreen mode

πŸ“— 5.Spread:-

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

// Object spread
const defaults =  {name: 'Product 1', price: 200};
const options = {
  ...defaults,
  visible: true
};
// Output: Object { name: "Product 1", price: 200, visible: true }

//Array spread
const roles = ['admin', 'officer', 'executive'];
const fullRoles = [
  ...roles,
  'super-admin'
]
//Output: Array ["admin", "officer", "executive", "super-admin"]
Enter fullscreen mode Exit fullscreen mode

πŸ”° Create React App

We need to install yarn package manager alternate of npm so we can download packages Ultra Fast.
Based on operating system, Install it form here.

  • 4.Check environments is ready or not.
node --version
v10.15.3

npm --version
6.4.1

yarn --version
1.21.1
Enter fullscreen mode Exit fullscreen mode

Note: Versions might be different, from me. Now we good to go forward

  • 4.Open terminal or command line and follow below commands,
# create new react app using command
npx create-react-app tdd-react-redux

# change directory or open directory using terminal command
cd tdd-react-redux

# run project
npm start
Enter fullscreen mode Exit fullscreen mode
  • 5.Open project folder using VSCode, React offers us
tdd-react-redux
β”œβ”€β”€ public
β”‚   β”œβ”€β”€ favicon.ico
β”‚   β”œβ”€β”€ index.html
β”‚   └── manifest.json
└── src
    β”œβ”€β”€ App.css
    β”œβ”€β”€ App.js
    β”œβ”€β”€ App.test.js
    β”œβ”€β”€ index.css
    β”œβ”€β”€ index.js
    β”œβ”€β”€ logo.svg
    └── serviceWorker.js
β”œβ”€β”€ README.md
β”œβ”€β”€ node_modules
β”œβ”€β”€ package.json
β”œβ”€β”€ .gitignore
Enter fullscreen mode Exit fullscreen mode
  • 6.Now install library, by opening terminal or command line
yarn add react-router-dom
yarn add node-sass
yarn add prop-types
yarn add -D enzyme enzyme-adapter-react-16 jest jest-enzyme
yarn add redux react-redux redux-thunk
yarn add -D husky
Enter fullscreen mode Exit fullscreen mode

πŸ”° Final project structure

tdd-react-redux
└── src
    β”œβ”€β”€ actions
        β”œβ”€β”€ index.js
        β”œβ”€β”€ types.js
    β”œβ”€β”€ assets
        β”œβ”€β”€ logo.png
    β”œβ”€β”€ components
        β”œβ”€β”€ core
            β”œβ”€β”€ button
            β”œβ”€β”€ headline
            β”œβ”€β”€ listitem
            β”œβ”€β”€ Common.js
            β”œβ”€β”€ IconWithList.js
            β”œβ”€β”€ PrintJson.js
            β”œβ”€β”€ style.scss
        β”œβ”€β”€ layouts
        β”œβ”€β”€ About.js
        β”œβ”€β”€ About.test.js
        β”œβ”€β”€ BlogPost.js
        β”œβ”€β”€ BlogPost.test.js
        β”œβ”€β”€ DetailsPage.js
        β”œβ”€β”€ DetailsPage.test.js
        β”œβ”€β”€ style.scss
    β”œβ”€β”€ reducers
        β”œβ”€β”€ posts
            β”œβ”€β”€ post.integration.test.js
            β”œβ”€β”€ reducer.js
        β”œβ”€β”€ index.js
    β”œβ”€β”€ App.scss
    β”œβ”€β”€ App.js
    β”œβ”€β”€ App.test.js
    β”œβ”€β”€ index.scss
    β”œβ”€β”€ index.js
    └── createStore.js
    └── serviceWorker.js
β”œβ”€β”€ utils
    β”œβ”€β”€ index.js
β”œβ”€β”€ .env
β”œβ”€β”€ .env.development
β”œβ”€β”€ .env.production
β”œβ”€β”€ .env.test
β”œβ”€β”€ README.md
β”œβ”€β”€ node_modules
β”œβ”€β”€ package.json
β”œβ”€β”€ .gitignore
Enter fullscreen mode Exit fullscreen mode

Full Source Code

πŸ”° Let's come into the coding part

πŸ“— Config helper Utilities.

1.Create utils/index.js file for test helper

import PropTypes, {checkPropTypes} from 'prop-types';
import { applyMiddleware, createStore } from 'redux';
import rootReducer from './../src/reducers';
import { middlewares } from './../src/createStore';

export const findByTestAttr = (component, attr) => {
    return component.find(`[data-test='${attr}']`);
};

export const checkProps = (component, expectedProps) => {
    return checkPropTypes(component.propTypes, expectedProps, 'props', component.name)
};

export const testStore = (initialState) => {
    const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
    return createStoreWithMiddleware(rootReducer, initialState);
};
Enter fullscreen mode Exit fullscreen mode

3.Create src/createStore.js, to support redux and enable store,

import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import RootReducer from './reducers';

export const middlewares = [ReduxThunk];
export const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
export const store = createStoreWithMiddleware(RootReducer);
Enter fullscreen mode Exit fullscreen mode

4.Update setupTests.js file, for enzyme support,

import Enzyme from 'enzyme';
import EnzymeAdapter from 'enzyme-adapter-react-16';

Enzyme.configure({
    adapter: new EnzymeAdapter(),
    disableLifecycleMethods: true
});
Enter fullscreen mode Exit fullscreen mode

5.To Enable Redux Provider open src/index.js file and update it,

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './createStore';
import App from './App';

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

πŸ“— 6.Now, Config Redux Reducers

Create redux reducer src/reducers/posts/reducer.spec.js test file

import { types } from './../../actions/types';
import postReducer from './reducer'

describe('Posts Reducer', () => {

    it('Should return default state', () => {
        const newState = postReducer(undefined, {});
        expect(newState).toEqual([]);
    });

    it('Should return new state if receiving type', ()=> {
        const posts = [
            { title: 'title 1',description: 'description 1' },
            { title: 'title 2',description: 'description 2' }
        ];
        const newState = postReducer(undefined, {
            type: types.GET_POSTS,
            payload: posts
        });
        expect(newState).toEqual(posts);
    });

});
Enter fullscreen mode Exit fullscreen mode

Create redux reducer src/reducers/posts/reducer.js

import { types } from './../../actions/types';

const postReducer = (state=[], action) => {
    switch (action.type) {
        case types.GET_POSTS:
            return action.payload;

        case types.GET_FORTNITE_POSTS:
            return action.payload;
        default:
            return state;
    }
};
export default postReducer;
Enter fullscreen mode Exit fullscreen mode

Finally, create your src/reducers/index.js and include

import { combineReducers } from 'redux';
import posts from './posts/reducer';

export default combineReducers({
    posts
});
Enter fullscreen mode Exit fullscreen mode

πŸ“— 6.Now Create Redux Actions

Create src/actions/types.js and include

export const types = {
    GET_POSTS: 'getPosts',
    GET_FORTNITE_POSTS: 'getFortnite'
};
Enter fullscreen mode Exit fullscreen mode

Create src/actions/index.js and includes

import { types } from './types';

export const fetchPosts = () => async (dispatch) => {
    try {
        const url = "https://jsonplaceholder.typicode.com";
        const posts = await fetch(`${url}/posts?_limit=10`);
        const res = await posts.json();
        dispatch({
            type: types.GET_POSTS,
            payload: res
        });
    } catch (error) {
        console.error("An error occurred");
        console.error(error);
    }
};

export const fetchFortnitePosts = () => async (dispatch) => {
    try{
        const url = "https://fortnite-api.theapinetwork.com/store/get";
        const result = await fetch(url);
        const res = await result.json();
        dispatch({
            type: types.GET_FORTNITE_POSTS,
            payload: res
        });
    }catch(error) {
        console.error(error);
    }
};
Enter fullscreen mode Exit fullscreen mode

Finally, create src/reducers/posts/post.integration.spec.js integration test,

import { testStore } from './../../../utils';
import { fetchPosts, fetchFortnitePosts } from './../../actions';

describe('fetch api action', () => {

    let store;
    beforeEach(() => {
        store = testStore();
    });

    it('Store is updated correctly', () => {

        return store.dispatch(fetchPosts())
            .then(() => {
                const newState = store.getState();

                //console.log('response', newState.posts[0]);
                //expect(newState.posts[0].title).not(undefined);
                expect(newState.posts[0]).toHaveProperty('title');
                expect(newState.posts[0]).toHaveProperty('body');

            });
    });

    it('Store is update with Fortnite api data correctly', () => {
        const store = testStore();
        return store.dispatch(fetchFortnitePosts())
        .then(() => {
            const newState = store.getState();
            //console.log('output', newState.posts.data);

            expect(newState.posts.data[0]).toHaveProperty('itemId');
            expect(newState.posts.data[0].item).toHaveProperty('name');
            expect(newState.posts.data[0].item.images).toHaveProperty('icon');

        });
    });
});

Enter fullscreen mode Exit fullscreen mode

πŸ”° 7.Setup React Component & Test Cases

πŸ“— 1. Nav Component,

create src/components/layouts/nav/index.spec.js

import React from 'react';
import { shallow } from 'enzyme';
import { findByTestAttr, checkProps } from '../../../../utils'
import Nav from './index';

const setUp =(props = {}) => {
    return shallow(<Nav/>);
};

describe('Index Component', () => {
    let component;
    beforeEach(()=> {
        component = setUp();
    });

    it('Should render without error', () => {
        const wrapper = findByTestAttr(component, 'navComponent');
        expect(wrapper.length).toBe(1)
    });
});

Enter fullscreen mode Exit fullscreen mode

create src/components/layouts/nav/index.js

import React from 'react';
import './style.scss';
import {Link} from "react-router-dom";

const Nav = (props) => {
    return (
        <div data-test="navComponent" className="navComponent">
            <div className="leftMenus">
                <Link to="/">Home</Link>
                <Link to="/about">About</Link>
            </div>
            <div className="rightMenus">
                <img data-test="userLogo" src="https://mesadhan.github.io/assets/profile_pic.jpg" alt="logo"/>
            </div>
        </div>
    )
};

export default Nav;
Enter fullscreen mode Exit fullscreen mode

πŸ“— 2. IconListItem Component

Create src/components/core/IconWithList.spec.js, and include

import React from 'react';
import {shallow} from 'enzyme';
import {findByTestAttr, checkProps} from '../../../utils'
import IconWithList from './IconWithList';

describe('IconWithList Component', () => {

    describe('Checking PropTpes', () => {

        it('Should not throwing warning', () => {
            const expectedProps = {
                name: 'item 1',
                icon: 'icon',
                ratings: {}
            };
            const propsError = checkProps(IconWithList, expectedProps);
            expect(propsError).toBeUndefined();
        });

    });

    describe('Should Renders', () => {
        let component;
        beforeEach(() => {
            const props = {
                name: 'item 1',
                icon: 'icon',
                ratings: {
                    avgStars: 1,
                    totalPoints: 1,
                    numberVotes: 1
                },
            };
            component = shallow(<IconWithList {...props} />);
        });

        it('Should render a ItemList', () => {
            let itemList = findByTestAttr(component, 'IconWithListComponent');
            expect(itemList.length).toBe(1);
        });

        it('Should render a name', () => {
            let title = findByTestAttr(component, 'componentTitle');
            expect(title.length).toBe(1);
        });

        it('Should render a Icon', () => {
            let title = findByTestAttr(component, 'componentIcon');
            expect(title.length).toBe(1);
        });

        it('Should render a Stars', () => {
            let title = findByTestAttr(component, 'componentStars');
            expect(title.length).toBe(1);
        });

        it('Should render a Points', () => {
            let title = findByTestAttr(component, 'componentPoints');
            expect(title.length).toBe(1);
        });

        it('Should render a Votes', () => {
            let title = findByTestAttr(component, 'componentVotes');
            expect(title.length).toBe(1);
        });
    });

    describe('Should Not Renders', () => {
        let component;
        beforeEach(() => {
            component = shallow(<IconWithList/>);
        });

        it('Component is not render', () => {
            let listItem = findByTestAttr(component, 'IconWithListComponent');
            expect(listItem.length).toBe(0);
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Create src/components/core/IconWithList.js, and include

import React, {Component} from 'react'
import PropTypes from 'prop-types';
import './style.scss'

class IconWithList extends Component {

    render() {
        const {icon, name, ratings} = this.props;

        if (!name) return null;

        return (
            <div data-test="IconWithListComponent" className="IconWithList">
                <img data-test="componentIcon" className="itemIcon" src={icon} alt="Icon"/>
                <div className="itemBoxChildren">
                    <h3 className="itemTitle" data-test="componentTitle"> {name}</h3>
                    <p data-test="componentStars" >Avg. Stars:- {ratings.avgStars}</p>
                    <p data-test="componentPoints" >Total Points:- {ratings.totalPoints}</p>
                    <p data-test="componentVotes" >Votes:- {ratings.numberVotes}</p>
                </div>
            </div>
        )
    }
};

IconWithList.propTypes = {
    name: PropTypes.string
};
export default IconWithList;
Enter fullscreen mode Exit fullscreen mode

πŸ“— 3. About Component

Create src/components/About.test.js, and include

import React from 'react';
import { shallow } from 'enzyme';
import { findByTestAttr, checkProps } from '../../utils'
import About from './About';

const setUp =(props = {}) => {
    return shallow(<About/>);
};

describe('About Component', () => {

    let component;
    beforeEach(()=> {
        component = setUp();
    });

    it('Should render without error', () => {
        const wrapper = findByTestAttr(component, 'aboutComponent');
        expect(wrapper.length).toBe(1)
    });
});
Enter fullscreen mode Exit fullscreen mode

Create src/components/About.js, and include

import React, { Component } from 'react';

class About extends Component {
    render() {
        document.title = "About";
        return (
            <div data-test="aboutComponent">
                <h1>Hello From About Page</h1>
            </div>
        );
    }
}
export default About;
Enter fullscreen mode Exit fullscreen mode

πŸ“— 4. DetailsPage Component

Create src/components/DetailsPage.test.js, and include

import React from 'react';
import {shallow} from 'enzyme';
import {findByTestAttr, checkProps} from '../../utils'
import DetailsPage from './DetailsPage';

describe('DetailsPage Component', () => {

    describe('Checking PropTpes', () => {
        it('Should not throwing warning', () => {
            const expectedProps = {
                name: 'item 1',
                icon: 'icon',
                ratings: {}
            };
            const propsError = checkProps(DetailsPage, expectedProps);
            expect(propsError).toBeUndefined();
        });
    });

    describe('Should Renders', () => {
        let component;
        beforeEach(() => {
            const props = {
                location: {
                    state: {
                        singlePost: {
                            name: null,
                            icon: null,
                            ratings: {
                                avgStars: 1,
                                totalPoints: 1,
                                numberVotes: 1
                            }
                        }
                    }
                }
            };
            component = shallow(<DetailsPage {...props}/>);
        });

        it('Component should render', () => {
            let listItem = findByTestAttr(component, 'detailsPageComponent');
            expect(listItem.length).toBe(1);
        });

        it('Component should render name', () => {
            let listItem = findByTestAttr(component, 'componentName');
            expect(listItem.length).toBe(1);
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Create src/components/DetailsPage.js, and include

import React, {Component} from 'react';
import './style.scss'
import PropTypes from "prop-types";
import IconWithList from "./core/IconWithList";

class DetailsPage extends Component {

    constructor(props) {
        super(props);
        document.title = "Details Page";
    }
    render() {
        const {name, icon, ratings} = this.props.location.state.singlePost;
        return (
            <div data-test="detailsPageComponent" className="DetailsPageComponent">
                <h1>Product Details</h1>
                <div className="DetailsBox">
                    <div>
                        <img data-test="componentIcon" className="iconBox" src={icon}/>
                    </div>
                    <div className="itemDetails">
                        <h3 data-test="componentName">{name}</h3>
                        <p>Avg. Stars: {ratings.avgStars}</p>
                        <p>Total Points: {ratings.totalPoints}</p>
                        <p>Votes: {ratings.numberVotes}</p>
                    </div>
                </div>
            </div>
        );
    }
}

DetailsPage.propTypes = {
    name: PropTypes.string
};

export default DetailsPage;

Enter fullscreen mode Exit fullscreen mode

πŸ“— 5. App Component

Create src/App.test.js, and include

import React from 'react';
import { shallow } from 'enzyme';
import { findByTestAttr, checkProps } from './../utils'
import App from './App';

const setUp = (props = {}) => {
    return shallow(<App />);
};

describe('App Component', () => {

    let component;
    beforeEach(() => {
        component = setUp();
    });

    it('Should render without error', () => {
        const wrapper = findByTestAttr(component, 'appComponent');
        expect(wrapper.length).toBe(1)
    });
});
Enter fullscreen mode Exit fullscreen mode

Create src/App.js, and include

import React, { Component } from 'react';
import Nav from './components/layouts/nav';
import Home from './components/Home';
import About from './components/About';
import './app.scss'

import {
  BrowserRouter as Router, Switch, Route,
} from "react-router-dom";

import BlogPost from "./components/BlogPost";
import DetailsPage from "./components/DetailsPage";

class App extends Component {
  render() {
    return (
      <Router basename={process.env.PUBLIC_URL}>
        <div data-test="appComponent" className="App">
          <Nav />
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/item/:id" exact component={DetailsPage} />
            <Route path="/about" exact component={About} />
          </Switch>
        </div>
      </Router>
    );
  }
}
export default App;
Enter fullscreen mode Exit fullscreen mode

πŸ“— 6. Home Component Setup

Create src/components/Home.test.js

import React from 'react';
import { shallow } from 'enzyme';
import { findByTestAttr, testStore } from '../../utils';
import Home from "./Home";

const setUp = (initialState={}) => {
    const store = testStore(initialState);
    const component = shallow(<Home store={store} />).childAt(0).dive();
    //console.log( component.debug() );
    return component;
};

describe('Home Component', () => {

    let component;
    beforeEach(() => {
        const initialState = {
            posts: [
                {title: 'title 1', body: 'Body 1'},
                {title: 'title 2', body: 'Body 2'},
                {title: 'title 3', body: 'Body 3'}
            ]
        };
        component =  setUp(initialState)
    });

    it('Should render without errors', () => {
        let c = findByTestAttr(component, 'homeComponent');
        expect(c.length).toBe(1);
    });
});
Enter fullscreen mode Exit fullscreen mode

Create src/components/Home.js

import React, {Component} from 'react';
import IconWithListfrom from './core/IconWithList'
import {connect} from 'react-redux';
import {fetchFortnitePosts, fetchPosts} from '../actions';

import './style.scss'
import {Link} from "react-router-dom";

class Home extends Component {

  constructor(props) {
    super(props);
    this.loadData = this.loadData.bind(this);
    this.loadData();    // initially load data
  }

  loadData() {
    this.props.fetchFortnitePosts();
  }

  render() {
    const { dumPosts, fortnitePosts } = this.props;
    document.title = "Welcome";
    return (
      <div data-test="homeComponent" className="Home">
        <section className="main">
          {fortnitePosts &&
          <div>
            {fortnitePosts.map((data, index) => {
              const {itemId} = data;
              const configurationListItem = {
                name: data.item.name,
                icon: data.item.images.icon,
                ratings: data.item.ratings
              };
              return (
                  <Link to={{
                        pathname: `/item/${itemId}`,
                        state: {
                          singlePost: configurationListItem
                        }
                      }}
                      style={{ textDecoration: 'none' }} key={index}>
                    <IconWithListfrom {...configurationListItem}/>
                  </Link>
              )
            })}
          </div>
          }
        </section>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    dumPosts: state.posts,
    fortnitePosts: state.posts.data
  }
};

// if we and to override dispatcher method
const mapDispatchToProps = dispatch => ({
  fetchPosts: () => dispatch(fetchPosts()),
  fetchFortnitePosts: () => dispatch(fetchFortnitePosts()),
});

//export default connect(mapStateToProps, { fetchPosts })(Home);
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Enter fullscreen mode Exit fullscreen mode

πŸ”° Environment Variable Setup

Note: The prefix REACT_APP_ is required when creating custom environment variables.

.env, .env.development, .env.test and .env.production
As a default behavior, those files will be served with no configuration. You do not even have to update scripts in package.json

.env.staging
Here is the main focus. To target .env.staging file for the staging build, we need a library to achieve this.

  • 1.Let's install env-cmd. This library will will help us on using/executing a selected environment file. See more detail
// execute command below at the root of project
npm install env-cmd --save
Or,
yarn add env-cmd
Enter fullscreen mode Exit fullscreen mode
  • 2.Add a script in package.json like below.
// package.json
scripts: {
  "start": "react-scripts start", // `NODE_ENV` is equal to `development`.
  "build": "react-scripts build", // `NODE_ENV` is equal to `production`.
  "build:staging": "env-cmd -f .env.staging react-scripts build", // `NODE_ENV` is equal to `production`.
  ...
}
Enter fullscreen mode Exit fullscreen mode
  • 3.Finally, test your build:staging` script.

πŸ”° Husky Configuration

Before push into git, we like to pass our test successfully. We already install dependency now just need to configure it,

Open package.json and update with

"scripts": {
    //... more
  },
  "husky": {
    "hooks": {
      "pre-push": "CI=true npm test"
    }
  }
Enter fullscreen mode Exit fullscreen mode

πŸ”° To run Application

# For run test suite
yarn run test

# Run application
yarn start
Enter fullscreen mode Exit fullscreen mode

πŸ‘Œ Congratulations. It's a long tutorial!. & Thanks for your time & passion.
Feel free to comments, If you have any issues & queries.

πŸ”° References

Top comments (2)

Collapse
 
kimsean profile image
thedevkim

best article that i've been looking for. Thank you so much!

Collapse
 
mesadhan profile image
Sadhan Sarker

Very welcome! If you benefited from it. Very encouraging comments for me.