Set dynamic values for properties in a a constant object react - reactjs

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;

Related

Trigger toast from outside React component

I am using PrimeReact's toast component, whose API looks like this:
function App() {
const toast = useRef(null);
useEffect(() => {
toast.current.show({
severity: 'info',
detail: 'Hellope'
});
});
return (
<div className='App'>
<Toast ref={toast} />
</div>
);
}
I would now like to call toast.current.show() from a non-React context. In particular, I have an http() utility function through which all HTTP calls are made. Whenever one fails, I would like to show a toast. What are clean/idiomatic ways to achieve this?
Initialize the toast on the window object.
useLayoutEffect(() => {
window.PrimeToast = toast.current || {};
}, []);
On your fetch or axios handler, use the above object on your error handler
const fakeUrl = "https://api.afakeurl.com/hello";
fetch(fakeUrl)
.then((res) => res.data)
.catch((err) => {
console.error("error fetching request", err);
if (window.PrimeToast) {
window.PrimeToast.show({
severity: "error",
summary: "Error calling https",
detail: "hello"
});
}
});
Updated Sandbox
Reference:
https://www.primefaces.org/primereact/toast/
I would create a toast context that would allow showing toasts
toast-context.js
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.css";
import { Toast } from "primereact/toast";
import { createContext, useContext, useRef } from "react";
// create context
const ToastContext = createContext(undefined);
// wrap context provider to add functionality
export const ToastContextProvider = ({ children }) => {
const toastRef = useRef(null);
const showToast = (options) => {
if (!toastRef.current) return;
toastRef.current.show(options);
};
return (
<ToastContext.Provider value={{ showToast }}>
<Toast ref={toastRef} />
<div>{children}</div>
</ToastContext.Provider>
);
};
export const useToastContext = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error(
"useToastContext have to be used within ToastContextProvider"
);
}
return context;
};
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { ToastContextProvider } from "./toast-context";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<ToastContextProvider>
<App />
</ToastContextProvider>
</StrictMode>
);
App.js
import { useToastContext } from "./toast-context";
export default function App() {
// use context to get the showToast function
const { showToast } = useToastContext();
const handleClick = () => {
http(showToast);
};
return (
<div className="App">
<button onClick={handleClick}>show toast</button>
</div>
);
}
// pass showToast callback to your http function
function http(showToast) {
showToast({
severity: "success",
summary: "Success Message",
detail: "Order submitted"
});
}
Codesanbox example: https://codesandbox.io/s/beautiful-cray-rzrfne?file=/src/App.js
Here is one solution I have been experimenting with, although I have the impression it isn't very idiomatic. I suppose one could look at it as a "micro-frontend" responsible exclusively for showing toasts.
import ReactDOM from 'react-dom/client';
import { RefObject, useRef } from 'react';
import { Toast, ToastMessage } from 'primereact/toast';
class NotificationService {
private toast?: RefObject<Toast>;
constructor() {
const toastAppRoot = document.createElement('div');
document.body.append(toastAppRoot);
const ToastApp = () => {
this.toast = useRef<Toast>(null);
return <Toast ref={this.toast} />;
};
ReactDOM.createRoot(toastAppRoot).render(<ToastApp />);
}
showToast(message: ToastMessage) {
this.toast!.current!.show(message);
}
}
export const notificationService = new NotificationService();
The simplicity of its usage is what's really nice of an approach like this. Import the service, call its method. It used to be that simple.

React.Context refers to a UMD global

