I am a noob with TypeScript and React Hooks. So I'm trying to learn with a simple todo app, please bear with me:
I've been trying to create a global state using the useContext and useState hooks. I set up some default values when I used useState, however when I try to access it through the Provider, I get an undefined value.
Here is my implementation of the context and Provider component:
import React, { createContext, PropsWithChildren, useState } from "react";
export interface AppContextType {
todos: TodoType[];
addTodo: (todo: TodoType) => void;
}
export interface TodoType {
todo: string;
}
export const AppContext = createContext<AppContextType>({} as AppContextType);
export const AppContextProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [todos, setTodos] = useState<TodoType[]>([
{ todo: "Learn React" },
{ todo: "Create Todo app" },
{ todo: "Learn TypeScript" },
]);
const addTodo = (todo: TodoType) => {
setTodos([...todos, todo]);
}
return (
<AppContext.Provider value={{ todos, addTodo }}>
{children}
</AppContext.Provider>
);
}
And here is my main App.tsx code:
import React, { useContext } from "react";
import TodoForm from "./components/TodoForm";
import {
AppContext,
AppContextProvider,
TodoType,
} from "./context/AppContext";
function App() {
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // Returns undefined.
return (
<AppContextProvider>
<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>
<TodoForm />
</AppContextProvider>
);
}
export default App;
Am I overlooking anything? Any help is appreciated, thank you!
EDIT: Thanks to @Erfan's answer, the issue was fixed by removing the code accessing these values in the same place where the Provider is the root, and by putting that code into a child component.
Updated App.tsx code:
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";
import { AppContextProvider } from "./context/AppContext";
function App() {
return (
<AppContextProvider>
<TodoList />
<TodoForm />
</AppContextProvider>
);
}
export default App;
And the new TodoList component:
import React, { useContext } from "react";
import {
AppContext,
TodoType,
} from "../context/AppContext";
const TodoList = () => {
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // This is ok!
return (
<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>
)
}
export default TodoList;
CodePudding user response:
You are using AppContextProvider in the same component you want to use AppContext values.
In order to use the values, you need to wrap elements at least from one level higher than the current component.
In your case, you can create a List component and use the context value inside it.
const List = ()=>{
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // Returns undefined.
return (<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>)
}
App:
function App() {
return (
<AppContextProvider>
<List/>
<TodoForm />
</AppContextProvider>
);}