Skip to main content

Simple Redux

TST, Hongkong

Setup

npm init -y && tsc --init
npm install --save-dev webpack webpack-cli webpack webpack-dev-server
npm install --save-dev @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint typescript ts-loader
npm install redux @redux-devtools/extension

./tsconfig.json

{
  "include": ["./src"],
  "exclude": ["./node_modules", "./src/bak"],
  "compilerOptions": {
    "target": "es6",
    "jsx": "react",
    "module": "es6",
    "moduleResolution": "node",
    "allowJs": true,
    "sourceMap": true,
    "outDir": "./public/src/",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

./package.json

{
  "name": "react-redux-2024",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "scripts": {
    "tsc": "tsc --watch",
    "dev": "node public/index.js",
    "serve": "webpack serve",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/lodash": "^4.14.202",
    "@types/node": "^20.11.5",
    "@typescript-eslint/eslint-plugin": "^6.19.0",
    "@typescript-eslint/parser": "^6.19.0",
    "eslint": "^8.56.0",
    "ts-loader": "^9.5.1",
    "typescript": "^5.3.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  },
  "dependencies": {
    "lodash": "^4.17.21"
  }
}

./webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.ts',
    devtool: 'inline-source-map',
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
      ],
    },
    resolve: {
      extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'public'),
    },
  devServer: {
    static: {
      directory: path.resolve(__dirname, 'public'),
    },
    hot: true,
    port: 3000
  },
  devtool: "source-map",
  mode: 'development'
};

Basic Redux Todo List

./src/store/tasks/actionTypes.js

export const ADD_TASK = "ADD_TASK"
export const TASK_DONE = "TASK_DONE"
export const REMOVE_TASK = "REMOVE_TASK"

./src/store/tasks/actions.ts

import * as actionTypes from './actionTypes'

export const addTask = (task: string) => {
    return {
        type: actionTypes.ADD_TASK,
        payload: {
        task: task
        }
    }
}

export const taskCompleted = (id: number, completed: boolean) => {
  return {
      type: actionTypes.TASK_DONE,
      payload: {
        id: id,
        completed: completed
      }
  }
}

export const removeTask = (taskid: number) => {
    return {
        type: actionTypes.REMOVE_TASK,
        payload: {
          id: taskid
        }
      }
}

./src/store/tasks/reducer.js

import * as actionTypes from '../actions/actionTypes'

let id = 0

export function taskReducer(state = [], action) {
    switch(action.type) {
        case actionTypes.ADD_TASK:
            return [
                ...state,
                {
                    id: ++id,
                    task: action.payload.task,
                    completed: false
                }
            ]
        case actionTypes.TASK_DONE:
            return state.map(task => task.id === action.payload.id ? {
                ...task, completed: true
            } : task)
        case actionTypes.REMOVE_TASK:
            return state.filter(task => task.id !== action.payload.id)

        default:
            return state
    } 
}

./src/store/configuration .js

import { legacy_createStore as createStore} from 'redux'
import { composeWithDevTools } from '@redux-devtools/extension';
import { taskReducer } from './tasks/reducer'

const composeEnhancers = composeWithDevTools({
    trace: true,
});

const store = createStore(
    taskReducer,
    /* preloadedState, */ composeEnhancers(),
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    // https://github.com/reduxjs/redux-devtools/tree/main/extension#installation
    )

export default store

Now with the store build and the reducer defined you can add the following to:

./src/index.ts

import store from './store/configuration
'
import { addTask, removeTask, taskCompleted } from './store/tasks/actions'

const unsubscribe = store.subscribe(() => {
  console.log("State updated ::", store.getState())
})

store.dispatch(addTask('Hello Task!'))
store.dispatch(taskCompleted(1, true))
store.dispatch(removeTask(1))
unsubscribe()

This will create a new task and store it inside your store, update it's completion state and then remove it. Check your Browser console for the log output:

State updated :: [
  {
    "id": 1,
    "task": "Hello Task!",
    "completed": false
  }
]

State updated :: [
  {
    "id": 1,
    "task": "Hello Task!",
    "completed": true
  }
]

State updated :: []