I created the following context.tsx:
import { createContext } from 'react';
export interface LibsAndComponentsInterface {
data: unknown[];
}
export const LibsAndComponentsContext = createContext<
LibsAndComponentsInterface | undefined
>(undefined);
And use it in my Wrapper component:
import { ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import { LibsAndComponentsContext } from 'core/context/dataContext';
import { useApiService } from 'common/hooks/useApiService';
import { useAuth } from 'common/contexts/Authentication';
// Wrap your App component with this
export function LibsAndComponentsProvider({
children,
}: {
children: ReactNode;
}) {
const [libs, setLibs] = useState<unknown[]>([]);
const [components, setComponents] = useState<unknown[]>([]);
const { isAuthenticated } = useAuth();
const { sendRequest } = useApiService();
useEffect(() => {
.....
}, []);
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;
}
I get now an error returned for <LibsAndComponentsContext.Provider value={ctxValue}>
'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.
'React' must be in scope when using JSX
I do not see where I am referencing as a module, could somebody point me to the error?
Adding import React from "react"; was the embarrassing solution.

React Context for Array passing

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;

How to navigate between screens in react without usage of libraries like react router

I want to know if is possible to navigate between screens, using like a context api, or something else, where I can get the "navigateTo" function in any component without passing by props. And of course, without the cycle dependency problem.
Example with the cycle dependency problem
NavigateContext.tsx:
import React, { createContext, useMemo, useReducer } from 'react'
import { Home } from './pages/Home'
interface NavigateProps {
navigateTo: (screenName: string) => void
}
export const navigateContext = createContext({} as NavigateProps)
const reducer = (state: () => JSX.Element, action: { type: string }) => {
switch (action.type) {
case 'home':
return Home
default:
throw new Error('Page not found')
}
}
export function NavigateContextProvider() {
const [Screen, dispatch] = useReducer(reducer, Home)
const value = useMemo(() => {
return {
navigateTo: (screenName: string) => {
dispatch({ type: screenName })
},
}
}, [])
return (
<navigateContext.Provider value={value}>
<Screen />
</navigateContext.Provider>
)
}
Home.tsx:
import React, { useContext, useEffect } from 'react'
import { Flex, Text } from '#chakra-ui/react'
import { navigateContext } from '../NavigateContext'
export function Home() {
const { navigateTo } = useContext(navigateContext)
useEffect(() => {
setTimeout(() => {
navigateTo('home')
}, 2000)
}, [])
return (
<Flex>
<Text>Home</Text>
</Flex>
)
}
Yes, this is possible, but you'll need to maintain the list of string view names independently from your mapping of them to their associated components in order to avoid circular dependencies (what you call "the cycle dependency problem" in your question):
Note, I created this in the TS Playground (which doesn't support modules AFAIK), so I annotated module names in comments. You can separate them into individual files to test/experiment.
TS Playground
import {
default as React,
createContext,
useContext,
useEffect,
useState,
type Dispatch,
type ReactElement,
type SetStateAction,
} from 'react';
////////// views.ts
// Every time you add/remove a view in your app, you'll need to update this array:
export const views = ['home', 'about'] as const;
export type View = typeof views[number];
export type ViewContext = {
setView: Dispatch<SetStateAction<View>>;
};
export const viewContext = createContext({} as ViewContext);
////////// Home.ts
// import { viewContext } from './views';
export function Home (): ReactElement {
const {setView} = useContext(viewContext);
useEffect(() => void setTimeout(() => setView('home'), 2000), []);
return (<div>Home</div>);
}
////////// About.ts
// import { viewContext } from './views';
export function About (): ReactElement {
const {setView} = useContext(viewContext);
return (
<div>
<div>About</div>
<button onClick={() => setView('home')}>Go Home</button>
</div>
);
}
////////// ContextProvider.tsx
// import {viewContext, type View} from './views';
// import {Home} from './Home';
// import {About} from './About';
// import {Etc} from './Etc';
// Every time you add/remove a view in your app, you'll need to update this object:
const viewMap: Record<View, () => ReactElement> = {
home: Home,
about: About,
// etc: Etc,
};
function ViewProvider () {
const [view, setView] = useState<View>('home');
const CurrentView = viewMap[view];
return (
<viewContext.Provider value={{setView}}>
<CurrentView />
</viewContext.Provider>
);
}

query inside/outside React context

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

Resources