Redux

When we want to use some data in the whole app, we can pass it by query parmeters or props. But it's not convenient.

Redux is what you need.

install

npm install redux react-redux

Basic

Redux is a state management tool. It's independant to React. It has some concepts.

Store

Store saves all the states in App. Only Reducers can update it.

const store = redux.createStore(rootReducer);

Reducers

It updates the store according to the actions.

Attention: current state is based on the copy of previous state

// store/reducer.js
const rootReducer = (state = initialState, action) => {
    if (action.type === 'ADD_COUNTER') {
        return {
            ...state,
            counter: state.counter + action.value
        };
    }
    return state;
};

Actions

Actions tell the Reducers how to update the Store. It can be dispatched.

const action = {type: 'ADD_COUNTER', value: 10};
store.dispatch({type: 'ADD_COUNTER', value: 10});

subscribe

Once Store is updated, it will tell all the components which subscribe it.

store.subscribe(() => {
    console.log('[Subscription]', store.getState());
});

react-redux

  • <Provider>: inject Redux store into React

    // index.js
    const store = createStore(reducer);
    
    ReactDOM.render(<Provider store={store}><App /></Provider>);
    
  • connect: a HOC function

    • state

      // Counter.js
      
      // pass state in store to component as props
      const mapStateToProps = state => {
          return {
              ctr: state.counter
          }
      }
      
      export default connect(mapStateToProps)(Counter);
      
      // Counter.js
      
      // in the Counter class
      <div>{this.props.ctr}</div>
      
    • dispatch

      // Counter.js
      
      // pass dispatch in store to component as props
      const mapDispatchToProps = dispatch => {
          return {
              onMyClick: () => dispatch({type: 'CLICK'});
          }
      }
      
      export default connect(mapStateToProps, mapDispatchToProps)(Counter);
      
      // Counter.js
      
      // in the Counter class
      <button onClick={this.props.onMyClick}></button>
      

Multi Reducers

use combineReducers to combine multi reducers into one

const rootReducer = combineReducers({
    ctr: counterReducer,
    res: resultReducer,
});

const store = createStore(rootReducer);

To access counterReducer, we use state.ctr

// Counter.js

const mapStateToProps = state => {
    return {
        ctr: state.ctr.counter
    }
}

However, in counterReducer.js, we can't access the state.

update state immutably

Immutable Update Patterns

In Redux, always create a new state based on the old one.

There are two ways:

  • Object.assign

    const newState = Object.assign({}, state);
    newState.counter = state.counter + 1;
    return newState
    
  • spread operator

    return {
        ...state,
        counter: state.counter + 1
    };
    

manipulate the array

function insertItem(array, action) {
  let newArray = array.slice()
  newArray.splice(action.index, 0, action.item)
  return newArray
}

function removeItem(array, action) {
  let newArray = array.slice()
  newArray.splice(action.index, 1)
  return newArray

  // or

  return array.filter((item, index) => index !== action.index)
}

function updateObjectInArray(array, action) {
  return array.map((item, index) => {
    if (index !== action.index) {
      // This isn't the item we care about - keep it as-is
      return item
    }

    // Otherwise, this is the one we want - return an updated value
    return {
      ...item,
      ...action.item
    }
  })
}