Wojciech Brożonowicz
Wojciech Brożonowicz
4 min read

Categories

Tags

React Redux store example

To use Redux first:

  • npm install @reduxjs/toolkit
  • npm install react-redux

Redux will need:

  • store: this is where We keep state
  • actions: defined actions for manipulation of state
  • reducers: functions to take state and actions and return new state

So let’s create directories:

  • store
  • actions
  • reducers

In store directory create file “store.js”. We will have in our store “tasks” array. If We want to keep in store more objects, just add them to reducer object.

import { configureStore } from '@reduxjs/toolkit';
import { taskReducer } from '../reducers/taskReducer';

// get all reducers into one (combine to root reducer automatically by configureStore())
const reducer = {
  tasks: taskReducer,
}

const store = configureStore({
  reducer
});

export default store;

In actions directory create file taskActions.js:

export const ADD_TASK = 'ADD_TASK';
export const DELETE_TASK = 'DELETE_TASK';
export const EDIT_TASK = 'EDIT_TASK';

export const addTask = ({name, timeStamp, status}) => ({
  type: ADD_TASK,
  payload: {
    name,
    timeStamp,
    id: Math.floor(Math.random() * 1234), // generate id
    status,
  }
});

export const deleteTask = id => ({
  type: DELETE_TASK,
  // for deleting only id is needed
  payload: {
    id,
  }
});

export const editTask = ({name, id, status, timeStamp}) => ({
  type: EDIT_TASK,
  payload: {
    name,
    timeStamp,
    id,
    status,
  }
});

In reducers directory create file “taskReducer.js”:

import {
  ADD_TASK, DELETE_TASK, EDIT_TASK
} from '../actions/taskActions';

// default state: empty array
export const taskReducer = (state = [], action) => {

  switch (action.type) {

    // add new -> get state and add action.payload
    case ADD_TASK:
      return [...state, action.payload];

    case EDIT_TASK:
      return state.map(currentStateElement => {
        // if id is not matched return existing element
        if (currentStateElement.id !== action.payload.id) {
          return currentStateElement;
        }
        // else take props from payload and return element with new props
        const { name, timeStamp, status } = action.payload;
        return ({
          name,
          timeStamp,
          id: currentStateElement.id,
          status,
        });
      });
    // delete task -> iterate in state (tasks) and filter matched id:
    case DELETE_TASK:
      return state.filter(currentStateElement => currentStateElement.id !== action.payload.id);

    // not defined action type -> show warning and retirn existing state
    default:
      console.warn(`Action type not defined: ${action.type}`);
      return state;
  }
}

Now, how to use it:

  • in file index.js add imports and store provider:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import  store  from './store/store';
import App from './App';
import './index.css';

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

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

In our project We will have:

  • App.js: main component
  • TaskList: here We will get all tasks from store
  • TaskForm: here We will have button to add/edit task
  • Task: for rendering task with button to delete tasks from store

So let’s take a look at our components:

  • App.js file:
import React from 'react';
import './App.css';
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';
import { useState, useMemo } from 'react';


function App() {
  const [currentTask, setCurrentTask] = useState({
    name : '',
    timeStamp : 0,
    status :'',
    id: undefined,
});

const handleSetCurrentTask = (task) => {
  setCurrentTask(task) 
}

  return (
    <div className="App">
      Redux example
      <div>
        <TaskList handler={handleSetCurrentTask}/>
        <TaskForm {...currentTask} handler={handleSetCurrentTask}/>
      </div>
    </div>
  );
}

export default App;

In directory “components” files:

  • TaskList.js:
import React from 'react';
import { useSelector } from 'react-redux';

import Task from './Task';

const TaskList = ({handler}) => {

    // get tasks from store
    const tasks = useSelector(store => store.tasks);

    const listOfTasks = tasks.map(task => (
        (<li key={task.id}><Task  {...task} handler={handler}></Task></li>)
    ))

    return (
        <div>
            <ul>
                {listOfTasks}
            </ul>
        </div>
    );
}

export default TaskList;
  • TaskForm.js:
import React from 'react';
import { useDispatch } from 'react-redux';

import { addTask, editTask } from '../actions/taskActions'; // import actions to manipulate state in store

const TaskForm = (
    {
        name,
        timeStamp,
        status,
        id,
        handler
    }
) => {

    const dispatch = useDispatch();

    const resetForm = () => {
        handler({ id: undefined, name: '', timeStamp: 0, status: '' })
    }

    const handleChangeTaskName = e => {
        handler({ id, name: e.target.value, timeStamp, status })
    }

    const handleStatusChange = e => {
        handler({ id, name, timeStamp, status: e.target.value })
    }

    const handleOnSubmit = e => {
        e.preventDefault();

        const taskObject = {
            name,
            status,
            id,
            timeStamp: timeStamp > 0 ? timeStamp : new Date().getTime(), // if editing dont't change timestamp, else new stamp with current time (new task)
        };

        id
            ? dispatch(editTask(taskObject)) // edit tasks in store.tasks
            : dispatch(addTask(taskObject)); // add new task to store.tasks

        resetForm()
    }

    return (<div>
        <form onSubmit={handleOnSubmit}>
            <div>
                <label>
                    Task name:
                    <input
                        onChange={handleChangeTaskName}
                        type="text"
                        value={name}
                    />
                </label>
            </div>
            <div>
                <label>
                    Status:
                    <input
                        onChange={handleStatusChange}
                        type="text"
                        value={status}
                    />
                </label>
            </div>
            <button type="submit">
                {id ? 'Edit task' : 'Add task'}
            </button>
        </form>
    </div>);
}

export default TaskForm;
  • Task.js:
import React from 'react';
import { useDispatch } from 'react-redux';
import { deleteTask } from '../actions/taskActions';


const Task = ({ id, name, timeStamp, status, handler }) => {
    const dispatch = useDispatch();

    const handleDelete = () => {
        dispatch(deleteTask(id))
    }

    return (
        <div>
            Task {id} name = {name} , status =  {status}, timestamp = {timeStamp}
            <button onClick={() => handler({ id, name, timeStamp, status })}>edit</button>
            <button onClick={handleDelete}>delete</button>
        </div>
    );
}

export default Task;

Summary:

  • to get something from store:
    import { useSelector } from 'react-redux';
    // ....
    const tasks = useSelector(store => store.tasks);
    
  • to manipulate state in components (f.e. delete):
    import { useDispatch } from 'react-redux';
    import { deleteTask } from '../actions/taskActions';
    // ...
    const dispatch = useDispatch();
      const handleDelete = () => {
          dispatch(deleteTask(id))
      }
    // ...
     <button onClick={handleDelete}>delete</button>
    // ...
    

That’s all!