Redux Fundamentals, Part 2: Concepts and Data Flow

  • Redux’s intent can be summarized in three principles
    • Global app state is kept in a single store
    • The store state is read-only to the rest of the app
    • Reducer functions are used to update the state in response to actions
  • Redux uses a “one-way data flow” app structure
    • State describes the condition of the app at a point in time, and UI renders based on that state
    • When something happens in the app:
      • The UI dispatches an action
      • The store runs the reducers, and the state is updated based on what occurred
      • The store notifies the UI that the state has changed
    • The UI re-renders based on the new state

References
https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow

Redux Fundamentals, Part 1: Redux Overview

  • Redux is a library for managing global application state
    • Redux is typically used with the React-Redux library for integrating Redux and React together
    • Redux Toolkit is the recommended way to write Redux logic
  • Redux uses several types of code
    • Actions are plain objects with a type field, and describe “what happened” in the app
    • Reducers are functions that calculate a new state value based on previous state + an action
    • A Redux store runs the root reducer whenever an action is dispatched

References
https://redux.js.org/tutorials/fundamentals/part-1-overview

Working with Multiple Slices in Redux Toolkit

import {configureStore, createSlice} from '@reduxjs/toolkit';

const initialCounterState = {counter: 0, showCounter: true};
const initialAuthState = {userName: "", isLoggedIn: false};

const counterSlice = createSlice({
    name: "counter",
    initialState: initialCounterState,
    reducers: {
        increment(state, action) {
            state.counter += action.payload
        },
        decrement(state, action) {
            state.counter -= action.payload
        },
        toggle(state, action) {
            state.showCounter = !state.showCounter
        }
    }
})

const authSlice = createSlice({
    name: "auth",
    initialState: initialAuthState,
    reducers: {
        login(state, action) {
            state.userName = action.payload.userName;
            state.isLoggedIn = true;
        },
        logout(state, action) {
            state.userName = "";
            state.isLoggedIn = false;
        }
    }
});

export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;

export const store = configureStore({
    reducer: {
        counter: counterSlice.reducer,
        auth: authSlice.reducer,
    }
});

References
https://stackoverflow.com/questions/67577835/same-action-triggering-in-multiple-slices-redux-toolkit

Getting Started with Redux Toolkit by using createSlice in React

createSlice internally uses createAction and createReducer, so you may also use Immer to write “mutating” immutable updates.

store.js

import {configureStore, createSlice} from '@reduxjs/toolkit';

const initialState = {counter: 0, showCounter: true};

const counterSlice = createSlice({
    name: "counter",
    initialState: initialState,
    reducers: {
        increment(state, action) {
            state.counter += action.payload
        },
        decrement(state, action) {
            state.counter -= action.payload
        },
        toggle(state, action) {
            state.showCounter = !state.showCounter
        }
    }
})

export const counterActions = counterSlice.actions;

export const store = configureStore({
    reducer: {
        counter: counterSlice.reducer
    }
});

index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
import reportWebVitals from './reportWebVitals';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

reportWebVitals();

Counter.js

import {useSelector, useDispatch} from 'react-redux';
import {counterActions} from "../app/store";

function Counter() {
    const counter = useSelector(state => state.counter.counter);
    const showCounter = useSelector(state => state.counter.showCounter);
    const dispatch = useDispatch();

    return (
        <div>
            <div>
                {showCounter && <div>Counter : {counter}</div>}
            </div>
            <div>
                <button onClick={() => dispatch(counterActions.increment(1))}>+1</button>
                <button onClick={() => dispatch(counterActions.decrement(1))}>-1</button>
                <button onClick={() => dispatch(counterActions.toggle())}>Toggle</button>
            </div>
        </div>
    );
}

export default Counter;

References
https://redux-toolkit.js.org/api/createslice

Working with Multiple State Properties in React Redux

store.js

import {configureStore} from '@reduxjs/toolkit';

const initialState = {counter: 0, showCounter: true};

const counterReducer = (state =initialState, action) => {

    if (action.type === "increment") {
        return {...state, counter: state.counter + action.payload};
    } else if (action.type === "decrement") {
        return {...state, counter: state.counter + action.payload};
    } else if (action.type === "toggle") {
        return {...state,showCounter: !state.showCounter}
    }

    return state;
}

