In this project, we'll create a small counter application using React and Redux. We'll also include extra functionality for undo/redo actions.
forkandclonethis repository.cdinto the project root.- Run
npm installto fetch the project dependencies. - Run
npm startto spin up a development server.
In this step, we'll install some new dependencies, create a reducer, and create a Redux store.
- Install
reduxandreact-redux - Create an initial state
src/ducks/counter.js - Write a simple reducer in
src/ducks/counter.js - Create a Redux store in
src/store.js
./src/ducks/counter.js
constinitialState={currentValue: 0};exportdefaultfunctioncounter(state=initialState,action){returnstate;} ./src/store.js
import{createStore}from"redux";importcounterfrom"./ducks/counter";exportdefaultcreateStore(counter);In this step, we'll make our application aware that redux exists and connect the Counter component.
- Open
src/App.js. - Import
Providerfromreact-redux. - Import
storefrom./src/store.js. - Wrap the
Appcomponent in theProvidercomponent.- Add a
storeprop that equals our importedstore.
- Add a
- Open
./src/Counter.js. - Import
connectfromreact-redux. - Connect the
Countercomponent to Redux.- Use a
mapStateToPropsfunction that takes in state.- Return
statefor now.
- Return
- Use a
./src/App.js
importReact,{Component}from"react";import{Provider}from"react-redux";importstorefrom"./store";import"./App.css";importCounterfrom"./Counter";classAppextendsComponent{render(){return(<Providerstore={store}><Counter/></Provider>);}}exportdefaultApp; ./src/Counter.js
importReact,{Component}from"react";import{connect}from"react-redux";classCounterextendsComponent{render(){return(/* lots of jsx */);}}constmapStateToProps=state=>state;exportdefaultconnect(mapStateToProps)(Counter);In this step, we'll set up Redux to actually execute actions. We'll start by creating action types, creating action creators, and implementing increment/decrement logic.
- Open
./src/ducks/counter.js. - Create
INCREMENTandDECREMENTaction types. - Write action creators corresponding to
INCREMENTandDECREMENTaction types.- Each of these action creators should accept an
amountargument.
- Each of these action creators should accept an
- Update the reducer to process these actions into state changes.
INCREMENTshould incrementcurrentValueby the givenamount.DECREMENTshould decrementcurrentValueby the givenamount.
./src/ducks/counter.js
constinitialState={currentValue: 0};constINCREMENT="INCREMENT";constDECREMENT="DECREMENT";exportdefaultfunctioncounter(state=initialState,action){switch(action.type){caseINCREMENT:
return{currentValue: state.currentValue+action.amount};caseDECREMENT:
return{currentValue: state.currentValue-action.amount};default:
returnstate;}}exportfunctionincrement(amount){return{ amount,type: INCREMENT};}exportfunctiondecrement(amount){return{ amount,type: DECREMENT};}In this step, we'll wire up the Counter component so that it can dispatch actions to our reducer.
- Open
./src/Counter.js. - Import the
incrementanddecrementaction creators. - Use
connect'smapDispatchToPropsto place the action creators onCounter's props. - Update the
.counter_buttonbuttons to callincrementordecrementwith the correctamount.
./src/Counter.js
importReact,{Component}from"react";import{connect}from"react-redux";import{decrement,increment}from"./ducks/counter";classCounterextendsComponent{render(){const{ currentValue, decrement, increment }=this.props;return(<divclassName="app"><sectionclassName="counter"><h1className="counter__current-value">{currentValue}</h1><divclassName="counter__button-wrapper"><buttonclassName="counter__button"onClick={()=>increment(1)}>
+1
</button><buttonclassName="counter__button"onClick={()=>increment(5)}>
+5
</button><buttonclassName="counter__button"onClick={()=>decrement(1)}>
-1
</button><buttonclassName="counter__button"onClick={()=>decrement(5)}>
-5
</button><br/><buttonclassName="counter__button"disabled={true}onClick={()=>null}>
Undo
</button><buttonclassName="counter__button"disabled={true}onClick={()=>null}>
Redo
</button></div></section><sectionclassName="state"><pre>{JSON.stringify(this.props,null,2)}</pre></section></div>);}}constmapStateToProps=state=>state;exportdefaultconnect(mapStateToProps,{ decrement, increment })(Counter);In this step, we'll implement undo/redo logic into our reducer.
- Open
./src/ducks/counter.js. - Create
UNDOandREDOaction types. - Write action creators for
UNDOandREDO. - Refactor
initialStateandcounterto handle undo/redo logic.
./src/ducks/counter.js
constinitialState={currentValue: 0,futureValues: [],previousValues: []};constINCREMENT="INCREMENT";constDECREMENT="DECREMENT";constUNDO="UNDO";constREDO="REDO";exportdefaultfunctioncounter(state=initialState,action){switch(action.type){caseINCREMENT:
return{currentValue: state.currentValue+action.amount,futureValues: [],previousValues: [state.currentValue, ...state.previousValues]};caseDECREMENT:
return{currentValue: state.currentValue-action.amount,futureValues: [],previousValues: [state.currentValue, ...state.previousValues]};caseUNDO:
return{currentValue: state.previousValues[0],futureValues: [state.currentValue, ...state.futureValues],previousValues: state.previousValues.slice(1)};caseREDO:
return{currentValue: state.futureValues[0],futureValues: state.futureValues.slice(1),previousValues: [state.currentValue, ...state.previousValues]};default:
returnstate;}}exportfunctionincrement(amount){return{ amount,type: INCREMENT};}exportfunctiondecrement(amount){return{ amount,type: DECREMENT};}exportfunctionundo(){return{type: UNDO};}exportfunctionredo(){return{type: REDO};}In this step, we'll import undo and redo action creators into our Counter.js and hook them up their respective buttons.
- Open
./src/Counter.js. - Import
undoandredoaction creators. - Add
undoandredotomapDispatchToProps. - Destrucuture
undoandredofromprops. - Hook up the
undoandredobuttons to their respective action creators.
./src/Counter.js
importReact,{Component}from"react";import{connect}from"react-redux";import{decrement,increment,redo,undo}from"./ducks/counter";classCounterextendsComponent{render(){const{
currentValue,
decrement,
futureValues,
increment,
previousValues,
redo,
undo
}=this.props;return(<divclassName="app"><sectionclassName="counter"><h1className="counter__current-value">{currentValue}</h1><divclassName="counter__button-wrapper"><buttonclassName="counter__button"onClick={()=>increment(1)}>
+1
</button><buttonclassName="counter__button"onClick={()=>increment(5)}>
+5
</button><buttonclassName="counter__button"onClick={()=>decrement(1)}>
-1
</button><buttonclassName="counter__button"onClick={()=>decrement(5)}>
-5
</button><br/><buttonclassName="counter__button"disabled={previousValues.length===0}onClick={undo}>
Undo
</button><buttonclassName="counter__button"disabled={futureValues.length===0}onClick={redo}>
Redo
</button></div></section><sectionclassName="state"><pre>{JSON.stringify(this.props,null,2)}</pre></section></div>);}}constmapStateToProps=state=>state;exportdefaultconnect(mapStateToProps,{ decrement, increment, redo, undo })(Counter);If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2017. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.





