Home > Back-end >  How can I update Redux state array immutably using Typescript
How can I update Redux state array immutably using Typescript

Time:01-27

I'm trying to learn Typescript by doing something simple like a Todo-List application. The problem is that I cannot update the Redux state array in a slice that I have created.(or more like I don't know how to)

Here is my main component Todolist.tsx :

import React, { FC, ChangeEvent, FormEvent, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { update } from '../todoSlice';
import { RootState } from '../todoStore';

const TodoList: FC = () => {

    // React state array for the todos
    const [todos, setTodos] = useState<string[]>([]);
    // Get the input from this state
    const [input, changeInput] = useState<string>('');

    const dispatch = useDispatch()
    // Selecting the state of the todoSlice component
    const selector = useSelector((state: RootState) => { return state.todoCard })

    const handleChange = (event: ChangeEvent<HTMLInputElement>): void => {
        event.preventDefault();
        changeInput(event.target.value);
    }
    
    const addTodos = (event: FormEvent<HTMLFormElement>): void => {
        event.preventDefault();
        setTodos([...todos, input]);
        dispatch(update(todos));
        console.log("REACT: ", todos);
        console.log("REDUX: ", selector);
    }

    const removeTodos = (index: number): void => {
        let filteredArray = todos.filter((todo, todoIndex) => (
            todoIndex !== index
        ))
        setTodos(filteredArray);
    }

    return (
    <React.Fragment>
    <div className='bg-light d-inline-block text-center mt-4' style={{ padding: '10vh', marginLeft: '4vw' }}>
      <h1 className='mb-4'>Todo List</h1>
      <section className='text-wrap mb-4' style={{ overflowY: 'auto', height: '10rem' }}>
        {
          // Mapping thee todo-list items
          todos.map((todo, index) => (
            <React.Fragment key={index}>
              <div className='d-flex justify-content-between mb-3'>
                <li className='d-block'>{todo}</li>
                <button onClick={() => {removeTodos(index)}}>X</button>
              </div>
            </React.Fragment>
          ))
        }
      </section>
      <form onSubmit={addTodos}>
        <input type='text' name="input" className='p-1 rounded border-success' onChange={handleChange}></input>
        <button type="submit" className='ms-3 btn btn-success mb-1'>Enter</button>
      </form>
    </div>
    </React.Fragment>
    )
}

export default TodoList

and here is my slice component todoSlice.ts :

import { createSlice } from '@reduxjs/toolkit'

export const todoSlice = createSlice({
    name: 'todo',
    initialState: {
        todoCard: [],
    },
    reducers: {
        update: (state, action) => {
            return {
                // Return a copy of the array and SHOULD update the state array here "immutably"
                ...state,
                todoCard: [
                    ...state.todoCard,
                    action.payload
                ]
            }
        }
    }
})

export const { update } = todoSlice.actions

export default todoSlice.reducer

I have tried many different ways from Google but none of their methods seem to work in my problem. Thank you for the responses in advance! :)

EDIT: I forgot to add the errors shown so here it is error image

CodePudding user response:

export const todoSlice = createSlice({
    name: 'todo',
    initialState: {
        todoCard: [],
    },
    reducers: { // <------- "Map Object" Notation
        update: (state, action) => {

By doing this you're using https://redux-toolkit.js.org/api/createReducer with the "Map Object" Notation. This is what the redux-toolkit docs say about it:

While this notation is a bit shorter, it works only in JavaScript, not TypeScript and has less integration with IDEs, so we recommend the "builder callback" notation in most cases.

So I'd recommend to rewrite this with https://redux-toolkit.js.org/api/createReducer#usage-with-the-builder-callback-notation - you also don't need to worry about mutability then.

It also looks like there might be some type annotations missing. If state.todoCard is an array of strings, it should be annotated as such. There are lots of examples in the redux-toolkit docs to get better TS support. Check out https://redux-toolkit.js.org/tutorials/typescript for example. Among other stuff, there is a version of useSelector that already knows how your redux state is typed, this helps a lot.

CodePudding user response:

You need typings, your action is of type PayloadAction<string[]>, try this

reducers: {
    update: (state, action: PayloadAction<string[]>) => { 
      ...

You should give a type to your initialState too

interface InitialState {
  todoCard: string[];  
}       

const init: InitialState  =  {
  todoCard: [],
}

export const todoSlice = createSlice({
  name: 'todo',
  initialState: init,
  • Related