Redux Fundamentals, Part 3: State, Actions, and Reducers

  • Redux apps use plain JS objects, arrays, and primitives as the state values
    • The root state value should be a plain JS object
    • The state should contain the smallest amount of data needed to make the app work
    • Classes, Promises, functions, and other non-plain values should not go in the Redux state
    • Reducers must not create random values like Math.random() or Date.now()
    • It’s okay to have other state values that are not in the Redux store (like local component state) side-by side with Redux
  • Actions are plain objects with a type field that describe what happened
    • The type field should be a readable string, and is usually written as 'feature/eventName'
    • Actions may contain other values, which are typically stored in the action.payload field
    • Actions should have the smallest amount of data needed to describe what happened
  • Reducers are functions that look like (state, action) => newState
    • Reducers must always follow special rules:
      • Only calculate the new state based on the state and action arguments
      • Never mutate the existing state – always return a copy
      • No “side effects” like AJAX calls or async logic
  • Reducers should be split up to make them easier to read
    • Reducers are usually split based on top-level state keys or “slices” of state
    • Reducers are usually written in “slice” files, organized into “feature” folders
    • Reducers can be combined together with the Redux combineReducers function
    • The key names given to combineReducers define the top-level state object keys

References
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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,
}
});
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, } });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
}
});
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 } });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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();
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();
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
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;
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
});
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 });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
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;
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
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;
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
});
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 });
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
# Redux + Plain JS template npx create-react-app my-app --template redux # Redux + TypeScript template npx create-react-app my-app --template redux-typescript
# 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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# If you use npm:
npm install react-redux
# If you use npm: npm install react-redux
# If you use npm:
npm install react-redux

Using React Redux

store.js

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
});
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 });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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();
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();
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
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;
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install redux
npm install redux
npm install redux

redux-demo.js

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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"});
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"});
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