React Hooks useContext value not updating - reactjs

I'm updating a username based on a form input from another component. I put a console.log inside the provider component to make sure it's getting updated... it is! But the value never updates on the component receiving this value.
Here is the provider component:
import React, { useState, useContext } from 'react';
export const GetFirstName = React.createContext();
export const GetLastName = React.createContext();
export const SetUserName = React.createContext();
export const UserNameProvider = ({ children }) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
console.log(firstName);
return (
<SetUserName.Provider value={{ setFirstName, setLastName }}>
<GetFirstName.Provider value={firstName}>
<GetLastName.Provider value={lastName}>
{children}
</GetLastName.Provider>
</GetFirstName.Provider>
</SetUserName.Provider>
);
};
Account page (wraps the component with the provider so it can receive context):
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { GetLoggedIn, UserNameProvider } from '../Providers/providers.js';
import AccountHeader from './Account/AccountHeader.js';
import AccountItemsList from './Account/AccountItemsList.js';
import LoginModal from './Modal/LoginModal.js';
const Account = () => {
const history = useHistory();
const loggedIn = useContext(GetLoggedIn);
return !loggedIn ? (
<LoginModal closeModal={history.goBack} />
) : (
<div id='account-div'>
<UserNameProvider>
<AccountHeader />
</UserNameProvider>
<AccountItemsList /> // within AccountItemsList,
// another component is wrapped the same way
// to use setFirstName and setLastName
// this works fine, as the console.log shows
</div>
);
};
export default Account;
And finally the AccountHeader page, which receives only the initial value of '', then never reflects the current value after another component calls setFirstName.
import React, { useContext } from 'react';
import { GetFirstName } from '../../Providers/providers.js';
const AccountHeader = () => {
const firstName = useContext(GetFirstName);
return (
<div id='account-top'>
<img src='#' alt='User' />
<h1>{firstName}</h1>
</div>
);
};
Just to check my sanity I implemented a really simple version of this in a codepen and it works as it should. Elsewhere in my app I'm using context to check if the user is logged in. That is also working as it should. I've pulled almost all the hair out of my head.

Related

How to create HOC to wrap useContext provider in React?

