I'm trying to use a custom hook that bring me functions to handle my TODOS on my context, but it gives me an error
Uncaught TypeError: useContext(...) is undefined
The above error occurred in the component:
Complete Error Image
TodoProvider.jsx
import { useReducer } from 'react';
import { useTodos } from '../hooks/useTodos';
import { TodoContext, todoReducer } from './';
export const TodoProvider = ({ children }) => {
const init = () => {
return [];
};
const [todos, dispatchTodos] = useReducer(todoReducer, {}, init);
const { handleNewTodo, handleToggleTodo } = useTodos();
return (
<TodoContext.Provider
value={{ todos, dispatchTodos, handleNewTodo, handleToggleTodo }}
>
{children}
</TodoContext.Provider>
);
};
useTodos.js
import { useContext } from 'react';
import { TodoContext } from '../context';
import { types } from '../types/types';
export const useTodos = () => {
const { dispatchTodos } = useContext(TodoContext);
const handleNewTodo = todo => {
const action = {
type: types.add,
payload: todo,
};
dispatchTodos(action);
};
const handleToggleTodo = id => {
dispatchTodos({
type: types.toggle,
payload: id,
});
};
return { handleNewTodo, handleToggleTodo };
};
The error traceback in your image says
`useContext(...)` is not defined
useTodos (useTodos.js:6)
Since you aren't showing your useTodos.js file, I must rely on my crystal ball to tell me that you've forgotten to
import {useContext} from 'react';
in useTodos.js, hence "not defined".
Here's an one-file example based on your code that verifiably does work...
import { useReducer, useContext, createContext } from "react";
function todoReducer(state, action) {
switch (action.type) {
case "add":
return [...state, { id: +new Date(), text: action.payload }];
default:
return state;
}
}
const TodoContext = createContext([]);
const TodoProvider = ({ children }) => {
const [todos, dispatchTodos] = useReducer(todoReducer, null, () => []);
return (
<TodoContext.Provider value={{ todos, dispatchTodos }}>
{children}
</TodoContext.Provider>
);
};
function useTodoActions() {
const { dispatchTodos } = useContext(TodoContext);
function handleNewTodo(todo) {
dispatchTodos({
type: "add",
payload: todo
});
}
function handleToggleTodo(id) {
dispatchTodos({
type: "toggle",
payload: id
});
}
return { handleNewTodo, handleToggleTodo };
}
function useTodos() {
return useContext(TodoContext).todos;
}
function TodoApp() {
const todos = useTodos();
const { handleNewTodo } = useTodoActions();
return (
<div>
{JSON.stringify(todos)}
<hr />
<button onClick={() => handleNewTodo((+new Date()).toString(36))}>
Add todo
</button>
</div>
);
}
export default function App() {
return (
<TodoProvider>
<TodoApp />
</TodoProvider>
);
}
Related
I'm trying to experiment creating a generic reusable Provider component, users can decided to pass in their context, or use a default context.
The issue im facing, is the following:
Error: object is not iterable (cannot read property Symbol(Symbol.iterator))
Provider itself
import React, { useReducer, createContext } from 'react';
export const defaultContext = (context) => {
let BaseContext = context;
if (!context) {
console.log('is this gettinged caled');
BaseContext = createContext({});
}
console.log('BaseContext', BaseContext);
return {
BaseContext,
};
};
function useGenericProvider(reducer, initState, context?) {
const { BaseContext } = defaultContext(context ? context : null);
const [state, dispatch] = useReducer(reducer, initState);
const OurProvider = React.useMemo(() => {
const myProvider = ({ children }: any) => {
return (
<BaseContext.Provider value={{ state, dispatch }}>
{children}
</BaseContext.Provider>
);
};
return myProvider;
}, []);
return {
OurProvider,
};
}
export { useGenericProvider };
How the context is being used..
Child.tsx
import React, { useContext } from 'react';
import { defaultContext } from './useGenericReducer';
const Child = () => {
const { BaseContext } = defaultContext(null);
const [state, dispatch] = useContext(BaseContext);
console.log('state *******', BaseContext);
return (
<div>
<div>{state}</div>
</div>
);
};
export default Child;
Parent.tsx
import React, { useReducer, useState } from 'react';
import { useGenericProvider } from './useGenericReducer';
import { render } from 'react-dom';
import MainApp from './MainApp';
import './style.css';
import Child from './Child';
interface IinitState {
count: number;
}
const App: React.FC = () => {
const initState: IinitState = {
count: 0,
};
function reducer(state: IinitState, action: any) {
switch (action) {
case 'ADD':
return {
...state,
count: state.count + 1,
};
case 'SUB':
return {
...state,
count: state.count - 1,
};
default:
return state;
}
}
const { OurProvider } = useGenericProvider(reducer, initState);
return (
<OurProvider>
<Child />
</OurProvider>
);
};
render(<App />, document.getElementById('root'));
Reproducible example: https://stackblitz.com/edit/xxtt-react-usereducer-geph38?file=index.tsx
When i add columnext into useEffect dependencies it caused a infnate loop,but when i removed
columnext from denpendencies,the prop materialExtValues passed to my child Component MaterialForm is not the newest redux state but the previous state,my child component render the wrong data.I tried my solution on stackoverflow but can't get my except result,I am really confused,Who can help my out?
import React, { useCallback, useEffect, useState } from 'react';
import FormModal from '../../../../components/FormModal/FormModal';
import { FormType, ColumnExt } from '../../../../types';
import {
MaterialValues,
initialMaterialValues,
} from '../MaterialValues/MaterialValues';
import MaterialForm from './MaterialForm';
import { cvtNullToEmpty } from '../../../../helpers/cvtNullToEmpty';
import { useDispatch, useSelector } from 'react-redux';
import { selectColumnExtDataSelector } from '../../../../redux/columnext/columnext.selector';
import {
materialExtValues,
ExtValues,
EXT_KEYS,
} from '../MaterialValues/MaterialValues';
import { fetchColumnextsRequest } from '../../../../redux/columnext/columnext.action';
interface MaterialEditProps {
editItem: string;
initialValues: MaterialValues;
handleClose: () => void;
}
const MaterialEdit: React.FC<MaterialEditProps> = ({
editItem,
initialValues,
handleClose,
}) => {
const dispatch = useDispatch();
const columnexts: ColumnExt[] = useSelector(selectColumnExtDataSelector);
const [extValues, setExtValues] = useState<ExtValues>(materialExtValues);
//get newest extValues
const initExtValues = useCallback(() => {
const colextFormData = new FormData();
colextFormData.append('TableName', 'material');
colextFormData.append('ObjectId', editItem);
dispatch(fetchColumnextsRequest(colextFormData));
}, [editItem, dispatch]);
//combine newest extValues with old extValues
const mergeMaterialExtValues = useCallback(() => {
const materialExtMerge: ExtValues = {};
columnexts.forEach((item) => {
EXT_KEYS.forEach((key) => {
if (item[key] !== '') {
materialExtMerge[`${item.ColumnName}__${key}`] = item[key];
}
});
});
console.log('materialExtMerge', materialExtMerge);
const newExts = Object.assign(materialExtValues, materialExtMerge);
setExtValues((prev) => ({ ...prev, ...newExts }));
console.log('materialExtValues', materialExtValues);
}, [columnexts]);
useEffect(() => {
initExtValues();
}, [initExtValues, columnexts]);
useEffect(() => {
if (columnexts.length > 0 && columnexts[0].ObjectId === editItem) {
mergeMaterialExtValues();
}
}, [mergeMaterialExtValues, editItem, columnexts.length]);
return (
<>
<div className='material-edit'>
<FormModal
title='Edit Material'
iconSrc='/assets/images/icons/material.png'
handleClose={handleClose}
renderDataForm={() => (
<MaterialForm
formType={FormType.EDIT}
editItem={editItem}
materialExtValues={extValues}
initialValues={
(cvtNullToEmpty(initialValues) as MaterialValues) ||
initialMaterialValues
}
handleClose={handleClose}
/>
)}
/>
</div>
</>
);
};
export default MaterialEdit;
The code of selectColumnExtDataSelector is :
import { RootState } from "../rootReducer";
import { createSelector } from "reselect";
export const selectColumnExts = (state: RootState) =>
state.columnext
export const selectColumnExtDataSelector = createSelector(
[selectColumnExts],
columnexts => columnexts.data
)
And ColumnExtReducer code is:
import { ColumnExt } from "src/types"
import { AnyAction } from 'redux';
import { columnextActionType } from "./columnext.types";
export interface ColumnExtState {
data: ColumnExt[],
loading: boolean;
error: string | null;
}
const initialState: ColumnExtState = {
data: [],
loading: false,
error: null
}
const columnextReducer = (state: ColumnExtState = initialState,
action: AnyAction
) => {
switch (action.type) {
case columnextActionType.FETCH_COLUMNEXTS_REQUEST:
return { ...state, loading: true }
case columnextActionType.FETCH_COLUMNEXTS_SUCCESS:
return { ...state, loading: false, data: action.payload }
case columnextActionType.FETCH_COLUMNEXTS_FAILURE:
return { ...state, loading: true, error: action.payload }
default:
return state;
}
}
export default columnextReducer;
I'm working a Nextjs - Typescript project. I'am trying to add a Loader component. while page is loading loader component is true. otherwise false.
This is my code:
LoderContext.ts
import React, { useReducer } from "react";
import { NextPage } from "next";
type State = { loading: boolean };
type Action = { type: "startLoading" } | { type: "endLoading" };
type Dispatch = (action: Action) => void;
export const LoadingContext =
React.createContext<{ state: State; dispatch: Dispatch } | undefined>(
undefined
);
const initialState = {
loading: true,
};
const { Provider } = LoadingContext;
const reducer = (state: State, action: Action) => {
switch (action.type) {
case "startLoading":
return {
loading: true,
};
case "endLoading":
return {
loading: false,
};
default:
throw state;
}
};
const LoadingProvider: NextPage = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Provider value={value}>{children}</Provider>;
};
export function useLoading() {
const context = React.useContext(LoadingContext); // error is somewhere here
if (context === undefined) {
throw new Error("useCount must be used within a CountProvider");
}
return context;
}
export default LoadingProvider;
Loder.tsx
const Loader: NextPage = () => {
return (
<div className={styles.loader_wrapper}>
<div className={styles.content}>
<span className={styles.loading_text}>Loading...</span>
</div>
</div>
);
};
export default Loader;
Index.tsx
import Loader from "../components/Loader/Loader";
import { useLoading } from "../context/LoadingContext";
import { useRouter } from "next/dist/client/router";
const { state, dispatch } = useLoading();
const router = useRouter();
useEffect(() => {
router.events.on("routeChangeStart", () => {
dispatch({ type: "startLoading" });
});
return () => {
router.events.off("routeChangeStart", () => {
dispatch({ type: "endLoading" });
});
};
}, [dispatch, router]);
useEffect(() => {
dispatch({ type: "endLoading" });
}, [dispatch, router]);
const Home: NextPage = () => {
return (
<>
{state.loading ? (
<Loader />
) : (
<>
<div>
// other components
</div>
</>
)}
</>
);
};
I got this error. Error: Invalid hook call. Hooks can only be called inside of the body of a function component
What are the mistakes in my code?
Your index page is calling useLoading, useEffect and useRouter outside the Home component.
Refer react documentation. It clearly mentions the issue you're facing.
Hooks can only be called inside the body of a function component.
It should be something like this.
import Loader from "../components/Loader/Loader";
import { useLoading } from "../context/LoadingContext";
import { useRouter } from "next/dist/client/router";
const Home: NextPage = () => {
const { state, dispatch } = useLoading();
const router = useRouter();
useEffect(() => {
router.events.on("routeChangeStart", () => {
dispatch({ type: "startLoading" });
});
return () => {
router.events.off("routeChangeStart", () => {
dispatch({ type: "endLoading" });
});
};
}, [dispatch, router]);
useEffect(() => {
dispatch({ type: "endLoading" });
}, [dispatch, router]);
return (
<>
{state.loading ? (
<Loader />
) : (
<>
<div>
// other components
</div>
</>
)}
</>
);
};
I am following this great tut by Kent C. Dodds regarding usage React Context.
But I actually have this component nested within the provider so not sure why I am getting.
Error: userState must be used within a UserProvider
So perhaps I am not getting the point of creating a function which is throwing an Error despite following how you should use the provider... Perhaps I implemented it incorrectly?
So this is my userContext setup:
import React, { useState, useEffect, useContext, useReducer } from 'react';
var initialState = {
...state...
};
var UserStateContext = React.createContext();
var UserContextDispatch = React.createContext();
function setLocalStorage(key, value) {
...function innards...
}
function getLocalStorage(key, initialValue) {
...function innards...
}
function UserProvider({ children }) {
function userReducer(state, { type, payload }) {
switch (type) {
case 'setUserId': {
return { ...state, ...{ id: payload.id } };
}
case 'setAvatar': {
return {
...state,
...{ avatar: payload.avatar }
};
}
case 'setIsRoutingVisible': {
return {
...state,
...{ isRoutingVisible: payload.isRoutingVisible }
};
}
case 'addMarker': {
user.isLengthOfMarkersLessThanTwo
? {
...state,
markers: user.markers.concat(payload.marker)
}
: null;
break;
}
case 'setMap': {
return {
...state,
currentMap: payload.curerntMap
};
}
default: {
throw new Error(`Unhandled action type: ${type}`);
}
}
}
const [user, setUser] = useState(() => getLocalStorage('user', initialState));
var [state, dispatch] = useReducer(userReducer, user);
useEffect(() => {
setLocalStorage('user', state);
}, [state]);
return (
<UserStateContext.Provider value={state}>
<UserContextDispatch.Provider value={dispatch}>
{children}
</UserContextDispatch.Provider>
</UserStateContext.Provider>
);
}
function userState() {
const context = React.useContext(UserStateContext);
if (context === undefined) {
throw new Error('userState must be used within a UserProvider');
}
return context;
}
function userDispatch() {
const context = React.useContext(UserContextDispatch);
if (context === undefined) {
throw new Error('userDispatch must be used within a UserProvider');
}
return context;
}
export { UserProvider, userState, userDispatch };
The error is pointing to my Map component, which is passing state and dispatch props from UserContext to my Routing component which is a class component.
import React, { useState, useContext, useEffect, useRef, useCallback } from 'react';
import { Button } from 'semantic-ui-react';
import L from 'leaflet';
import * as ELG from 'esri-leaflet-geocoder';
import { Map } from 'react-leaflet';
import { Dimmer, Loader } from 'semantic-ui-react';
import Routing from '../RoutingMachine/RoutingMachine.jsx';
import { userState, userDispatch } from '../Context/UserContext.jsx';
import UIContext from '../Context/UIContext.jsx';
import { stringify } from 'flatted';
export default function MyMap({}) {
var [zoom, setZoom] = useState(18);
var [animate, setAnimate] = useState(false);
var [userLocation, setUserLocation] = useState(null);
var mapRef = useRef();
console.log('userState() ', userState());
var {
avatar,
currentMap,
id,
isLengthOfMarkersLessThanTwo,
isRoutingVisible,
markers,
removeRoutingMachine
} = userState();
var dispatch = userDispatch();
var { isMobile, isDesktop } = useContext(UIContext);
useEffect(() => {
if (isRoutingVisible === false) {
dispatch({
type: 'setIsRoutingVisible',
payload: {
isRoutingVisible: true
}
});
}
});
useEffect(() => {
if (markers.length === 2) {
dispatch({
type: 'isLengthOfMarkersLessThanTwoFalse',
payload: { isLengthOfMarkersLessThanTwo: false }
});
}
}, [JSON.stringify(markers)]);
return (
<Map
animate={animate}
onLocationFound={handleOnLocationFound}
zoom={zoom}
ref={mapRef}
>
{isRoutingVisible && (
<Routing
markers={markers}
dispatch={dispatch}
removeRoutingMachine={removeRoutingMachine}
map={currentMap}
userLocation={userLocation}
isMobile={isMobile}
isDesktop={isDesktop}
/>
)}
</Map>
);
}
It seemed the problem was the way I was passing the state & dispatch value into each provider:
return (
<UserStateContext.Provider value={state}>
<UserContextDispatch.Provider value={dispatch}>
{children}
</UserContextDispatch.Provider>
</UserStateContext.Provider>
);
I got it working by passing a object with the value: value={{ key: value }}
So one should do this:
return (
<UserStateContext.Provider value={{ state: state }}>
<UserContextDispatch.Provider value={{ dispatch: dispatch }}>
{children}
</UserContextDispatch.Provider>
</UserStateContext.Provider>
);
I need your help. I'm creating an app with useContext and useReducer hooks and I a have problems. I have a function to get all notes from my database. I called that function inside off useEffect hook:
import React, { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
//Context
import AuthContext from "../../context/auth/authContext";
import NoteContext from '../../context/notes/noteContext';
//Components
import { Row, Col, Container, Button } from "react-bootstrap";
import Canva from '../Canva/Canva';
import Note from '../Note/Note';
const Dashboard = () => {
const { t, i18n } = useTranslation();
const authContext = useContext(AuthContext);
const { authUser, user } = authContext;
const noteContext = useContext(NoteContext);
const { notes, getNotes, addNote } = noteContext;
useEffect(() => {
getNotes();
}, []);
return (
<>
<Container>
<Row>
<Col sm={12} md={10}>
<Button onClick={() => addNote()} type='button' className='mb-2'>
AƱadir elemento
</Button>
<Canva>
{notes && (notes.map(note => {
return (
<Note key={note._id} note={note} />
)
}))}
</Canva>
</Col>
</Row>
</Container>
</>
);
};
export default Dashboard;
If I called that function that way, my state doesn't change:
notes: undefined
But if I introduce a dependency inside of useEffect, my app goes into an infinite loop. For example:
useEffect(() => {
getNotes();
}, [notes])
//Or:
useEffect(() => {
getNotes()
}, [getNotes])
How can I avoid the infinite loop?
You need to use 2 useEffect hooks, one for fetch data and second to proceed it:
useEffect(() => {
getNotes();
}, []);
useEffect(() => {
if (notes && notes.length) {
....setState or what else
}
}, [notes]);
My note state looks like:
import React, { useReducer } from 'react';
import clientAxios from '../../config/clientAxios';
import NoteContext from './noteContext';
import NoteReducer from './noteReducer';
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
const NoteState = ({ children }) => {
const initialState = {
notes: [],
noteError: false,
};
const [state, dispatch] = useReducer(NoteReducer, initialState);
const getNotes = async () => {
try {
const response = await clientAxios.get('/user/Notes');
dispatch({
type: GET_NOTES,
payload: response.data
})
} catch (error) {
console.log(error.response);
}
}
const addNote = async data => {
try {
const response = await clientAxios.post('/addNote', data);
dispatch({
type: ADD_NOTE,
payload: response.data.data
})
} catch (error) {
console.log(error.response);
}
}
const updateNote = async (id, { title, description }) => {
try {
const response = await clientAxios.put(`updateNote/${id}`, { title, description });
console.log(response.data);
dispatch({
type: UPDATE_NOTE,
payload: response.data
})
} catch (error) {
console.log(error.response)
}
}
const deleteNote = async id => {
try {
await clientAxios.put(`/deleteNote/${id}`);
dispatch({
type: DELETE_NOTE,
payload: id
})
} catch (error) {
console.log(error.response);
}
}
return(
<NoteContext.Provider
value={{
notes: state.notes,
noteError: state.noteError,
getNotes,
addNote,
updateNote,
deleteNote,
}}
>
{children}
</NoteContext.Provider>
);
}
export default NoteState;
and my reducer:
import {
GET_NOTES,
ADD_NOTE,
DELETE_NOTE,
UPDATE_NOTE,
} from '../../types';
export default (action, state) => {
switch(action.type) {
case GET_NOTES:
return {
...state,
notes: action.payload
}
case ADD_NOTE:
return {
...state,
notes: [...state.notes, action.payload]
}
case UPDATE_NOTE:
return {
...state,
notes: state.notes.map(note => note._id === action.payload._id ? action.payload : note)
}
case DELETE_NOTE:
return {
...state,
notes: state.notes.filter(note => note._id !== action.payload),
}
default:
return state;
}
}