*Full React Redux with testing Tutorial *
- What is Redux
- What Redux include
- Implement simplest Redux
- Create Redux store with ReactJs
- Adding Actions and Reducers to store
- Redux Devtool
- Combine Reducers
- Providers
- Connects
- Redux thunk
- Testing
- Redux is a predictable state container for JavaScript apps.
- Run in different environments(client, server and native)
- It is tiny (2KB, including dependencies)
- Create React App
npm install create-react-app && create-react-app your_application_name
- Run Project
npm start
- Install Redux
npm install redux
- In your src->index.js
import {createStore} from 'redux';
Next create store
const store = createStore();
An error will be displayed due to the absence of the reducer function. to correct this error you just have to create the reducer function as follows, before create store.
const reducer = () => {}
const store = createStore(reducer)
console.log(store);
- can see
dispatch, subscribe, getState, replaceReducer...
- We're going to look at the getState
console.log(store.getState());
-
undefined message -> because our application does not yet have a state
-
So we create our state this way
const reducer = () => {
return {name:'Patrick'}
}
- run
console.log(store.getState());
-
So we can see our state.
-
Then we move on to our state update.
const reducer = (state, actions) => {
if(actions.type=== 'UPDATE_NAME'){
return {name:actions.payload}
}
return {name:'Patrick'}
}
- add the method dispatch() for update
store.dispatch({type:'UPDATE_NAME', payload:'Isaac'})
console.log(store.getState());
So the three elements to remember are the following:
-
Create reducer
-
Create store
-
Create actions
Next step Combine Reducers
So what does this step consist of? Just manipulating two reducers. And to do that we proceed as follows:
add combineReducers in import elements like this
import {createStore, combineReducers} from 'redux';
Create two reducer like this
- reducer one
const personReducer = (state ={}, {type, payload}) => {
if(type === 'UPDATE_PERSON'){
return {name:payload}
}
return state
}
- reducer two
const gameReducer = (state = {}, {type, payload}) => {
if(type === 'UPDATE_GAME'){
return {name:payload}
}
return state
}
Next we use combineReducers like this
const AllReducers = combineReducers({game:gameReducer, person:personReducer})
Next Initialize state like this
const InitialStates = {
game:{name:'Football'},
person:{name:'Patrick'},
}
Next create store like this
const store = createStore(AllReducers, InitialStates)
We can quickly check what's going on from our console with
console.log(store.getState());
Next we add actions that will allow us to modify our initial state. Like This
store.dispatch({type:'UPDATE_PERSON', payload:'Isaac'})
Then we can observe the changes with a
console.log(store.getState());
So what have we improved?
- The architecture of the redux project
- We have installed react-redux to better handle the Provider & Connect
npm install react-redux --save
Then, as previously announced, the following milestones have just been exported and imported.
-
Create reducer
-
Create store
-
Create actions
But also to have our tool, we went on this page to get
src -> store-> index.js
const store = createStore(
AllReducers,
InitialStates,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
And to operate our store we just did this.
in src -> index.js
import store from './store/index.js';
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
And to operate our state we just did this.
in src -> App.js
import React from 'react';
import './App.css';
import store from './store/index';
import update_person from './store/actions/personActions';
import update_game from './store/actions/gameActions';
import {connect} from 'react-redux';
function updatePerson () {
store.dispatch(update_person);
}
function updateGame () {
store.dispatch(update_game);
}
function App() {
return (
<div className="App">
<h1>Redux Tutorial </h1>
Person Name : {store.getState().person.name}
<button onClick={updatePerson}>update person </button>
<br/><br/>
Game Name : {store.getState().game.name}
<button onClick={updateGame}>update game </button>
</div>
);
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App);
In order to be able to execute asynchronous actions, like a request to an external API we need to use a middleware which is redux-thunk
Install redux-thunk
npm install redux-thunk --save
After this installation we'll need to import
- applyMiddleware & compose from redux like this
src -> store -> index.js
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
Next import thunk from 'redux-thunk' like this
src -> store -> index.js
import thunk from 'redux-thunk';
Next create userReducer like this
import {UPDATE_USER} from '../actions/userActions'
const userReducer = (state ={}, {type, payload}) => {
switch(type) {
case UPDATE_USER:
return payload
default:
return state
}
}
export default userReducer;
Next create userActions like this
export const UPDATE_USER = 'UPDATE_USER'
const fetch_user = (dispatch) => {
fetch('https://reqres.in/api/users')
.then(res => res.json())
.then(res => dispatch({type:UPDATE_USER, payload:res.data}))
}
export default fetch_user;
Next update personReducer like this
import {UPDATE_PERSON} from '../actions/personActions'
const personReducer = (state ={}, {type, payload}) => {
switch(type) {
case UPDATE_PERSON:
return Object.assign({},state,{name:payload})
default:
return state
}
}
export default personReducer;
Next update index.js like this
src -> store > index.js
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import personReducer from './reducers/personReducer';
import gameReducer from './reducers/gameReducer';
import userReducer from './reducers/userReducer';
import thunk from 'redux-thunk';
const AllReducers = combineReducers({game:gameReducer, person:personReducer, users:userReducer})
const InitialStates = {
game:{name:'Football'},
person:{name:'Patrick', email:'reactredux@tutorial.pat'},
users:[]
}
const middleware = [thunk]
const store = createStore(AllReducers, InitialStates, compose(applyMiddleware
(...middleware), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()))
export default store;
Next update App.js like this
src -> App.js
import React from 'react';
import './App.css';
import update_person from './store/actions/personActions';
import update_game from './store/actions/gameActions';
import fetch_user from './store/actions/userActions';
import {connect} from 'react-redux';
function App(props) {
return (
<div className="App">
<h1>Redux Tutorial </h1>
Person Name : {props.person.name}
<button onClick={props.updatePerson}>update person </button>
<br/><br/>
Game Name : {props.game.name}
<button onClick={props.updateGame}>update game </button>
<br/><br/>
Users : <button onClick={props.fetchUsers}>Fetch Users </button>
{
props.users.length === 0 ? <p>No User found</p>:
props.users.map(user => <p key={user.id}>{user.id} - {user.first_name} - {user.email}</p>)
}
</div>
);
}
const mapStateToProps = state => {
return {
game:state.game,
person:state.person,
users:state.users
}
}
const mapDispatchToProps = dispatch => {
return {
updateGame: () => {dispatch(update_game)},
updatePerson: () => {dispatch(update_person)},
fetchUsers: () => {dispatch(fetch_user)}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
So it's great what we're doing here.
- In order to do the testing we have to install jest like this:
npm install jest babel-jest --save-dev
Next step
At the root of the project we create the file
.babelrc and config
{
"presets":["@babel/preset-env"]
}
Next step
In the package.json change the config of the "scripts": like this:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest",
"test:watch": "npm test -- --watch",
"eject": "react-scripts eject"
},
Now we can start with the first test "test actions" like this:
- create src -> store -> actions -> person.test.js and add config like this :
import update_person, {UPDATE_PERSON} from './personActions'
describe('actions',()=>{
it('should return the action for person',()=>{
const expectedAction = {
type: UPDATE_PERSON,
payload:'Isaac'
}
expect(update_person).toEqual(expectedAction)
})
})
To run the test we're just doing
npm run test:watch
- select all test 'option -> a'
And we have this result
Next step Now we can start with the second test "test reducer" like this:
- create src -> store -> reducers -> person-reducer.test.js and add config like this :
import personReducer from './personReducer'
import {UPDATE_PERSON} from '../actions/personActions'
describe('reducer',()=>{
it('should update person name only',()=>{
const initialState = {name:'Patrick', email:'reactredux@tutorial.pat'}
expect(
personReducer(initialState,{
type: UPDATE_PERSON,
payload:'Isaac'
})
)
.toEqual({name:'Isaac', email:'reactredux@tutorial.pat'})
})
})
And we have this result
- Official Redux Website
- Next Course on TypeScript
We'll do a more in-depth tutorial on redux very soon but on our newsletter
- subscribe massively and I'll see you tomorrow for the TypeScript series.