I want to reuse a context provider in different parts of my app using HOC ("higher order components"), but my state does not get updated.
This is the wrapper of the provider.
import React, { FC, useState } from "react";
import AdminContext from "./adminContext";
const AdminContextWrapper: FC = ({ children }) => {
const [authAdmin, setAuthAdmin] = useState<boolean | null>(null);
const value = { authAdmin, setAuthAdmin };
return (
<AdminContext.Provider value={value}>{children}</AdminContext.Provider>
);
};
export default AdminContextWrapper;
This is how I am implementing it :
import { useContext } from "react";
import AdminContext from "#comp/contexts/adminContext";
import AdminLogin from "#comp/admin/adminLogin";
import Limit from "#comp/admin/limits";
import AdminContextWrapper from "#comp/contexts/adminWrapper";
const Admin = () => {
const { authAdmin } = useContext(AdminContext);
const AdminPage = () => {
return (
<div>
<Limit />
</div>
);
};
return (
<AdminContextWrapper>
{authAdmin ? <AdminPage /> : <AdminLogin />}
</AdminContextWrapper>
);
Finally, this is my context:
import { createContext } from "react";
import { AdminContextType } from "#comp/utils/types";
const InitialUserContext: AdminContextType = {
authAdmin: false,
setAuthAdmin: (authAdmin: boolean | null) => {},
};
const AdminContext = createContext<AdminContextType>(InitialUserContext);
export default AdminContext;
I can see the state change in the login page but the admin page is not getting the update.
adminLogin.tsx
//...
const { setAuthAdmin, authAdmin } = useContext(AdminContext);
useEffect(() => {
console.log(authAdmin); // returns true after validating but the admin does not update.
}, [authAdmin]);
//...
I highly appreciate any help. Thank you.
Unless I'm misreading things, in
const Admin = () => {
const { authAdmin } = useContext(AdminContext);
// ...
return (
<AdminContextWrapper>
{authAdmin ? <AdminPage /> : <AdminLogin />}
</AdminContextWrapper>
);
}
you're trying to use the context outside its provider AdminContextWrapper - useContext would return undefined there unless you're already nested within another provider for the admin context, in which case the inner AdminContextWrapper there would give the inner components a different admin context.
You may want to make sure there's only ever exactly one admin context.
(As an aside, the // ... above used to be a nested component in your original code. Never do that – nested components' identity changes on each update, causing spurious re-renders.)

React update function from child using context

How can i update a function from a child using context??
The context
import React from 'react';
const Fin_states = React.createContext({
title: "title_title"
});
export default Fin_states;
This is the main page , the aim is update function_() using set_function_(see the states const [function_,set_function_] ) , but doing it using the context in the List_of_list child.
import React , { useState ,useContext} from 'react';
import Input_list_panel from './Input_list_panel'
import Fin_states from './Financial_instrument_states'
export default function FinancialInstruments() {
//Attribs
const context = useContext(Fin_states);
const [value, set_value] = useState({title:"initial_title"})
const [function_, set_function_] = useState(() => (data) => console.log(data))
// set_function_ is going to be represented by update_function in the context provider.
return (
<>
<Fin_states.Provider value={{ data:value, update_value:set_value,update_function:set_function_}}>
<Input_list_panel/>
</Fin_states.Provider>
<button onClick={()=>function_("data")}>
Add item in child from parent
</button>
</>
);
}
This is the file where the function is going to be modified using useEffect in this line context.update_function(()=>updated_function)
import React , { useState , useEffect, forwardRef ,useRef,useImperativeHandle, useContext } from 'react';
import Fin_states from './Financial_instrument_states'
export default function List_of_list(props) {
const [list_of_list,set_list_of_list]= useState(["data_1","data_2"]);
const context = useContext(Fin_states);
const updated_function=(data)=>{
var new_item=[...list_of_list];
new_item.push(data)
set_list_of_list(new_item);
console.log(list_of_list)
}
useEffect(() => {
/*update_function represents at set_function_ to modify function_() state , see the context
provider in the parent FinancialInstruments*/
context.update_function(()=>updated_function)
}, [])
return (
<>
<Fin_states.Consumer>
{
(context)=>{
return(
<>
<button onClick={
()=>{
updated_function("data")
}
}>
Add item in child
</button>
</>
)
}
}
</Fin_states.Consumer>
</>
);
}
The problem is that when the state of function_() is updated in the main page (FinancialInstruments functional component) it does not use the same context than updated_function() in List_of_list.
How can i have the same result that i have in List_of_list using updated_function() but using function_() in FinancialInstruments?

How to test code that uses a custom hook based on useContext with react-testing-library and jest

I've created a custom context hook - and I'm struggling to figure out how to pass values to its provider during testing.
My hook:
import React, { createContext, useContext, useState } from 'react';
const Context = createContext({});
export const ConfigurationProvider = ({ children }) => {
// Use State to keep the values
const [configuration, setConfiguration] = useState({});
// pass the value in provider and return
return (
<Context.Provider
value={{
configuration,
setConfiguration,
}}
>
{children}
</Context.Provider>
);
};
export const useConfigurationContext = () => useContext(Context);
export const { Consumer: ConfigurationConsumer } = Context;
This is how it's used in the application:
function App() {
return (
<ConfigurationProvider>
<div className="app">
<ComponentA />
</div>
</ConfigurationProvider>
);
}
And in ComponentA:
const ComponentA = () => {
// Get configuration
const configuration = useConfigurationContext();
return (
<div>{JSON.stringify(configuration)}</div>
)
}
This all works fine - considered that I'm calling setConfiguration from another component and set an object. Now for the testing part:
import React, { Component, createContext } from 'react';
import { render, waitFor } from '#testing-library/react';
import ComponentA from 'componentA';
const config = {
propertyA: 'hello',
};
test('renders the config', async () => {
const ConfigurationContext = createContext();
const { queryByText } = render(
<ConfigurationContext.Provider value={config}>
<ComponentA />
</ConfigurationContext.Provider>
);
expect(queryByText('hello')).toBeInTheDocument();
});
This doesn't work - I'm expecting the value that I'm sending in would be rendered in the div, but the context is an empty object. What am I doing wrong?
Thanks to Carle B. Navy I got the reason why it doesn't work. For other people two wonder what the solution is I fixed it by doing the following:
In my context hook, I changed the last line to export the provider as well:
export const { Consumer: ConfigConsumer, Provider: ConfigProvider } = Context;
Then in my test case, instead of creating a new context, I import the ConfigProvider at the top, and then:
const { queryByText } = render(
<ConfigProvider value={config}>
<ComponentA />
</ConfigProvider>
);
Thanks for helping me solve this and hope this helps someone else.

createContext using a dynamic object

1. Static object
To create context based on a static object, I use this code:
import React, { createContext } from 'react';
const user = {uid: '27384nfaskjnb2i4uf'};
const UserContext = createContext(user);
export default UserContext;
This code works fine.
2. Dynamic object
But if I need to create context after fetching data, I use this code:
import React, { createContext } from 'react';
const UserContext = () => {
// Let's suppose I fetched data and got user object
const user = {uid: '18937829FJnfmJjoE'};
// Creating context
const context = createContext(user);
// Returning context
return context;
}
export default UserContext;
Problem
When I debugg option 1, console.log(user) returns the object. Instead, option 2, console.log(user) returns undefined. What I'm missing?
import React, { useEffect, useState, useContext } from 'react';
import UserContext from './UserContext';
const ProjectSelector = (props) => {
const user = useContext(UserContext);
console.log(user);
return(...);
}
export default App;
one thing i would suggest is move this logic to a react component itself.
anhow you need to use a Provider in which you will set value to be the value consumers need to consume.useEffect is greatway to do asynchronous updates, like your requirment.
so , use a state variable as value of provider.in useEffect you fetch the data and update the state variable which in turn will update context value.
following is the code
UserContext.js
import { createContext } from "react";
const UserContext = createContext();
export default UserContext;
App.js
export default function App() {
const [user, setUser] = useState();
useEffect(() => {
console.log("here");
fetch("https://reqres.in/api/users/2")
.then(response => {
return response.json();
})
.then(data => {
setUser(data);
});
}, []);
return (
<div className="App">
<UserContext.Provider value={user}>
<DummyConsumer />
</UserContext.Provider>
</div>
);
}
DummyConsumer.js
import React, { useContext } from "react";
import UserContext from "./UserContext";
const DummyConsumer = () => {
const dataFromContext = useContext(UserContext);
return <div>{JSON.stringify(dataFromContext)}</div>;
};
export default DummyConsumer;
demo:anychronus context value providing

Why am I getting "useContext is not a function" or "context is not defined"?

I am trying to use context in my stateless component. I updated my react to v16.8.0 and added useContext, however, I keep getting these two errors and don't know what else to do. Here is my code:
import React, { useState } from "react";
import axios from "axios";
import { LanguageContext } from "./languageContext";
import { useContext } from "react";
function StripeButton() {
const context = useContext(LanguageContext);
const stripe = Stripe("pk_live_5PjwBk9dSdW7htTKHQ3HKrTd");
const [error, setError] = useState();
const handleClick = () => {
stripe
.redirectToCheckout({
...
});
};
return (
<div>
<button
id="UrgentCheckedButtonYES"
className="btn-primary"
onClick={handleClick}
>
{this.context.main.name}
<br />
</button>
<div>{error}</div>
</div>
);
}
export default StripeButton;
StripeButton.contextType = LanguageContext;
You need to import useContext like this:
import { useContext } from 'react';
const { useContext } = React
useContext is exported as method property of React
(Tested this in React 18.)
In App.js File:
import { createContext } from "react";
import ChildComponent from "./components/ChildComponent";
export const Context = createContext("Default Value");
export default function App() {
const value = "My Context Value";
return (
<Context.Provider value={value}>
<ChildComponent />
</Context.Provider>
);
}
In ChildComponent.jsx file:
import { useContext } from "react";
import { Context } from "../App";
function ChildComponent() {
const value = useContext(Context);
return <h1>{value}</h1>;
}
export default ChildComponent;
You can have as many consumers as you want for a single context. If the context value changes,then all consumers are immediately notified and re-rendered.
If the consumer isn't wrapped inside the provider, but still tries to access the context value (using useContext(Context) or <Context.Consumer>), then the value of the context would be the default value argument supplied to createContext(defaultValue) factory function that had created the context.

Resources