Notes on Redux

Notes on Redux

A state management library for web apps

Redux manages state of the web app in a single big container called Store.

store takes reducer & action as parameter

Reducer

  • reducer is a function that takes state & returns state
const reducer = (state = 5) => {
  return state;
}
const store = Redux.createStore(reducer);

//fetch state from store
const currentState = store.getState();

Action

  • action is an object that represents occurrence of an event

  • action can contain data as property associated/passed with it (optional)

  • action must have a type property that defines type of action

📜 Think of Redux actions as messengers that deliver information about events happening in your app to the Redux store. The store then conducts the business of updating state based on the action that occurred.

Action Creator & Dispatch

  • Before passing action to store, we need to wrap action in actionCreator()

  • actionCreator is simply a function that returns an action

const action = {
  type: 'LOGIN'
}
const actionCreator = (action) => action;
// -------- OR -------- //

const loginActionCreator() {
    return {
        type: 'LOGIN'
    }
}
  • To pass action to store, we need to pass it with dispatch() function
store.dispatch(actionCreator());

More on Reducer

  • Redux need to respond to an action.

  • It is done by a reducer. Reducer in Redux is responsible for state modification in response to an action.

  • reducer takes state & action as arguments and always returns a new state.

  • reducer is not supposed to do anything else. Just take state & action and return a new state.

📜 The reducer is simply a pure function that takes state and action, then returns new state.

  • In redux, state is read-only.

  • Reducer must always make a new state and not tamper with taken state.

📜 Redux does not enforce state immutability. It is coder’s responsibility.

const defaultState = 5; //any data for the app

const reducer = (state = defaultState, action) => {

  if(action.type === 'LOGIN') {
    return {login: true};
  }

  return state;
};
  • Use switch statement to handle how to respond to multiple actions
const authReducer = (state = defaultState, action) => {

  switch (action.type) {
    case 'LOGIN': return {authenticated: true}; // no break because return will take control away
    case 'LOGOUT': return {authenticated: false}; 
    default: return state;
  }

};
  • Assign action types as read-only constants, then reference these constants wherever they are used.
const LOGIN = 'LOGIN'
const LOGOUT = 'LOGOUT'

Subscribe

  • We can listen to actions on stores.

  • We can subscribe() to the store, so whenever any action is dispatched to the store, subscribe gets called.

  • this is to mimic the scenario:

    1. dispatch an action to change state
    2. state change in store by reducer
    3. subscribe to store’s changes
  • so basically, subscriber will take an operation as input & perform that operation

let count = 0;
const subscriberOperation = () => {count+=1};
store.subscribe(subscriberOperation);

Combine Reducers

  • Our app’s state may grow big and complex.

  • But we should not split the states in Redux. Redux says maintain all state in one Redux store object.

  • Therefore, Redux offers reducer composition.

  • make multiple reducer to handle different states in app.

  • combine the reducers with combineReducers() method

  • pass to the store with createStore() method.

  • We combine reducers in a root Reducer.

const rootReducer = Redux.combineReducers({
  count: counterReducer,
  auth: authReducer
});

const store = Redux.createStore(rootReducer);

Action data

  • till now our action had type property

  • we can also pass data along with action

const defaultState = 1; //any data for the app. e.g. think of it as login count into an account.

let loginCount = {};

const loginActionCreator(u_name, u_password) {
    return {
        type: 'LOGIN',
        username: u_name,
        password: u_password
    };
};

const authReducer = (state = defaultState, action) => {
  switch (action.type) {
            case 'LOGIN': return {authenticated: true}; // no break because return will take control away
            case 'LOGOUT': return {authenticated: false}; 
            default: return state;
    }
};

const loginCountReducer = (state = defaultState, action) => {
    if(action.type === 'LOGIN') {
        if(loginCount.find(action.username))
            loginCount[action.username]++;
        else
            loginCount = {...loginCount, ...{action.username: 1}}; //spread operator in action.
                                                                                                    // Spread objects and place content in the object
    }
}

const rootReducer = Redux.combineReducers({
  count: loginCountReducer,
  auth: authReducer
});

const store = Redux.createStore(rootReducer);

store.dispatch(loginActionCreator('user_1', 'encrypted_password'));

Handling Asynchronous Actions/Requests

  • To handling async requests and performing async actions, redux gives Redux Thunk Middleware.

  • just add Redux.applyMiddleware() as additional parameter in the createStrore() method

  • to handle async actions we need to make a separate function

  • this function will itself return a function (call it func2) having the async code

  • func2 will take dispatch as parameter

  • and run the async order of things

  • Redux Thunk takes care of all the rest. It is responsible to handle how the logic is run.

const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }

const handleAsync = () => {
    return function(dispatch) {
    // Dispatch request action here
        store.dispatch(requestingData());
        setTimeout(function() {
            let data = {
                users: ['Jeff', 'William', 'Alice']
            }
            // Dispatch received data action here
            store.dispatch(receivedData(data));  // receiving data in timeout
        }, 2500);
    }
};

const defaultState = {
  fetching: false,
  users: []
};

const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }
    default:
      return state;
  }
};

const store = Redux.createStore(
    asyncDataReducer,
    Redux.applyMiddleware(ReduxThunk.default);
);