Redux core - florypaul/ReactJS Wiki

Redux is a third party library - a state management system for cross-component or app-wide state usage. Manage state-data across multiple components and complete app.

When some data changes we use useState to react that some data has changes so update the UI for us.

3 main kinds of state

  1. local state - state which belongs to single component, input data, toggle button to show and hide info. useState() or useReducer() is used

  2. cross-component state- state that affects multiple components - button that opens/closes overlay - useState or useReducer using prop chains or prop drilling

  3. App-wide state - State that affects the entire app eg. user authentication status - requires prop chains/ prop drilling

React Content or Redux help us manage cross-component or App-wide state.

Why Redux?

  • React Context - has few potential disadvantages
    • We may end up having a complex setup in case of large application, deeply nested JSX code or context provider component
    • Performance can be bad- it's not good for high frequency data changes

Core Redux Concept

  1. One central data store for your entire application
  2. Components subscribe to the store using useSelector(). Components never change the data in the store, they only receive changed data
  3. Reducer function - is responsible for changing the store data
  4. Actions - components dispatch/triggers actions which the reducer should perform -
  5. The reducer then makes the changes the data in the store then the subscribing components are notified and UI change happens
  6. we dispatch type and identifier names type : 'identifier'

image

Practical example in Functional components

  • npm install redux react-redux
  • Creating redux store in react app
    • create new folder store
    • create index.js file import {createStore} from 'redux' const counterReducer = (state = { counter: 0 }, action) = > { if(action.type === 'increment'){ return { counter: state.counter + 1 } }

if(action.type === 'decrement'){ return { counter: state.counter - 1 } } return state; }; const store = createStore(counterReducer) // point the reducer function in the store

export default store;

  • Connecting App to the store
  • Providing the store using Provider component In main index.js

import { Provider } from 'react-redux'; import store from './store/index.js';

wrap APP with Provider component and pass our store to a store prop <Provider store={store}> <App/> </Provider>

  • redux data in components
    • we use useSelector to access data in functional components
    • we use connect method in class based components in counter.js

import { useSelector } from 'react-redux';

const counter = useSelector(state=>state.counter)

<div> Counter value {counter} </div>

  • dispatching actions to change data in store
    • In component we use useDispatch hook import { useDispatch } from 'react-redux';

const dispatch = useDispatch();

const incrementHandler = () => { dipatch { type: 'increment' } }

const decrementHandler = () => { dipatch { type: 'decrement' } }

<button onClick={incrementHandler}> Increment </button> <button onClick={decrementHandler}> Decrement </button>

Practical examples in Class based components

  • important connect from react-redux to access state from store

      import classes from './Counter.module.css';
      import React, { Component} from 'react';
      import { connect } from 'react-redux';
    
      class CounterClass extends Component {
          incrementCounterHandler() {
              this.props.increment();
          }
          decrementCounterHandler(){
              this.props.decrement();
            }
         toggleCounterHandler(){
    
          }
       render(){
       return (
          <main className={classes.counter}>
           <h1>Redux Classbased Counter</h1>
      
        <div className={classes.value}>-- { this.props.counter } --</div>
     
      <button onClick={this.incrementCounterHandler.bind(this)}>Increment Counter</button>
      <button onClick={this.decrementCounterHandler.bind(this)}>Decrement Counter</button>
      <button onClick={this.toggleCounterHandler.bind(this)}>Toggle Counter</button>
    </main>
        );
      };
    }
    
    const mapStateToProps = state => {
        return {
              counter: state.counter
     }
    
     }
    
     const mapDispatchToProps = dispatch => {
        return {
            increment : () => dispatch ({type: 'increment'}),
            decrement : () => dispatch ({type: 'decrement'})
        }
     }
    
     export default connect(mapStateToProps, mapDispatchToProps)(CounterClass);
    

Code in store

  import { legacy_createStore as createStore} from 'redux';

 const initialSate = { 
         counter : 0,
          showCounter : true
    }

 const reducerFunction = (state=initialSate, action) => {

switch(action.type){
    case 'increment' : {
      return {
        counter : state.counter + 1,
        showCounter : state.showCounter
      }
    }

    case 'decrement' : {
        return {
            counter : state.counter - 1,
            showCounter : state.showCounter
        }
    }

    case 'toggle' : {
        return {
            counter: state.counter,
            showCounter : !state.showCounter
        }
    }

     default:
        return state 
}    

/*
if(action.type === 'increment') {
    return {
        counter : state.counter + 1,
        showCounter : state.showCounter
    }
}
if(action.type === 'decrement') {
    return {
        counter : state.counter - 1,
        showCounter : state.showCounter
    }
}

if(action.type === 'toggle'){
    return {
        counter: state.counter,
        showCounter : !state.showCounter
    }
}
return state;
*/
}

   const store = createStore(reducerFunction);

   export default store;

Attaching payloads to Actions

  • passing user input or removing element id to the action)

  • Here we want to increase counter by 5 (can be coming from use input)

  • action payload is just and extra property which we add to the objects while dispatching

    const increaseHandler = () => { dispatch({ type: 'increase', amount: 5}) }

  • in our store in the reducer function if(action.type==='increase'){ return{ counter: state.counter + action.amount } }

Challenges using Redux / Why we need to use Redux/toolkit

Potential problems:

  1. Avoid typo's while dispatching an action, the action type '' should be accurate
  2. We may have clashing identifier names, which can be avoided using JavaScript instead of giving action.type idendifier name
    if(action.type==='increase'){
    * we'll assign identifier to a const variable and import this variable in the component
    In the store ->
    export const INCREMENT = 'increment'; if(action.type === INCREMENT) {
    In the component ->
    import { INCREMENT } from '../store/index'; const incrementCounterHandler = () => { dispatch({ type: INCREMENT }) };
    3. We can also splitting the reducer into smaller multiple reducers so that we don't get large super big single file
    4. Best solution is to use Redux toolkit - which makes working with redux more convenient and easier
⚠️ **GitHub.com Fallback** ⚠️