React Context value never updates - reactjs

I have the following code as a component that returns a context. For some reason when I call the updateUser function it is not setting the state, I keep getting a blank object. Is there something different I have to do to a function like that when it has parameters?
import React, { useState } from "react";
const UserContext = React.createContext({
user: {},
updateUser: (incomingUser) => {},
});
const UserData = (props) => {
const [user, setUser] = useState({});
const updateUser = (incomingUser) => {
setUser(incomingUser);
console.log(`user=${JSON.stringify(incomingUser)}`);
};
return (
<UserContext.Provider value={{ user, updateUser }}>
{props.children}
</UserContext.Provider>
);
};
export { UserData, UserContext };

Get rid of the default value for UserContext, or initialise it as null:
const UserContext = React.createContext(null);
Per the React Docs:
When React renders a component that subscribes to this Context object it will read the current context value from the closest matching Provider above it in the tree.
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This default value can be helpful for testing components in isolation without wrapping them.

Related

how useContext hook is working here with useState hook

import { createContext, useState } from "react";
this is the place where the actual useContext data is going to be saved currentUser and setCurrentUser
export const UserContext = createContext({
currentUser: null,
seCurrentUser: () => null,
});
but here is userProvider with useState Hook and what is
the function of this useState Hook here and how Value is adding data in useState hook, if it is?
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const value = { currentUser, setCurrentUser };
return (
<UserContext.Provider value={value}> {children} </UserContext.Provider>
);
};
please tell me how it's going to be used in other web pages to collect data it's really confusing for me i'm trying to understand since last week.
this is the place where the actual useContext data is going to be saved currentUser and setCurrentUser
export const UserContext = createContext({
currentUser: null,
seCurrentUser: () => null,
});
Actually, this is just the default value. You will not be storing data here, and if a component ever gets these values it means you have forgotten to render a <UserContext.Provider>.
const [currentUser, setCurrentUser] = useState(null);
const value = { currentUser, setCurrentUser };
return (
<UserContext.Provider value={value}> {children} </UserContext.Provider>
);
This is where the work really happens. Your component has a state, which behaves just like any other state in react. The only thing different is that you are then making the current user and the setCurrentUser function available via context, so components farther down the tree can use and change the state.
Context is just a way to pass a value Component A to Component B. You still need to actually implement state or other code to do what you want.
Here's how it looks to consume the context:
const SomeComponent = () => {
const { currentUser, setCurrentUser } = useContext(UserContext);
// currentUser is the state found in UserProvider, and setCurrentUser
// is the state setter function, also from UserProvider
}

How to use getStaticProps in combination with useReducer and useContext

I am converting a React app to a NextJS app and cannot figure out how to use the props of getStaticProps into the initial state of my reducer inside my context.
In my current setup, my AppContext looks like this:
import { createContext, useReducer } from "react";
import { initialState, appReducer } from "../reducers/appReducer";
const AppContext = createContext();
const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={[state, dispatch]}>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppContextProvider };
The I have my appReducer as follows:
export const initialState = {
example: null,
anotherExample: []
};
export const AppReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "example_action":
return {...state, example: payload.example }
}
default:
return state;
}
What I do not understand is how I can get the props of getStaticProps in the initial state of my reducer (since my whole application is based on this context with reducer).
ExamplePage (pages/example-page.js)
export default function ExamplePage({ data }) {
const [state, dispatch] = useReducer(appReducer, {
...initialState,
example: data.example
});
return (
<AppContext.Provider value={[state, dispatch]}>
<div>...</div> // Rest of my application
<AppContext.Provider>
);
}
export export async function getStaticProps() {
const { data } = // Fetching my data ....
return {
props: {
data: data,
},
};
}
What I tried to do is move the reducer out of the AppContext, so I can pass the props from getStaticProps to it, but then I cannot use the AppContext including my reducer anymore in other places in my application. Or should I create my context like this:
AppContext.js
import { createContext } from "react";
const AppContext = createContext();
// Don't create my provider here, do it in ExamplePage?
export default AppContext;
And import this everywhere I want to use this context and create the provider inside my ExamplePage only?
I have the feeling I do not understand the concept of context, reducer with NextJS, is that true?
I didn't fully understand what exactly you want to achieve, but I think you're using contexts in the wrong place.
If, as I understand it, your whole React application was wrapped in a Context Provider, and you want to repeat this in nextjs, the _app.js file is best suited for this. This is a special file in nextjs, more about it here: https://nextjs.org/docs/advanced-features/custom-app, if you don't have it, you need to create it in the pages folder.
In your case it will be something like this:
// ./pages/_app.js
const AppContext = createContext();
export default function MyApp({ Component, pageProps }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return(
<AppContext.Provider value={[state, dispatch]}>
<Component {...pageProps} />
</AppContext.Provider>
)
}
The _app.js is a special file in nextjs, all pages of your site will be wrapped in it, you can use this for your global states, they will not be reset when the user navigates through the pages of the site.
getStaticProps is designed for a completely different purpose, getStaticProps is designed to process data for a specific page, and not for all pages of the site.

