im using the useContext from react , i have add boolean state to context but when i use it in the component it's give me typeErro : the setState is not a function
what im doing wrong this is my code ?
// context file
import { createContext, useContext, Dispatch, SetStateAction } from 'react';
interface TasksContextType {
isSubmitLoading: boolean;
setIsSubmitLoading: Dispatch<React.SetStateAction<boolean>>;
}
export const TasksContext = createContext({} as TasksContextType);
export const useTasksContext = () => {
const context = useContext<TasksContextType>(TasksContext);
if (!context) {
throw new Error('useTasksContext should be used within a TasksContext');
}
return context;
};
the provider file
//provider file
import React, { FC, useState } from 'react';
import { TasksContext } from './TasksContext';
export const TasksProvider: FC = ({ children }) => {
const [isSubmitLoading, setIsSubmitLoading] = useState<boolean>(false);
return (
<TasksContext.Provider
value={{
isSubmitLoading,
setIsSubmitLoading,
}}
>
{children}
</TasksContext.Provider>
);
};
the component where i use the useContext
//component
const { setIsSubmitLoading, isSubmitLoading } = useTasksContext();
useEffect(() => {
setIsSubmitLoading(mutation.isLoading);
}, [mutation, setIsSubmitLoading]);
CodePudding user response:
Here's an example based on the code you provided so you can see that it works and how to use it (more explanation below):
<div id="root"></div><script src="https://unpkg.com/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom';
// import {
// default as React,
// createContext,
// useContext,
// useEffect,
// useState,
// type Dispatch,
// type ReactElement,
// type ReactNode,
// type SetStateAction,
// } from 'react';
// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {
createContext,
useContext,
useEffect,
useState,
} = React;
type TasksContextType = {
isSubmitLoading: boolean;
setIsSubmitLoading: Dispatch<SetStateAction<boolean>>;
};
const TasksContext = createContext(undefined as unknown as TasksContextType);
function useTasksContext (): TasksContextType {
return useContext<TasksContextType>(TasksContext);
}
function TasksProvider ({children}: { children?: ReactNode }): ReactElement {
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
return (
<TasksContext.Provider
value={{
isSubmitLoading,
setIsSubmitLoading,
}}
>{children}</TasksContext.Provider>
);
}
function App (): ReactElement {
const {isSubmitLoading, setIsSubmitLoading} = useTasksContext();
useEffect(() => {
setTimeout(() => setIsSubmitLoading(true), 1500);
}, [setIsSubmitLoading]);
const handleClick = () => setIsSubmitLoading(bool => !bool);
return (
<div style={{fontFamily: 'sans-serif'}}>
<h1>Using the Context API</h1>
<div>
<pre>
<code>
{JSON.stringify({isSubmitLoading}, null, 2)}
</code>
</pre>
</div>
<button onClick={handleClick}>Toggle state</button>
</div>
);
}
function AppRoot (): ReactElement {
return (
<TasksProvider>
<App />
</TasksProvider>
);
}
ReactDOM.render(<AppRoot />, document.getElementById('root'));
</script>
You didn't show in your code where you actually use the ContextProvider and the JSX structure of your app, but it's important that you only use useTasksContext
in components that are rendering inside the Provider root. In the example above, I created a component dedicated to rendering the Provider so that it's out of the way and in the main App
component, you can just focus on the code.
CodePudding user response:
You are checking if you are within your context provider by checking for a falsy value, but since you initialize context with a value ({}
) it never will be undefined:
import { createContext, useContext, Dispatch, SetStateAction } from 'react';
interface TasksContextType {
isSubmitLoading: boolean;
setIsSubmitLoading: Dispatch<React.SetStateAction<boolean>>;
}
export const TasksContext = createContext({} as TasksContextType); // Here is your problem, change this to:
// createContext<TasksContextType | undefined>();
export const useTasksContext = () => {
const context = useContext<TasksContextType>(TasksContext); // You can remove the generic here, it will be inferred
if (!context) {
throw new Error('useTasksContext should be used within a TasksContext');
}
return context;
};
Now if you try to use the context somewhere you can't, it will throw an error useTasksContext should be used within a TasksContext
Or, if you use it within your context it should work ok.
Also note: You should be memoizing your value prop in TasksContext.Provider, otherwise it will be a new object every time and cause all consumers to rerender.
const value = useMemo(() => ({
isSubmitLoading,
setIsSubmitLoading,
}, [isSubmitLoading]) // you can skip setIsSubmitLoading as it is referentially stable