Working with redux - PaulL48/SOEN341-SC4 GitHub Wiki

Getting Started

Reason for integration

  • Great to manage the state of shared data
  • Easily connect screen/components to access the shared data
  • Reduce the number of calls to the back-end for the same data.

Important If a screen/component doesn't need to access shared data, No need for this process!

Integration guide

Let's say we want to keep track of the user's data (ex. name)

  1. Create a new type of action you want to dispatch. Ex.
   import {createAction} from 'util';
   export const SET_CURRENT_USER = createAction("SET_CURRENT_USER");

Note:createAction is a function that takes in a string and appends "_SUCCESS","_PENDING","_ERROR".

  1. Create a new Action that will be dispatched
   import {getCurrentUser} from 'api';
   import {SET_CURRENT_USER} from 'typeDirectory';
   export const setCurrentUserAction = () =>{
      return dispatch =>{
        dispatch({type: SET_CURRENT_USER.PENDING});
        getCurrentUser().then((res)=>{
           dispatch({type: SET_CURRENT_USER.SUCCESS,res});
        }).catch((err)=>{
          dispatch({type: SET_CURRENT_USER.ERROR,err});
       });
      }
   };

Note : The getCurrentUser function makes a call to the Laravel back end and returns a response with data. The reason for the PENDING,SUCCESS and ERROR is that we can, for example, show a loading icon while the system dispatched a PENDING signal.

  1. Implementing the new action with a Reducer ! (I know great name)

    1. Case 1: An existing Reducer will logically suit my needs!

    Then the steps are simple, since a Reducer is a fancy name for a giant Switch statement.

    Given that you see an existing reducer with code like this :

   ...
        case SIGN_OUT.ERROR:
        return{
            ...state,
            error: action.err
        };
        case SIGN_OUT.PENDING:
        return{
            ...state,
            error: null,
            isSigningOut: true
        };
        case SIGN_OUT.SUCCESS:
        return{
            ...initialState,
        };
   ...
        case SIGN_OUT.ERROR:
        return{
            ...state,
            error: action.err
        };
        case SIGN_OUT.PENDING:
        return{
            ...state,
            error: null,
            isSigningOut: true
        };
        case SIGN_OUT.SUCCESS:
        return{
            ...initialState,
        };
        case SET_CURRENT_USER.ERROR:
        return{
            ...state,
            error: action.err
        };
        case SET_CURRENT_USER.PENDING:
        return{
            ...state,
            error: null,
            isGettingCurrentUser: true
        };
        case SET_CURRENT_USER.SUCCESS:
        return{
           ...state,
            currentUser: action.res
        };
      ...

Note : Don't forget to import SET_CURRENT_USER at the top of the file ! You will also need to add the new keys to the initial state object at the top. In this case, we would add isGettingCurrentUser and initialize it to false. As well as currentUser, but for objects which contain data it is Important that we initialize it with a structure that mimics the data structure from the database. If not, on initial load, screens might throw an error saying an attribute doesn't exist. OK that was a lot ! XD

Case 2 : An existing Reducer will sadly not logically suit my needs! Example: I want to keep track of the current Question clicked! Well keeping track of this doesn't logically go in the auth Reducer. So I need to create a question Reducer!

  1. Create a new file called question.reducer.js in the question directory. The way I prefer stucturing my files, is that in each screen directory that uses Redux, I have a directoryName.type.js,directoryName.action.js,a and directoryName.reducer.js . As well as an index.js, of course , which exports all the functions from each file.

In question.reducer.js, here are the steps:

  1. Create an initialState object like :
   import {SET_CURRENT_QUESTION} from 'typeDirectory';

   const initialState ={
     error: null,
     currenQuestion: data : {
                        id: "",
                        question : "",
                        author: ""
                     },
     isGettingQuestion: false 
   };

Note : Like I mentioned before, I'm assuming the data received from the back-end will resemble this structure. Important to avoid errors in the future. Also, i'm assuming I created a new type SET_CURRENT_QUESTION like shown before.

  1. Create the switch statement! Easy :)
   ...
   isGettingQuestion: false
   };
   
   export const questionReducer = (initialState = state, action) =>{
     switch(action.type){
       case SET_CURRENT_QUESTION.ERROR:
        return{
            ...state,
            error: action.err
        };
        case SET_CURRENT_QUESTION.PENDING:
        return{
            ...state,
            error: null,
            isGettingQuestion: true
        };
        case SET_CURRENT_QUESTION.SUCCESS:
        return{
            ...state,
            currentQuestion: res
        };
       default:
          return state;
     }  
   }

Note : The only reason for initialState = state is that it's good practice.

And we have one final step!

  1. Import your newly created reducer in the root reducer file in the store directory like this :
import {combineReducers} from 'redux';
import {authReducer,questionReducer} from '../screens'; //Add questionReducer here!

export const rootReducer = combineReducers({
    auth: authReducer,
    question:questionReducer //Add questionReducer here! question is the key used to access the data from screens/components

});

Accessing the data from a screen/component!

Ok so this part is supper simple, here are the steps :

  1. Import the connect function from react-redux;
  2. Create two new functions after the imports at the top. These functions are called mapStateToProps and mapDispatchToProps. As well as defining any data you want to access in the first method or any actions you want to dispatch in the second method.
  3. Change how the screen/component is exported, I prefer to append another word like Screen or Component to the class and export it at the bottom of the file as a const variable which passes the data defined in the mapStateToProps and mapDispatchToProps as props to the screen/component.
  4. Access the data through props!

Example code time!

Let's say we have a screen that needs to display the question that was clicked. This is the initial class:

import React,{Component} from 'react';

export class Question extends Component{
      render(){
          return(
           <div>My question</div>
          );
      }
  }

});

Now let's follow the steps!

import React,{Component} from 'react';
import {connect} from 'react-redux'; //Step 1!

const mapStateToProps = state =>({ //Step 2!
    isGettingQuestion: state.question.isGettingQuestion, // the redux store has a state with keys that we defined in the rootReducer. This is where the question key is used! We can easily access the data in the store.
    currentQuestion : state.question.currentQuestion
});
const mapDispatchToProps = dispatch =>({ //In this case, I don't have a function to dispatch
    
});

export class Question extends Component{ //Keep the export here to test the dumb component.
      render(){
          return(
           <div>{this.props.currentQuestion.data.question}</div> //Will display the question and re-renders if the store receives new data.
          );
      }
  }

});
export const QuestionScreen = connect(mapStateToProps,mapDispatchToProps)(Question); //Step 3!

Note : Now QuestionScreen will export the Question screen with access to the store data.

Questions

What is the ...state in the Reducers?

This is used to maintain the immutable structure of the store. We need to initially copy all the data from the previous store, and only modify what changed! It's es6 code i believe.

I have another question!

Github comments please, Ill answer any! Let me know if this information is clear 👍

⚠️ **GitHub.com Fallback** ⚠️