I have a state that I want to make global so that I can use it across multiple different components
and I am trying to do this through using context.
So I have my initial Component which gets the data and sets the global state, the issue I am having is when I try to use this state in the other components it seems to be empty because I believe my GlobalContext varibale is not updating so will be empty when the other components try to use the state. I cannot seem to figure out what I am missing to ensure my global state and context are both updated so that I can use them across the different components that require the data as well.
Can anyone figure out where I should update my context as well as my state
Component that gets the data initially:
import React from "react";
import { useState, useEffect, useMemo, useContext } from "react";
import axios from "axios";
import { GlobalContext } from "./Store";
function Map() {
// ------- global state
const [activities, setActivities] = useContext(GlobalContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading ? (
<>
<MapComp
activityData={activities}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
GlobalStateStore component:
import React, {useState} from "react";
const initState = [];
export const GlobalContext = React.createContext();
const Store = ({children}) => {
const [activities, setActivities] = useState(initState);
return (
<GlobalContext.Provider value={[activities, setActivities]}>
{children}
</GlobalContext.Provider>
)
}
export default Store;
component I am trying to use the global state in but is empty:
import React, {useContext} from 'react';
import { GlobalContext } from "./Store";
function ActivityList() {
const [activities, setActivities] = useContext(GlobalContext);
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<Store>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</Store>
);
}
export default App;
Here's a barebones single-file version of your code that certainly works.
Since you aren't showing how you're mounting your <Map /> and <ActivityList /> components originally, there's not much more I can do to help you with that code, though I will note that it's useless to try and log activities in the same function that has just setActivities, since setState is async (and the function will have captured the earlier activities value anyway).
import React, { useContext, useState, useEffect } from "react";
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
async function getActivityData() {
console.log("calling");
await delay(1000);
return [{ name: "foo", distance: 123 }];
}
function Map() {
const [, setActivities] = useContext(GlobalContext);
useEffect(() => {
getActivityData().then(setActivities);
}, [setActivities]);
return <>map</>;
}
const initState = [];
const GlobalContext = React.createContext();
const Store = ({ children }) => {
const [activities, setActivities] = useState(initState);
return (
<GlobalContext.Provider value={[activities, setActivities]}>
{children}
</GlobalContext.Provider>
);
};
function ActivityList() {
const [activities] = useContext(GlobalContext);
return <div>{JSON.stringify(activities)}</div>;
}
export default function App() {
return (
<Store>
<Map />
<ActivityList />
</Store>
);
}
Related
I have the following app entry component:
React.useEffect(() => {
const fetchData = async () => {
try {
const libraries: unknown[] = await sendRequest('/libraries');
const softwareComponents: unknown[] = await sendRequest('/softwareComponents');
localStorage.setItem('libraries', JSON.stringify(arraySetup(libraries, 'libraries')));
localStorage.setItem('softwareComponents', JSON.stringify(arraySetup(softwareComponents, 'software-components')));
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
I am fetching Arrays from two endpoints and then set the result in the Local Storage, so I can read from it in other components.
A child component is using the data like this:
const [data, setData] = React.useState<Array<any>>([]);
React.useEffect(() => {
const libraries = getLocalStorageItem('libraries');
const softwareComponents = getLocalStorageItem('softwareComponents');
const condition = libraries && softwareComponents;
if (condition) {
setData([...libraries, ...softwareComponents]);
}
}, []);
const getDataLength = (category: string) => {
return (data || []).filter((item: any) => item.category === category).length;
};
return (
<React.Fragment>
<OwcGrid item xs={12} s={4}>
<LibrariesCard numberOfElements={getDataLength('libraries')} /> // rendering here the length of the localStorage item.
</OwcGrid>
Goal/Challenge:
I want to use React.Context to remove local storage implementation but I am not sure how to keep it as simple as possible.
I only saw guides which implemented dispatch actions and so on but this seems already too complex because I only fetch the data and don't change it as I only render it.
Are there any tipps or guides how to start with this?
Possible implementation with context:
//context.tsx
import {
createContext,
ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
export interface LibsAndComponentsInterface {
data: unknown[];
}
const LibsAndComponentsContext = createContext<
LibsAndComponentsInterface | undefined
>(undefined);
// Wrap your App component with this
export function LibsAndComponentsProvider({
children,
}: {
children: ReactNode;
}) {
const [libs, setLibs] = useState<unknown[]>([]);
const [components, setComponents] = useState<unknown[]>([]);
useEffect(() => {
const fetchData = async () => {
try {
const libraries: unknown[] = await sendRequest('/libraries');
const softwareComponents: unknown[] = await sendRequest(
'/softwareComponents'
);
setLibs(libraries);
setComponents(softwareComponents);
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
const ctxValue = useMemo(
() => ({
data: [...libs, ...components],
}),
[libs, components]
);
return (
<LibsAndComponentsContext.Provider value={ctxValue}>
{children}
</LibsAndComponentsContext.Provider>
);
}
export function useLibsAndComponents() {
const ctx = useContext(LibsAndComponentsContext);
if (ctx == null) {
throw new Error(
'useLibsAndComponents must be inside LibsAndComponentsProvider'
);
}
return ctx;
}
// later in your components
const { data } = useLibsAndComponents()
Here is the complete setup for React Context. Please use typescript if needed.
MyContextProvider.js
const { createContext, useState } = require("react");
//Create a context
export const Mycontext = createContext();
//Created a component that helps to provide the context.
const MyContextProvider = ({ children }) => {
//Declare all the states that you need
const [libraries, setLibraries] = useState([]);
const [softwareComponents, setSoftwareComponents] = useState([]);
return (
<Mycontext.Provider
//provide all the state, function as value that you need in any child component
value={{
libraries,
setLibraries,
softwareComponents,
setSoftwareComponents
}}
>
{children}
</Mycontext.Provider>
);
};
export default MyContextProvider;
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import MyContextProvider from "./MyContextProvider";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
//Wrap App component By the MycontextProvider component
root.render(
<StrictMode>
<MyContextProvider>
<App />
</MyContextProvider>
</StrictMode>
);
App.js
import { useContext } from "react";
import ChildComponent from "./Child";
import { Mycontext } from "./MyContextProvider";
import "./styles.css";
export default function App() {
//This is the way of getting value from context here useContext is the builtin hook and Mycontext is the context name
const { setLibraries, setSoftwareComponents } = useContext(Mycontext);
//After getting data from API use setLibraries and setSoftwareComponents to store data in the state(context) instead of local storage.
return (
<div>
<ChildComponent />
</div>
);
}
Child.js
import { useContext } from "react";
import { Mycontext } from "./MyContextProvider";
const ChildComponent = () => {
const { libraries, softwareComponents } = useContext(Mycontext);
//Here is your state you can use it as your need.
console.log(libraries, softwareComponents);
return <h1>Child Component</h1>;
};
export default ChildComponent;
I am having a very tough time solving this issue
So I have set a context for cart from commerce.js and want to render it on a new page.
I am able to add to cart and all.
The cart object is getting picked in one page, but it to appear in a whole new page so therefore using contexts.
import {createContext, useEffect, useContext, useReducer} from 'react'
import {commerce} from '../../lib/commerce'
//need to correct this file to be a tsx file
const CartStateContext = createContext()
const CartDispatchContext = createContext()
const SET_CART = "SET_CART"
const initialState = {
total_items: 0,
total_unique_items: 0,
line_items: []
}
const reducer = (state,action) => {
switch(action.type){
case SET_CART:
return { ...state, ...action.payload }
default:
throw new Error(`Unknown action: ${action.type}` )
}
}
export const CartProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState)
const setCart = (payload) => dispatch({type: SET_CART, payload})
useEffect(() => {
getCart()
},[])
const getCart = async() => {
try {
const cart = await commerce.cart.retrieve()
setCart(cart)
} catch (error){
console.log("error")
}
}
return (
<CartDispatchContext.Provider value = {{setCart}}>
<CartStateContext.Provider value = {state}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
)
}
export const useCartState = () => useContext (CartStateContext)
export const useCartDispatch = () => useContext (CartDispatchContext)
my _app.tsx
const MyApp = ({ Component, pageProps }: AppProps) => (
<div>
<Web3ContextProvider>
<>
<Component {...pageProps} />
<ToastContainer
hideProgressBar
position="bottom-right"
autoClose={2000}
/>
</>
</Web3ContextProvider>
<CartProvider>
<>
<Component {...pageProps} />
</>
</CartProvider>
</div>
);
export default MyApp;
When I try to render it on the cartPage
import React from "react"
import {CartItem, CartContainer, NavigationBar } from "../components"
import {useRouter} from 'next/router'
import { useCartState, useCartDispatch } from "../context/Cart"
export const CartPage = () => {
const {setCart} = useCartDispatch()
// console.log(setCart)
const {line_items} = useCartState()
// console.log(line_items)
return(
<pre>
{JSON.stringify(line_items,null,2)}
</pre>
)
}
export default CartPage
I get the above Type error
I am not able to figure out what is going wrong. Also if you folks have any other suggestion in next how do i render the cart object in another page
I am using jest and react-testing-library to write tests for my react application. In the application, I have a context provider that contains the state across most of the application. In my tests, I need to mock one state variable in context provider and leave one alone, but I'm not sure how to do this.
AppContext.tsx
const empty = (): void => {};
export const emptyApi: ApiList = {
setStep: empty,
callUsers: empty,
}
export const defaultState: StateList = {
userList = [],
step = 0,
}
const AppContext = createContext<ContextProps>({ api: emptyApi, state: defaultState });
export const AppContextProvider: React.FC<Props> = props => {
const [stepNum, setStepNum] = React.useState(0);
const [users, setUsers] = useState<User[]>([]);
const api: ApiList = {
setStep: setStepNum,
callUsers: callUsers,
}
const state: Statelist = {
userList: users,
step: stepNum,
}
const callUsers = () => {
const usersResponse = ... // call users api - invoked somewhere else in application
setUsers(userResponse);
}
return <AppContext.Provider value={{ api, state}}>{children}</AppContext.Provider>;
}
export default AppContext
In _app.tsx
import { AppContextProvider } from '../src/context/AppContext';
import { AppProps } from 'next/app';
import { NextPage } from 'next';
const app: NextPage<AppProps> = props => {
const { Component, pageProps } = props;
return (
<React.Fragment>
<AppContextProvider>
<Component {...pageProps}
</AppContextProvider>
</React.Fragment>
)
}
export default app
the component that uses AppContext
progress.tsx
import AppContext from './context/AppContext';
const progress: React.FC<Props> = props => {
const { state: appState, api: appApi } = useContext(AppContext);
const { userList, step } = appState;
const { setStep } = appApi;
return (
<div>
<Name />
{step > 0 && <Date /> }
{step > 1 && <UserStep list={userList} /> }
{step > 2 && <Address />
</div>
)
}
export default progress
users is data that comes in from an API which I would like to mock. This data is shown in progress.tsx.
stepNum controls the display of subcomponents in progress.tsx. It is incremented after a step is completed, once incremented, the next step will show.
In my test, I have tried the following for rendering -
progress.test.tsx
import progress from './progress'
import AppContext, { emptyApi, defaultState } from './context/AppContext'
import { render } from "#testing-library/react"
describe('progress', () => ({
const api = emptyApi;
const state = defaultState;
it('should go through the steps', () => ({
state.usersList = {...}
render(
<AppContext.Provider value={{api, state}}>
<progress />
</AppContext.Provider>
)
// interact with screen...
// expect(...)
})
})
However, when I set up the context provider like that in the test, I can set the userList to whatever I want, but it'll also override the setStep state hook so in the component, it won't update the state.
Is it possible to mock only the users variable inside of AppContext with jest, but leave users hook alone?
I have an existential question about react context.
Let's say I have a component that uses useEffect to make call my back-end and get some data. then useState set it inside the context, it works fine, but what if I use useEffect and call my back-end inside the context and then just read data in the component. what you think which one is more "accurate"
for example
old method
context.Js
import React, { useState, useEffect } from "react";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
setData = (data) => { do some logic then setState(data)}
const value = {
...state,
setData
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import React, { useState, useEffect } from "react";
import { SomeContext, SomeProvider } from "context/SomeContext";
import { getById } from "services";
const Layout = ({ someId }) => {
const { data, setData } = useContext(GroupContext);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setData)
}, [])
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
new method
context.Js
import React, { useState, useEffect } from "react";
import { getById } from "services";
const defaultValue = {someState};
export const SomeContext = React.createContext();
export const SomeProvider = ({ someId }) => {
const [state, setState] = useState(defaultValue);
useEffect(()=>{
//getById call my back-end to get some data
getById(someId).then(setState)
}, [])
const value = {
...state,
};
return <SomeContext.Provider value={value}>{children}</SomeContext.Provider>;
};
index.js
import { SomeContext, SomeProvider } from "context/SomeContext";
const Layout = ({ }) => {
const { data } = useContext(GroupContext);
return <Component />
}
export default props => {
const providerProps = {
someId,
};
return (
<GroupProvider {...providerProps}>
<Layout activeTab={activeTab} {...props} />
</GroupProvider>
);
};
something like this
I have the following custom hook called useFlash:
import { useState } from 'react';
export default function useFlash() {
const [messages, setMessages] = useState([]);
const showFlash = (message: string) => {
setMessages([...messages, message]);
};
const clearMessage = (index: number) => {
setMessages(messages.filter((_m, i) => index !== i));
};
return {
messages,
showFlash,
clearMessage
};
}
Then I have this HOC providing it to two other components:
import React from 'react';
import useFlash from '../effects/useFlash';
const withFlash = (WrappedComponent: React.Component) => {
const WithFlash = () => {
const { messages, showFlash, clearMessage } = useFlash();
return (
<WrappedComponent
messages={messages}
showFlash={showFlash}
clearFlashMessage={clearMessage}
/>
);
};
return WithFlash;
};
export default withFlash;
It works well, except each use of the HOC gets its own state data. I need the state to be global. I know I can use contexts with consumer/providers, but I thought this way would be a little simpler. It is not proving to be true, is there a way to make this global?
You'll need to use Context, but it's not that bad..
create your context..
import React, { useState } from 'react';
export const FlashContext = React.createContext();
export const FlashProvider = ({ children }) => {
const [messages, setMessages] = useState([]);
return (
<FlashContext.Provider value={{ messages, setMessages }}>
{children}
</FlashContext.Provider>
);
};
wrap your components in the provider somewhere higher in the tree..
import React from "react";
import { FlashProvider } from "./flash-context";
const App = () => <FlashProvider><TheRest /></FlashProvider>;
export default App;
then use the context in your custom hook..
import React, { useContext } from "react";
import { FlashContext } from "./flash-context";
export default function useFlash() {
const { messages, setMessages } = useContext(FlashContext);
const showFlash = (message) => {
setMessages([...messages, message]);
};
const clearMessage = (index) => {
setMessages(messages.filter((_m, i) => index !== i));
};
return {
messages,
showFlash,
clearMessage
};
}