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;
Related
I have Const like this in my config.service.ts file
export const mysettings={
userid:"12324",
conf:{
sessionDuration:30,
mac:"LON124"
}
}
I am using this constant in some components
But instead of hardcoding those values in const I need to get that at runtime from JSON file in my public folder
So I have a function like this as well
async getConfig(){
const data=await fetch("./data/data.json")
.then((response) => response.json())
.then((json) => return json );
}
So in my data.json files I have those values for the const and I need these values in that JSON file to updated or the JSON file itself sometimes replaced.
Please help me how to accomplish that?
You can create a Context and ContextProvider to load your data from json file, set it to state variable and just pass it to Context consumers:
DataContext.tsx:
import { createContext, useState, useContext, useEffect, PropsWithChildren } from "react";
import asyncData from "./asyncData";
interface IData {
userid: string;
conf: {
sessionDuration: number;
mac: string;
};
}
interface IContextValue {
data: IData;
}
const StateContext = createContext<IContextValue>(null!);
export function DataContextProvider(props: PropsWithChildren) {
const [data, setData] = useState<IData>(undefined!);
useEffect(() => {
asyncData.then((json) => setData(json)).catch(console.error);
}, []);
// Optionally - wrap with useMemo
const contextValue: IContextValue = {
data: data
};
return (
<StateContext.Provider value={contextValue}>
{data && props.children}
</StateContext.Provider>
);
}
export default function useDataContext() {
const context = useContext(StateContext);
if (!context) {
throw new Error(
"useDataContext must be used within the DataContextProvider"
);
}
return context;
}
Updates to index.tsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { DataContextProvider } from "./DataContext";
import App from "./App";
import asyncData from "./asyncData";
asyncData.then((config) => {
console.log("Config is fetched before render: ", config);
const rootElement = document.getElementById("root");
const root = createRoot(rootElement!);
root.render(
<StrictMode>
<DataContextProvider>
<App />
</DataContextProvider>
</StrictMode>
);
});
Usage:
import useDataContext from "./DataContext";
export default function App() {
const { data } = useDataContext();
return <div className="App">{JSON.stringify(data)}</div>;
}
asyncData.ts:
const asyncData = fetch("./data/data.json").then((response) => response.json());
export default asyncData;
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>
);
}
I have this problem, can anyone help me?
TypeError: customers.map is not a function.
I've always used it that way and I've never had any problems.
Its about data integration.
Basically is that, please anyone can help me?
import React, { useState, useEffect } from "react";
import { List, Card } from "antd";
import { data } from "../../../mocks/customers";
import { DeleteCustomerButton } from "#components/atoms/DeleteCustomerButton";
import { CustomersEditButton } from "#components/atoms/CustomersEditButton";
import { useContext } from "../../../contexts/context";
const { Meta } = Card;
const CustomersCardList: React.FC = () => {
const customers: any = useContext();
return (
<div>
{customers.map((customer, key) => { })}</div>)
}
//context.tsx
import * as React from 'react';
import axios from 'axios';
export const AccountContext = React.createContext({});
export const useContext = () => React.useContext(AccountContext);
interface AccounterContextProviderProps {
value: any
};
export const AccounterContextProvider: React.FC<AccounterContextProviderProps> = ({ children, value }) => {
const [customers, setCustomers] = React.useState<any>([]);
React.useEffect(() => {
const getCustomers = async () => {
const result = await axios.get("http://localhost:3333/customers");
setCustomers(result.data);
}
getCustomers();
}, []);
console.log(customers);
return (
<AccountContext.Provider value={{ ...value, customers }}>
{children}
</AccountContext.Provider>
)
};
Any can be anything not only array, so it will not have a map method. Use const customers:any[] = useContext() instead
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
};
}