🔰 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.
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:- Useconst
orlet
instead ofvar
varmessage='Hello! world';// ES5 Expressionletmessage='Hello! world';// ES6 Expressionconstmessage='Hello! world';// ES6 - const like constant or final
📗 2.Arrow Functions (=>):- is compact alternative to a regular function expression
// ES5 ExpressionfunctiongetGreetings(){return'Hello! From JavaScript.';};// ES5 ExpressionfunctionaddNumbers(a,b){returna+b;}// ES6 ExpressionconstgetGreetings=()=>{return'Hello! From JavaScript.';};// ES6 ExpressionconstaddNumbers=(a,b)=>a+b;// Or, ES6 ExpressionconstaddNumbers=(a,b)=>{returna+b;};
📗 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.
classAppextendsReact.Component{render(){consttext="Hello! World";return(<div>Messageis:{text}</div>);}}
📗 4.Destructuring:-
Object destructuring and array destructuring are very easy ways to simplify our JavaScript code.
Object Destructing
// create an objectconstproduct={name:'Item 1',price:200};// we can access objectletname=product.name;letprice=product.price;// we can destructuring that object like belowlet{name,price}=product;// Module Import Issue,importReactDOMfrom'react-dom';// no destructuringimport{render}from'react-dom';// with destructuring
Array Destructuring
// create an arrayconstproduct=['item 1','item 2'];// access without destructuringletproduct1=product[0];letproduct2=product[1];// access with destructuringlet[p1,p2]=product;
📗 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 spreadconstdefaults={name:'Product 1',price:200};constoptions={...defaults,visible:true};// Output: Object { name: "Product 1", price: 200, visible: true }//Array spreadconstroles=['admin','officer','executive'];constfullRoles=[...roles,'super-admin']//Output: Array ["admin", "officer", "executive", "super-admin"]
🔰 Create React App
- 1.Download & Install NodeJS
- 2.Download & Install VSCode
- 3.Prerequisites Configurations
We need to install
yarn
package manager alternate ofnpm
so we can download packages Ultra Fast.
Based on operating system,Install it form here.
- 4.Check environments is ready or not.
node--versionv10.15.3npm--version6.4.1yarn--version1.21.1
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 commandnpx create-react-app tdd-react-redux# change directory or open directory using terminal commandcdtdd-react-redux# run projectnpm start
- 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
- 6.Now install library, by opening terminal or command line
yarn add react-router-domyarn add node-sassyarn add prop-typesyarn add-D enzyme enzyme-adapter-react-16 jest jest-enzymeyarn add redux react-redux redux-thunkyarn add-D husky
🔰 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
🔰 Let's come into the coding part
📗 Config helper Utilities.
1.Create
utils/index.js
file for test helper
importPropTypes,{checkPropTypes}from'prop-types';import{applyMiddleware,createStore}from'redux';importrootReducerfrom'./../src/reducers';import{middlewares}from'./../src/createStore';exportconstfindByTestAttr=(component,attr)=>{returncomponent.find(`[data-test='${attr}']`);};exportconstcheckProps=(component,expectedProps)=>{returncheckPropTypes(component.propTypes,expectedProps,'props',component.name)};exportconsttestStore=(initialState)=>{constcreateStoreWithMiddleware=applyMiddleware(...middlewares)(createStore);returncreateStoreWithMiddleware(rootReducer,initialState);};
3.Create
src/createStore.js
, to support redux and enable store,
import{createStore,applyMiddleware}from'redux';importReduxThunkfrom'redux-thunk';importRootReducerfrom'./reducers';exportconstmiddlewares=[ReduxThunk];exportconstcreateStoreWithMiddleware=applyMiddleware(...middlewares)(createStore);exportconststore=createStoreWithMiddleware(RootReducer);
4.Update
setupTests.js
file, for enzyme support,
importEnzymefrom'enzyme';importEnzymeAdapterfrom'enzyme-adapter-react-16';Enzyme.configure({adapter:newEnzymeAdapter(),disableLifecycleMethods:true});
5.To Enable Redux Provider open
src/index.js
file and update it,
importReactfrom'react';importReactDOMfrom'react-dom';import{Provider}from'react-redux';import{store}from'./createStore';importAppfrom'./App';ReactDOM.render(<Providerstore={store}><App/></Provider>, document.getElementById('root'));
📗 6.Now, Config Redux Reducers
Create redux reducer
src/reducers/posts/reducer.spec.js
test file
import{types}from'./../../actions/types';importpostReducerfrom'./reducer'describe('Posts Reducer',()=>{it('Should return default state',()=>{constnewState=postReducer(undefined,{});expect(newState).toEqual([]);});it('Should return new state if receiving type',()=>{constposts=[{title:'title 1',description:'description 1'},{title:'title 2',description:'description 2'}];constnewState=postReducer(undefined,{type:types.GET_POSTS,payload:posts});expect(newState).toEqual(posts);});});
Create redux reducer
src/reducers/posts/reducer.js
import{types}from'./../../actions/types';constpostReducer=(state=[],action)=>{switch(action.type){casetypes.GET_POSTS:returnaction.payload;casetypes.GET_FORTNITE_POSTS:returnaction.payload;default:returnstate;}};exportdefaultpostReducer;
Finally, create your
src/reducers/index.js
and include
import{combineReducers}from'redux';importpostsfrom'./posts/reducer';exportdefaultcombineReducers({posts});
📗 6.Now Create Redux Actions
Create
src/actions/types.js
and include
exportconsttypes={GET_POSTS:'getPosts',GET_FORTNITE_POSTS:'getFortnite'};
Create
src/actions/index.js
and includes
import{types}from'./types';exportconstfetchPosts=()=>async(dispatch)=>{try{consturl="https://jsonplaceholder.typicode.com";constposts=awaitfetch(`${url}/posts?_limit=10`);constres=awaitposts.json();dispatch({type:types.GET_POSTS,payload:res});}catch(error){console.error("An error occurred");console.error(error);}};exportconstfetchFortnitePosts=()=>async(dispatch)=>{try{consturl="https://fortnite-api.theapinetwork.com/store/get";constresult=awaitfetch(url);constres=awaitresult.json();dispatch({type:types.GET_FORTNITE_POSTS,payload:res});}catch(error){console.error(error);}};
Finally, create
src/reducers/posts/post.integration.spec.js
integration test,
import{testStore}from'./../../../utils';import{fetchPosts,fetchFortnitePosts}from'./../../actions';describe('fetch api action',()=>{letstore;beforeEach(()=>{store=testStore();});it('Store is updated correctly',()=>{returnstore.dispatch(fetchPosts()).then(()=>{constnewState=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',()=>{conststore=testStore();returnstore.dispatch(fetchFortnitePosts()).then(()=>{constnewState=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');});});});
🔰 7.Setup React Component & Test Cases
📗 1. Nav Component,
create
src/components/layouts/nav/index.spec.js
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,checkProps}from'../../../../utils'importNavfrom'./index';constsetUp=(props={})=>{returnshallow(<Nav/>);};describe('Index Component',()=>{letcomponent;beforeEach(()=>{component=setUp();});it('Should render without error',()=>{constwrapper=findByTestAttr(component,'navComponent');expect(wrapper.length).toBe(1)});});
create
src/components/layouts/nav/index.js
importReactfrom'react';import'./style.scss';import{Link}from"react-router-dom";constNav=(props)=>{return(<divdata-test="navComponent"className="navComponent"><divclassName="leftMenus"><Linkto="/">Home</Link><Linkto="/about">About</Link></div><divclassName="rightMenus"><imgdata-test="userLogo"src="https://mesadhan.github.io/assets/profile_pic.jpg"alt="logo"/></div></div>)};exportdefaultNav;
📗 2. IconListItem Component
Create
src/components/core/IconWithList.spec.js
, and include
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,checkProps}from'../../../utils'importIconWithListfrom'./IconWithList';describe('IconWithList Component',()=>{describe('Checking PropTpes',()=>{it('Should not throwing warning',()=>{constexpectedProps={name:'item 1',icon:'icon',ratings:{}};constpropsError=checkProps(IconWithList,expectedProps);expect(propsError).toBeUndefined();});});describe('Should Renders',()=>{letcomponent;beforeEach(()=>{constprops={name:'item 1',icon:'icon',ratings:{avgStars:1,totalPoints:1,numberVotes:1},};component=shallow(<IconWithList{...props}/>);});it('Should render a ItemList',()=>{letitemList=findByTestAttr(component,'IconWithListComponent');expect(itemList.length).toBe(1);});it('Should render a name',()=>{lettitle=findByTestAttr(component,'componentTitle');expect(title.length).toBe(1);});it('Should render a Icon',()=>{lettitle=findByTestAttr(component,'componentIcon');expect(title.length).toBe(1);});it('Should render a Stars',()=>{lettitle=findByTestAttr(component,'componentStars');expect(title.length).toBe(1);});it('Should render a Points',()=>{lettitle=findByTestAttr(component,'componentPoints');expect(title.length).toBe(1);});it('Should render a Votes',()=>{lettitle=findByTestAttr(component,'componentVotes');expect(title.length).toBe(1);});});describe('Should Not Renders',()=>{letcomponent;beforeEach(()=>{component=shallow(<IconWithList/>);});it('Component is not render',()=>{letlistItem=findByTestAttr(component,'IconWithListComponent');expect(listItem.length).toBe(0);});});});
Create
src/components/core/IconWithList.js
, and include
importReact,{Component}from'react'importPropTypesfrom'prop-types';import'./style.scss'classIconWithListextendsComponent{render(){const{icon,name,ratings}=this.props;if(!name)returnnull;return(<divdata-test="IconWithListComponent"className="IconWithList"><imgdata-test="componentIcon"className="itemIcon"src={icon}alt="Icon"/><divclassName="itemBoxChildren"><h3className="itemTitle"data-test="componentTitle">{name}</h3><pdata-test="componentStars">Avg.Stars:-{ratings.avgStars}</p><pdata-test="componentPoints">TotalPoints:-{ratings.totalPoints}</p><pdata-test="componentVotes">Votes:-{ratings.numberVotes}</p></div></div>)}};IconWithList.propTypes={name:PropTypes.string};exportdefaultIconWithList;
📗 3. About Component
Create
src/components/About.test.js
, and include
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,checkProps}from'../../utils'importAboutfrom'./About';constsetUp=(props={})=>{returnshallow(<About/>);};describe('About Component',()=>{letcomponent;beforeEach(()=>{component=setUp();});it('Should render without error',()=>{constwrapper=findByTestAttr(component,'aboutComponent');expect(wrapper.length).toBe(1)});});
Create
src/components/About.js
, and include
importReact,{Component}from'react';classAboutextendsComponent{render(){document.title="About";return(<divdata-test="aboutComponent"><h1>HelloFromAboutPage</h1></div>);}}exportdefaultAbout;
📗 4. DetailsPage Component
Create
src/components/DetailsPage.test.js
, and include
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,checkProps}from'../../utils'importDetailsPagefrom'./DetailsPage';describe('DetailsPage Component',()=>{describe('Checking PropTpes',()=>{it('Should not throwing warning',()=>{constexpectedProps={name:'item 1',icon:'icon',ratings:{}};constpropsError=checkProps(DetailsPage,expectedProps);expect(propsError).toBeUndefined();});});describe('Should Renders',()=>{letcomponent;beforeEach(()=>{constprops={location:{state:{singlePost:{name:null,icon:null,ratings:{avgStars:1,totalPoints:1,numberVotes:1}}}}};component=shallow(<DetailsPage{...props}/>);});it('Component should render',()=>{letlistItem=findByTestAttr(component,'detailsPageComponent');expect(listItem.length).toBe(1);});it('Component should render name',()=>{letlistItem=findByTestAttr(component,'componentName');expect(listItem.length).toBe(1);});});});
Create
src/components/DetailsPage.js
, and include
importReact,{Component}from'react';import'./style.scss'importPropTypesfrom"prop-types";importIconWithListfrom"./core/IconWithList";classDetailsPageextendsComponent{constructor(props){super(props);document.title="Details Page";}render(){const{name,icon,ratings}=this.props.location.state.singlePost;return(<divdata-test="detailsPageComponent"className="DetailsPageComponent"><h1>ProductDetails</h1><divclassName="DetailsBox"><div><imgdata-test="componentIcon"className="iconBox"src={icon}/></div><divclassName="itemDetails"><h3data-test="componentName">{name}</h3><p>Avg.Stars:{ratings.avgStars}</p><p>TotalPoints:{ratings.totalPoints}</p><p>Votes:{ratings.numberVotes}</p></div></div></div>);}}DetailsPage.propTypes={name:PropTypes.string};exportdefaultDetailsPage;
📗 5. App Component
Create
src/App.test.js
, and include
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,checkProps}from'./../utils'importAppfrom'./App';constsetUp=(props={})=>{returnshallow(<App/>);};describe('App Component',()=>{letcomponent;beforeEach(()=>{component=setUp();});it('Should render without error',()=>{constwrapper=findByTestAttr(component,'appComponent');expect(wrapper.length).toBe(1)});});
Create
src/App.js
, and include
importReact,{Component}from'react';importNavfrom'./components/layouts/nav';importHomefrom'./components/Home';importAboutfrom'./components/About';import'./app.scss'import{BrowserRouterasRouter,Switch,Route,}from"react-router-dom";importBlogPostfrom"./components/BlogPost";importDetailsPagefrom"./components/DetailsPage";classAppextendsComponent{render(){return(<Routerbasename={process.env.PUBLIC_URL}><divdata-test="appComponent"className="App"><Nav/><Switch><Routepath="/"exactcomponent={Home}/><Routepath="/item/:id"exactcomponent={DetailsPage}/><Routepath="/about"exactcomponent={About}/></Switch></div></Router>);}}exportdefaultApp;
📗 6. Home Component Setup
Create
src/components/Home.test.js
importReactfrom'react';import{shallow}from'enzyme';import{findByTestAttr,testStore}from'../../utils';importHomefrom"./Home";constsetUp=(initialState={})=>{conststore=testStore(initialState);constcomponent=shallow(<Homestore={store}/>).childAt(0).dive();//console.log( component.debug() );returncomponent;};describe('Home Component',()=>{letcomponent;beforeEach(()=>{constinitialState={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',()=>{letc=findByTestAttr(component,'homeComponent');expect(c.length).toBe(1);});});
Create
src/components/Home.js
importReact,{Component}from'react';importIconWithListfromfrom'./core/IconWithList'import{connect}from'react-redux';import{fetchFortnitePosts,fetchPosts}from'../actions';import'./style.scss'import{Link}from"react-router-dom";classHomeextendsComponent{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(<divdata-test="homeComponent"className="Home"><sectionclassName="main">{fortnitePosts&&<div>{fortnitePosts.map((data,index)=>{const{itemId}=data;constconfigurationListItem={name:data.item.name,icon:data.item.images.icon,ratings:data.item.ratings};return(<Linkto={{pathname:`/item/${itemId}`,state:{singlePost:configurationListItem}}}style={{textDecoration:'none'}}key={index}><IconWithListfrom{...configurationListItem}/></Link>)})}</div>}</section></div>);}}constmapStateToProps=(state)=>{return{dumPosts:state.posts,fortnitePosts:state.posts.data}};// if we and to override dispatcher methodconstmapDispatchToProps=dispatch=>({fetchPosts:()=>dispatch(fetchPosts()),fetchFortnitePosts:()=>dispatch(fetchFortnitePosts()),});//export default connect(mapStateToProps, { fetchPosts })(Home);exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Home);
🔰 Environment Variable Setup
Note: The prefixREACT_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
// executecommandbelow at the root of projectnpminstallenv-cmd--saveOr,yarn add env-cmd
- 2.Add a script in package.json like below.
// package.jsonscripts:{"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`....}
- 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" } }
🔰 To run Application
# For run test suiteyarn run test# Run applicationyarn start
👌 Congratulations. It's a long tutorial!. & Thanks for your time & passion.
Feel free to comments, If you have any issues & queries.
🔰 References
- https://linuxjourney.com/
- http://www.agiledata.org/essays/tdd.html
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
- https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
- https://www.npmjs.com/package/env-cmd
Top comments(2)

- LocationDhaka, Bangladesh
- WorkFull-Stack Software Engineer
- Joined
Very welcome! If you benefited from it. Very encouraging comments for me.
For further actions, you may consider blocking this person and/orreporting abuse