0

I am trying to use React Context successfully but I a have had a lot of trouble with it. I can't even successfully pass anything one level to the provider's immediate children, as a result all I am getting at this stage is "x is undefined" errors in the console. I am using a separate class for the context an a custom hook to manage my state data.

App.js (where TodoProvider component warps around its children) -

import logo from './logo.svg';
import './App.css';
import React, {createContext, useContext} from "react"
import TodoItem from './Components/Other/TodoItem';
import TodoList from './Components/List/TodoList';
import TodoAdd from './Components/Forms/TodoAdd';
import CompletedTaskList from './Components/List/CompletedTaskList';
import useTodo from './libs/useTodo';
import {TodoContext, TodoProvider} from "./Contexts/TodoContext"

function App() {
  const {
    todoArray, setTodoArray,
    completedTaskArray,
    addCompletedItem,
    addTodoItem
  } = useContext(TodoContext);

  return (
    <TodoProvider
      value={
        todoArray, setTodoArray,
        completedTaskArray,
        addCompletedItem,
        addTodoItem
      }
    >
      <div className="App">
        <div className='card' id='mainCard'>
          <div className='card-header' id='mainCardHeader'><h4>Todo List</h4></div>
          <TodoList/>
          <TodoAdd
          />
          <CompletedTaskList
          />
        </div>
      </div>
    </TodoProvider>
    )
}

export default App;

TodoContext.js (My Context) -

import React, {createContext} from "react";
import useTodo from "../libs/useTodo";

const TodoContext = createContext();

const TodoProvider = ({children}) => {
    const {
        todoArray, setTodoArray,
        completedTaskArray,
        addCompletedItem,
        addTodoItem
      } = useTodo();
    return (
        <TodoContext.Provider
            value={
                todoArray, setTodoArray,
                completedTaskArray,
                addCompletedItem,
                addTodoItem
            } 
        >
            {children}
        </TodoContext.Provider>
    )
}

export {TodoContext, TodoProvider}

useTodo.js (My custom hook to manage state)

import React, {useState} from "react"

const useTodo = () => {
      
  const [todoArray, setTodoArray] = useState([{id: 1,todoTitle: "Code", todoDescription: "Practice React"},{id: 2,todoTitle: "Clean", todoDescription: "Wash dishes, wipe surfaces"}]);
  const [completedTaskArray, setCompletedTaskArray] = useState(["Wake up", "Make Bed"]);

  const [currentId, setCurrentId] = useState(3);

  const addTodoItem = (todoTitleInputItem, todoDescriptionInputItem) => {
    let todoTitle = todoTitleInputItem;
    let todoDescription = todoDescriptionInputItem;
    let id = currentId;
    setCurrentId(currentId+1)
    setTodoArray(todoArray => [...todoArray, {id,todoTitle, todoDescription}]);
  }

  const addCompletedItem = ({todoTitle}) => {
    setCompletedTaskArray(completedTaskArray => [...completedTaskArray, todoTitle]);
  }

  return {
      todoArray, setTodoArray,
      completedTaskArray, setCompletedTaskArray,
      addTodoItem,
      addCompletedItem
  }
}

export default useTodo;

CompletedTasklist(An example of my implementation of using a the context in one of it's children) -

import { useContext } from "react";
import {TodoContext, TodoProvider} from "../../Contexts/TodoContext"
const CompletedTaskList = () => {

    const {
        completedTaskArray
    } = useContext(TodoContext);


    return (
        <div className="card todo-item">
            <div className="card-header">
                <h3> Completed Task</h3>
            </div>
            <div className="card-body">
                <ul className="list-group ">
                    {completedTaskArray.map((item,index) => {
                        return <li className="list-group-item list-group-item-success" key={index}>{item}</li>
                    })}
                </ul>
            </div>
        </div>
    )
}

export default CompletedTaskList;   

I've been trying to resolve this for a while now and cannot wrap my mind around it.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • 2
    https://stackblitz.com/edit/react-wq6g57 have added comments should help in understanding it – N.K. Jan 14 '22 at 16:27
  • @N.K. Thanks a ton for providing a clear comments explaining what I was doing wrong. – Aueisbiejei3i939 Jan 14 '22 at 16:38
  • 1
    @N.K. Why not answer the question on the site here using a [stack snippet](https://meta.stackoverflow.com/questions/338537/how-do-i-create-a-react-stack-snippet-with-jsx-support/338538#338538)? That link can disappear or change over time, making the answer useless. – ggorlen Jan 14 '22 at 17:19

1 Answers1

1

App.js

import React, { createContext, useContext } from 'react';
import CompletedTaskList from './comp';
import { TodoProvider } from './context';

function App() {
  // you dont need useTodo, or TodoContext here
  return (
    <TodoProvider>
      {/** todo Provider is a wrapper, you dont need to pass value as prop again, you are already doing it */}
      <div className="App">
        <div className="card" id="mainCard">
          <div className="card-header" id="mainCardHeader">
            <h4>Todo List</h4>
          </div>

          <CompletedTaskList />
        </div>
      </div>
    </TodoProvider>
  );
}

export default App;

Context

import React, { createContext } from 'react';
import useTodo from './useTodo';

// Define default values of your context data.
// otherwise everything would be undefined and you need to handle it everywhere
// you are using context
const TodoContext = createContext({
  todoArray: [],
  setTodoArray: () => {},
  completedTaskArray: [],
  addCompletedItem: () => {},
  addTodoItem: () => {},
});

const TodoProvider = ({ children }) => {
  const {
    todoArray,
    setTodoArray,
    completedTaskArray,
    addCompletedItem,
    addTodoItem,
  } = useTodo();
  return (
    <TodoContext.Provider
      value={{
        // <--- you were passing value incorrectly here, it should be an object
        // you passed it as (......) instead of {...}
        // curly braces not paranthesis
        todoArray,
        setTodoArray,
        completedTaskArray,
        addCompletedItem,
        addTodoItem,
      }}
    >
      {children}
    </TodoContext.Provider>
  );
};

export { TodoContext, TodoProvider };

Repasting the answer from the link i shared above.

N.K.
  • 464
  • 2
  • 4