dispatch is not accessible from useContext

i have simple store
import React, { createContext, useReducer } from "react";
import Reducer from "./UserReducer";
const initialState = {
user: {},
error: null
};
const Store = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
{children}
</Context.Provider>
);
};
export const Context = createContext(initialState);
export default Store;
i have wrapped my app with it like
<Store>
<ThemeProvider theme={Theme}>
<CssBaseline />
<Navbar />
<Wrapper>
<Profile />
</Wrapper>
</ThemeProvider>{" "}
</Store>
There is additional setup as well where my authentication pages are located in separate wrapper so i wrapped that with store as well.
here is code for that wrapper (extra removed).
import Store from "../../../context/Store";
export default function Wrapper({ children }) {
const classes = useStyles();
return (
<Store>
//different dumb containers opening
{children}
//different dumb containers closing
</Store>
);
}
Now when i try to access context within child component like
import React, { useContext } from "react";
import { Context } from "../../../context/Store";
import { SET_USER } from "../../../context/UserTypes";
function SignUp(props) {
const [state, setState] = React.useState({ ...initialState });
const [userData, dispatch] = useContext(Context);
console.log(userData, dispatch, "check");
// rest of component
i get following error
TypeError: undefined is not a function
i tried to log result of useContext without destructuring it but all it had was global state but no dispatch function with it.
Reactjs version = 17.0.1
Update: dispatch is accessible outside withAuthenticator HOC but not within that so it might be amplify issue.
i have opened issue on amplify repo.
Unable to access dispatch from useContext from components wrapped withAuthenticator
There a few things I see that could be potential issues.
The major problem is the value of your Context. When you create the context, its value is a state (createContext(initialState)). But when you pass a value to the Context.Provider you are giving it an array which contains both a state and a dispatch function (value={[state, dispatch]}). You need to be consistent about the value that is contained in the context. Is it a state object or a tuple?
The error that you are describing seems like what would happen if the Context is accessed outside of a Provider. It would fall back the initial context value which is just a state rather than a [state, dispatch] tuple.
You need to change the initial value to something that matches the value that you are expecting.
const Context = createContext([
initialState, // state
() => console.error("Cannot dispatch because Context was used outside of a provider") // dispatch
]);
But you also need to figure out why the useContext is getting the default value instead of one from a Provider. I'm not sure if these next two issues will fix that or not.
It looks like the Profile component is inside of two different Store providers. One which is outside of everything and one which is inside of the Wrapper component. These will have two separate instances of state. When there are two providers for the same context React uses the inner one. So any actions that you dispatch from Profile won't update the outer version of Store.
You create the Context variable after you use it in your Store component. You should switch the order.

react Login with useContext useReducer basic example

I'm using Typescript. I want to
Create a context
Use the context in routing
Update the context when logged in.
I'm following this tutorial, except Typescript cut my hopes short. See below
I have this in my App.tsx
import React from 'react';
import './App.css';
import Login from "./auth/login";
import Home from "./dash/home";
import Header from "./menu";
const initialState = {
isAuthenticated: false,
user: null,
token: null,
};
export const AuthContext = React.createContext(); // <---- This right here complains:
// Expected 1-2 arguments, but got 0.ts(2554)
// index.d.ts(349, 9): An argument for 'defaultValue' was not provided.
const reducer = (state: any, action: any) => {
switch (action.type) {
case "LOGIN":
return {
...state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token
};
case "LOGOUT":
localStorage.clear();
return {
...state,
isAuthenticated: false,
user: null
};
default:
return state;
}
};
const App: React.FC = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<AuthContext.Provider
value={{
state,
dispatch
}}
>
<Header />
<div className="App">{!state.isAuthenticated ? <Login /> : <Home />}</div>
</AuthContext.Provider>
);
}
export default App;
In my login page, I have this:
import React from "react";
import { useForm } from 'react-hook-form';
import axios from 'axios';
import { AuthContext } from "../App";
export const Login = () => {
const { dispatch } = React.useContext(AuthContext) // <--- This the right way to do it?
// Where is the "dispatch" magically coming from?
const onSubmit = (data: any) => {
axios.post('http://localhost/api/v1/users/login/', data)
.then(res => {
console.log(res);
})
}
return (
<div className="login-container">
<!-- assume there's a form here -->
</div>
);
};
export default Login;
So
What do I put in for the "defaultValue"?
How do I update the context after login?
EDIT:
More context (pun intended) of what I wanna achieve.
The App.tsx has an <AuthContext.Provider ...> ... </AuthContext.Provider> If I understand correctly, this Context takes in the value of the state, dispatch and as per the {!state.isAuthenticated ... } part, dynamically alternates between <Login /> and <Home/> component.
This <AuthContext.Provider/> as per the initialState is set to isAuthenticated: false
When the user logs in at the Login Component, my question is, how do I, via the AuthContext, update the values in the App.tsx so as the routing will change?
If you take a look at the docs about React.createContext
The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming components to use defaultValue.
So you don't need to pass anything to the context, but you are getting the error because of typescript.
You need to create an interface for the context value.
You are passing the context as
value={{
state,
dispatch
}}
But you need to have an interface for that when create the context.
export const AuthContext = React.createContext(); // <---- missing interface
Here is an example on how to do it.
Where is the "dispatch" magically coming from?
This is how useContext works.
The value you get form useContext(MyContex) is the value passed by MyContext.Provider by value prop.
e.g.
<MyContext.Provider value={fooValue}>
{/* ... */}
</MyContext.Provider>
// same fooValue from the Provider
const fooValue = useState(MyContext)
Edit
After the async call inside onSubmit you must call dispatch and pass {type: 'LOGIN', payload: DATA_FROM_API} so it does in the reducer method and sets isAuthenticated to true
const onSubmit = (data: any) => {
axios.post('http://localhost/api/v1/users/login/', data)
.then(res => {
console.log(res.data) // <= if this is `payload`, than pass it to dispatch
dispatch({type: 'LOGIN', payload: res.data})
})
}
I had a similar problem, but after many trials and errors I was able to fix it.
I was following this tutorial:
Link: https://soshace.com/react-user-login-authentication-using-usecontext-and-usereducer/
Code: https://github.com/nero2009/login-auth-useContext
The problem about routing is also covered there. But it is in JS, so you have to make some modifications. Here is what I did:
First, I specified the action types, in your example it would be:
export type AuthenticationAction = { type: 'LOGIN', payload: IPayload } | {type: 'LOGOUT'}
I specified an interface for my payload, but it may work with "any" as well.
Then, I specified the default values (this is probably what you were looking for):
const AuthenticationStateContext = React.createContext(initialState)
const AuthenticationDispatchContext = React.createContext({} as Dispatch<AuthenticationAction>)
After that it should work.

