0

I'm a beginner learning ts for the first time. Thank you in advance for sharing your knowledge. I am making a todolist. I used react to complete it. But now I am using react and typescript together to complete the code.

It seems to me that 'reducer' is not working properly. How can I operate this? I'd appreciate it if you let me know. This is 'App.tsx' code with surface error.

import React, { useState } from "react";
import Add from "./Add";
import List from "./List";
import ToDo from "./ToDo";
import Title from "./Title";
import Progress from "./Progress";
import styled from "styled-components";

interface ITodo {
  toDos: string;
  completed: boolean;
}

function App() {
  const { toDos, completed } = useState<ITodo>();  Error part
  return (
    <Title>
      <Add />
      <Progress />
      <Lists>
        <List title={toDos.length !== 0 ? "To Dos" : ""}>
          {toDos.map((toDo: any) => (
            <ToDo key={toDo.id} id={toDo.id} text={toDo.text} isCompleted={false} />
          ))}
        </List>
        <List title={completed.length !== 0 ? "Completed" : ""}>
          {completed.map((toDo: any) => (
            <ToDo key={toDo.id} id={toDo.id} text
              {...toDo.text} isCompleted />
          ))}
        </List>
      </Lists>
    </Title>
  );
}

export default App;

This code is the 'reducer.tsx' code that I thought there was a problem.

import { v4 as uuidv4 } from "uuid";
import { ADD, DEL, COMPLETE, UNCOMPLETE, EDIT } from "./actions";

export const initialState = {
  toDos: [],
  completed: [],
};

const reducer = ({ state, action }: any) => {
  switch (action) {
    case ADD:
      return {
        ...state,
        toDos: [...state.toDos, { text: action.payload, id: uuidv4() }],
      };
    case DEL:
      return {
        ...state,
        toDos: state.toDos.filter((toDo: { id: number; }) => toDo.id !== action.payload),
      };
    case COMPLETE:
      const target = state.toDos.find((toDo: { id: number; }) => toDo.id === action.payload);
      return {
        ...state,
        toDos: state.toDos.filter((toDo: { id: number; }) => toDo.id !== action.payload),
        completed: [...state.completed, { ...target }],
      };
    case UNCOMPLETE:
      const aTarget = state.completed.find(
        (toDo: { id: any; }) => toDo.id === action.payload
      );
      return {
        ...state,
        toDos: [...state.toDos, { ...aTarget }],
        completed: state.completed.filter(
          (complete: { id: number; }) => complete.id !== action.payload
        ),
      };
    case EDIT:
      const bTarget = state.toDos.find((toDo: { id: number; }) => toDo.id === action.id);
      const rest = state.toDos.filter((toDo: { id: number; }) => toDo.id !== action.id);
      return {
        ...state,
        toDos: rest.concat({ ...bTarget, text: action.payload }),
      };
    default:
      return;
  }
};

export default reducer;

This code is 'context.tsx' code.

import React, { createContext, useReducer, useContext } from 'react';
import reducer, { initialState } from "./reducer";

export type Todo = {
  id: number;
  text: string;
  done: boolean;
};

export type TodosState = Todo[];

const ToDosContext = createContext<TodosState | any>(undefined);

const ToDosProvider = ({ children }: { children: React.ReactNode }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <ToDosContext.Provider value={{ state, dispatch }}>
      {children}
    </ToDosContext.Provider>
  );
};

export const useDispatch = () => {
  const { dispatch } = useContext(ToDosContext);
  return dispatch;
};

export const useState = () => {
  const { state } = useContext(ToDosContext);
  return state;
};

export default ToDosProvider;
eeez kim
  • 175
  • 1
  • 3
  • 15

1 Answers1

1

What's happening here is due to confusing naming. You created a custom hook useState which shares its name with a built-in React hook and you’re using the wrong one.

You get an error here:

const { toDos, completed } = useState<ITodo>();  Error part

Because of this:

import React, { useState } from "react";

You wanted to be using your custom useState hook, which returns an object with properties toDos and completed. But instead of importing that hook, you imported the built-in React hook useState which returns an array with two entries (state and setState).

Go into context.tsx and rename your custom hooks to something less ambiguous, like useTodosState and useTodosDispatch. This isn't strictly required but it will make life so much easier!

Then go into App.tsx and replace useState with useTodosState, making sure to import it from context.tsx. Now that there's no doubt about which hook you intended to use, you won't need to worry about your IDE auto-importing useState from React.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • I solved this problem thanks to you. But another issue appeared. Can you tell me this? Thank you for your hard work. TypeError: Cannot destructure property 'toDos' of 'Object(...)(...)' as it is undefined. `const { toDos, completed } = useTodosState();` This is still the problem. – eeez kim Feb 07 '21 at 06:17
  • It sounds like you might not be inside a ToDosProvider. Make sure that you wrap your entire App component in a ToDosProvider. – Linda Paiste Feb 07 '21 at 06:35