dev-notes/JavaScript/React/Redux.md
Marcello Lamonaca 17bf5fe2de
Add React Notes (#1)
* Start React notes

* useEffect & custom hooks notes

* Add React-Router Notes

* Spread operator cloning improvements

* Clarify notation for object with same key-value pair

* Add Redux notes

* Fix: translation

* Add details to event notes

* Add details to useEffect and class components

* Notes on async operations

* Fix: import <script> tag type

* Fix typos

* Add React Tests Notes

* Redux Tests Notes

* generalize paths

* Fix react testing library import

* Improve Store notes

* add missing ;

* Fix swapped examples for thunk and action tests

* Improve variable names

* Typo fixes
2021-03-29 19:34:47 +02:00

6.6 KiB

Redux

Redux is a pattern and library for managing and updating application state, using events called actions. It serves as a centralized store for state that needs to be used across the entire application, with rules ensuring that the state can only be updated in a predictable fashion.

Actions, Store, Immutability & Reducers

Actions & Action Creators

An Action is a plain JavaScript object that has a type field. An action object can have other fields with additional information about what happened.
By convention, that information is stored in a field called payload.

Action Creators are functions that creates and return an action object.

function actionCreator(data)
{
    return { type: ACTION_TYPE, payload: data };  // action obj
}

Store

The current Redux application state lives in an object called the store.
The store is created by passing in a reducer, and has a method called getState that returns the current state value.

The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object.
The store will run its reducer function and save the new state value inside.

Selectors are functions that know how to extract specific pieces of information from a store state value.

In initialState.js;

export default {
    // initial state here
}

In configStore.js:

// configStore.js
import { createStore, applyMiddleware, compose } from "redux";

export function configStore(initialState) {
    const composeEnhancers =
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // support for redux devtools

    return  createStore(
        rootReducer, 
        initialState, 
        composeEnhancers(applyMiddleware(middleware, ...))
    );
}

// available functions & methods
replaceReducer(newReducer);  // replace an existing reducer, useful for Hot Reload
store.dispatch(action);  // trigger a state change based on an action
store.subscribe(listener);
store.getState();  // retireve current state

Reducers

Reducers are functions that receives the current state and an action, decide how to update the state if necessary, and return the new state.

Reducers must always follow some specific rules:

  • They should only calculate the new state value based on the state and action arguments
  • They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
  • They must not do any asynchronous logic, calculate random values, or cause other "side effects"
import initialState from "path/to/initialState";

function reducer(state = initialState, action) {
    switch(action.type){
        case "ACTION_TYPE":
            return { ...state, prop: value };  // return modified copy of state (using spread operator)
            break;

        default:
            return state;  // return unchanged state (NEEDED)
    }
}

// combining reducers
import { combineReducers } from "redux";

const rootReducer = combineReducers({
    entity: entityReducer
});

NOTE: multiple reducers can be triggered by the same action since each one operates on a different portion of the state.

React-Redux

Container vs Presentational Components

Container Components:

  • Focus on how thing work
  • Aware of Redux
  • Subscribe to Redux State
  • Dispatch Redux actions

Presentaional Components:

  • Focus on how things look
  • Unaware of Redux
  • Read data from props
  • Invoke callbacks on props

Provider Component & Connect

Used at the root component and wraps all the application.

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { configStore } from 'path/to/configStore';
import initialState from "path/to/initialState";
import App from './App';

const store = configStore(initialState);

const rootElement = document.getElementById('root');
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);
// Component.js
import { connect } from 'react-redux';
import { increment, decrement, reset } from './actionCreators';

// const Component = ...

// specifies which state is passed to the component (called on satte change)
const mapStateToProps = (state, ownProps /* optional */) => {
    // structure of the props passsed to the component
    return { propName: state.property };
};

// specifies the action passed to a component (the key is the name that the prop will have )
const mapDispatchToProps = { actionCreator: actionCreator };
// or
function mapDispathToProps(dispatch) {
    return {
        // wrap action creators
        actionCreator: (args) => dispatch(actionCreator(args))
    };
}
// or
function mapDispathToProps(dispatch) {
    return {
        actionCreator: bindActionCreators(actionCreator, dispatch),
        actions: bindActionCreators(allActionCreators, dispatch)
    };
}

// both args are optional
// if mapDispatch is missing the dispatch function is added to the props
export default connect(mapStateToProps, mapDispatchToProps)(Component);

Async Operations with Redux-Thunk

Note: Redux middleware runs after and action and before it's reducer.

Redux-Thunk allows to retrurn functions instead of objects from action creators.
A "thunk" is a function that wraps an expression to delay it's evaluation.

In configStore.js:

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";

function configStore(initialState) {
    const composeEnhancers =
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // support for redux devtools

    return  createStore(
        rootReducer, 
        initialState, 
        composeEnhancers(applyMiddleware(thunk, ...))  // add thunks middleware
    );
}
// usally action on async func success
function actionCreator(arg) {
    return { type: TYPE, data: arg };
}

export function thunk() {
    return function (dispatch) {  // redux-thunk injects dispatch as arg
        return asyncFunction().then((data) => {  // async function returns a promise
                dispatch(actionCreator(data));
            })
            .catch((error) => {
                throw error;
            });
    };
}

// or using async/await
export async function thunk() {
    return function (dispatch) {  // redux-thunk injects dispatch as arg
        try {
            let data = await asyncFunction();
            return dispatch(actionCreator(data));
        } catch(error) {
                throw error;
        }
    }
}