export const store = configureStore({
    reducer: counterReducer
});

Counter.js

import {useSelector, useDispatch} from 'react-redux';

function Counter() {
    const counter = useSelector(state => state.counter);
    const showCounter = useSelector(state => state.showCounter);

    const dispatch = useDispatch();

    return (
        <div>
            {showCounter && <div>Counter : {counter}</div>}
            <div>
                <button onClick={() => dispatch({type: "increment", payload: 1})}>+1</button>
                <button onClick={() => dispatch({type: "decrement", payload: 1})}>-1</button>
                <button onClick={() => dispatch({type: "toggle"})}>Toggle</button>
            </div>
        </div>
    );
}

export default Counter;

Attaching Payloads to Actions in React Redux

Counter.js

import {useSelector, useDispatch} from 'react-redux';

function Counter() {
    const counter = useSelector(state => state.counter);
    const dispatch = useDispatch();

    return (
        <div>
            <div>Counter : {counter}</div>
            <div>
                <button onClick={() => dispatch({type: "increment", payload: 1})}>+1</button>
                <button onClick={() => dispatch({type: "decrement", payload: 1})}>-1</button>
                <button onClick={() => dispatch({type: "increment", payload: 5})}>+5</button>
                <button onClick={() => dispatch({type: "decrement", payload: 5})}>-5</button>
            </div>
        </div>
    );
}

export default Counter;

store.js

import {configureStore} from '@reduxjs/toolkit';

const counterReducer = (state = {counter: 0}, action) => {

    if (action.type === "increment") {
        return {...state, counter: state.counter + action.payload};
    } else if (action.type === "decrement") {
        return {...state, counter: state.counter + action.payload};
    }

    return state;
}

export const store = configureStore({
    reducer: counterReducer
});

References
https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#actions

Getting Started with React Redux

Installation

The recommended way to start new apps with React and Redux is by using the official Redux+JS template or Redux+TS template for Create React App, which takes advantage of Redux Toolkit and React Redux’s integration with React components.

# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

or an existing react app:

# If you use npm:
npm install react-redux

Using React Redux

store.js

import {configureStore} from '@reduxjs/toolkit';

const counterReducer = (state = {counter: 0}, action) => {

    if (action.type === "increment") {
        return {counter: state.counter + 1};
    } else if (action.type === "decrement") {
        return {counter: state.counter - 1};
    }

    return state;
}

export const store = configureStore({
    reducer: counterReducer
});

index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
import reportWebVitals from './reportWebVitals';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

reportWebVitals();

Counter.js

import {useSelector, useDispatch} from 'react-redux';

function Counter() {
    const counter = useSelector(state => state.counter);
    const dispatch = useDispatch();

    const incrementHandler = () => {
        dispatch({type: "increment"});
    }

    const decrementHandler = () => {
        dispatch({type: "decrement"});
    }

    return (
        <div>
            <div>Counter : {counter}</div>
            <div>
                <button onClick={incrementHandler}>Increment</button>
                <button onClick={decrementHandler}>Decrement</button>
            </div>
        </div>
    );
}

export default Counter;

References
https://react-redux.js.org/introduction/getting-started

Introduction to Redux

This article will describe you a fundamental basics of redux in a simple node.js app

npm install redux

redux-demo.js

const redux = require("redux");

const counterReducer = (state = {counter: 0}, action) => {
    if (action.type === "increment") {
        return {
            counter: state.counter + 1,
        }
    } else if (action.type === "decrement") {
        return {
            counter: state.counter - 1,
        }
    }

    return state;
};

const counterSubscriber = () => {
    const newState = store.getState();
    console.log(newState);
};

const store = redux.createStore(counterReducer);

// in real world this will be subscribed in ui
store.subscribe(counterSubscriber);

// in real world this will be called from ui
store.dispatch({type: "increment"});
store.dispatch({type: "decrement"});
store.dispatch({type: "increment"});

References
https://redux.js.org/tutorials/fundamentals/part-1-overview