I am using React Context and would like to get confirmed my structure

It is my first application using react context with hooks instead of react-redux and would like to get help of the structure of the application.
(I'm NOT using react-redux or redux-saga libraries.)
Context
const AppContext = createContext({
client,
user,
});
One of actions example
export const userActions = (state, dispatch) => {
function getUsers() {
dispatch({ type: types.GET_USERS });
axios
.get("api address")
.then(function(response) {
dispatch({ type: types.GOT_USERS, payload: response.data });
})
.catch(function(error) {
// handle error
});
}
return {
getUsers,
};
};
Reducer (index.js): I used combineReducer function code from the redux library
const AppReducer = combineReducers({
client: clientReducer,
user: userReducer,
});
Root.js
import React, { useContext, useReducer } from "react";
import AppContext from "./context";
import AppReducer from "./reducers";
import { clientActions } from "./actions/clientActions";
import { userActions } from "./actions/userActions";
import App from "./App";
const Root = () => {
const initialState = useContext(AppContext);
const [state, dispatch] = useReducer(AppReducer, initialState);
const clientDispatch = clientActions(state, dispatch);
const userDispatch = userActions(state, dispatch);
return (
<AppContext.Provider
value={{
clientState: state.client,
userState: state.user,
clientDispatch,
userDispatch,
}}
>
<App />
</AppContext.Provider>
);
};
export default Root;
So, whenever the component wants to access the context store or dispatch an action, this is how I do from the component :
import React, { useContext } from "react";
import ListMenu from "../common/ListMenu";
import List from "./List";
import AppContext from "../../context";
import Frame from "../common/Frame";
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
return (
<Frame>
<ListMenu
dataList={userState.users}
selectDataList={selectUserList}
/>
<List />
</Frame>
);
};
export default Example;
The problem I faced now is that whenever I dispatch an action or try to access to the context store, the all components are re-rendered since the context provider is wrapping entire app.
I was wondering how to fix this entire re-rendering issue (if it is possible to still use my action/reducer folder structure).
Also, I'm fetching data from the action, but I would like to separate this from the action file as well like how we do on redux-saga structure. I was wondering if anybody know how to separate this without using redux/redux-saga.
Thanks and please let me know if you need any code/file to check.
I once had this re-rendering issue and I found this info on the official website:
https://reactjs.org/docs/context.html#caveats
May it will help you too
This effect (updating components on context update) is described in official documentation.
A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization
Possible solutions to this also described
I see universal solution is to useMemo
For example
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
const users = userState.users;
return useMemo(() => {
return <Frame>
<ListMenu
dataList={users}
selectDataList={selectUserList}
/>
<List />
</Frame>
}, [users, selectUserList]); // Here are variables that component depends on
};
I also may recommend you to completly switch to Redux. You're almost there with using combineReducers and dispatch. React-redux now exposes useDispatch and useSelector hooks, so you can make your code very close to what you're doing now (replace useContext with useSelector and useReducer with useDispatch. It will require minor changes to arguments)

Resources