How to update a component by changing localstorage from another component?
for example with react hooks I want to call a function by changing localstorage value, but doesn't work:
React.useEffect(() => {
//call a function by changing id value in localstorage
}, [localStorage.getItem("id")])
You need to use ContextProvider to share same hooks and data between different components.
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const MyContext = React.createContext();
const useMyHookEffect = (initId) => {
const [id, setId] = useState(initId);
const saveId = (id) => {
window.localStorage.setItem('id', id);
setId(id);
};
useEffect(() => {
//Now you can get the id from the localStorage
const myId = window.localStorage.getItem('id');
setId(myId);
}, []);
return { id, saveId };
};
// Provider component that wraps app and makes themeMode object
export function MyHookProvider({ children, id }) {
const myEffect = useMyHookEffect(id);
return (
<MyContext.Provider value={myEffect}>
{children}
</MyContext.Provider>
);
}
MyHookProvider.defaultProps = {
children: null,
id: null,
};
MyHookProvider.propTypes = {
children: PropTypes.node,
id: PropTypes.string,
};
export const useMyHook = () => useContext(MyContext);
And you need to call it as provider outside of your components.
<MyHookProvider>
<ComponentA />
<ComponentB />
</MyHookProvider>
Now you can use shared hook between your components.
export function ComponentA(){
const { id, saveId } = useMyHook(null);
return (<div>{id}<button onClick={() => saveId(2)}></button></div>);
}
You can use window.addEventListener('storage ...
React.useEffect(() => {
function example() {
//call a function by changing id value in localstorage
}
window.addEventListener('storage', example)
return () => window.removeEventListener('storage', example)
} , [ ])
inside example you might check that id is the localStorage piece make the function run
As you want to re-render an element you should use states. By using states every element using this variable will automatically update. You can use the hook useState().
import React, { useState, useEffect } from 'react';
const [ id, setId ] = useState(initalValue);
useEffect(() => {
setId(localStorage.getItem('id'));
}, [localStorage.getItem('id')]);
return(
'Your code and the element that should update'
);
Related
Trying to get the API functional component that updates Context values in useEffect to execute within another App functional component (on import) when mounted to allow render when values exist/update.
The API function at API.js appears to be working correctly as a stand alone component. The problem (due to lack of understanding) is upon the import to App.js where the API function should execute.
API.js
import { useState, useContext, useEffect } from 'react'
import { AppContext } from './contexts/AppContext';
export const API = () => {
const {updateOrder, updateCustomer} = useContext(AppContext);
useEffect(() => {
const fetchData = async () => {
const orderDetails = await fetch('url.com/order');
const customerDetails = await fetch('url.com/customer');
updateOrder(await orderDetails.json())
updateCustomer(await customerDetails .json())
}
fetchData();
}, [])
}
App.js
import React, { useContext, useEffect } from 'react';
import { AppContext } from './contexts/AppContext';
import { API } from './components/API';
const App = ({ isLoading }) => {
const {order, customer} = useContext(AppContext);
useEffect(() => {
isLoading && <API />
}, []) // eslint-disable-line react-hooks/exhaustive-deps
if (order && customer) {
return <SomeComponent/>
}
}
The expected outcome is to be able to use API within the initial mount (as API dependency) and conditionally return/render content in App.
I've tried changing the API component into function and exporting with default, however context is not supported outside of component.
A component is a function that renders something in the UI. You API component is not really a component - it doesn't have any branch that returns any JSX - although that's not always required.
You should look into building a custom hook instead. Try something like this:
App.js
import React, { useContext, useEffect } from "react";
import { AppContext } from "./contexts/AppContext";
// Hooks should start with 'use'
export const useAPI = () => {
const { updateOrder, updateCustomer } = useContext(AppContext);
useEffect(() => {
const fetchData = async () => {
// Fetching data from both endpoints in parallel
const res = await Promise.all([fetch("url.com/order"), fetch("url.com/customer")]);
// Converting both payloads to JSON in parallel
const data = await Promise.all([res[0].json(), res[1].json()]);
updateOrder(data[0]);
updateCustomer(data[1]);
};
fetchData();
}, []);
};
const App = ({ isLoading }) => {
// Calling the custom hook
useAPI();
const { order, customer } = useContext(AppContext);
if (order && customer) {
return <SomeComponent />;
}
};
I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes
I have created one wrapper component where I put my react context.
Inside that wrapper component I have used useEffect() hook where I fetch values from api and try to update context default values.
In my child component I tried to fetch context values but only default value of that context is fetched. So it seems that useEffect hook didnt updated my context object.
Here is wrapper component:
export const CorporateWrapper = ({ apiBaseUrl, children }) => {
const [corporateContextDefaults, setCorporateContextDefaults] = useState({});
useEffect(() => {
(async () => {
try {
const json = await fetchCorporateUserDetails(apiBaseUrl, getClientSideJwtTokenCookie());
if (json.success !== true) {
console.log(json.message);
return {
notFound: true,
};
}
console.log(json.data);
setCorporateContextDefaults({corporateId: json.data.corporate_id, corporateRole: json.data.corporate_role, corporateAdmin: json.data.corporate_role == 'Admin', corporateSuperAdmin: json.data.corporate_super_admin});
} catch (e) {
console.log(e.message);
}
})();
}, []);
return (
<CorporateProvider value={corporateContextDefaults}>
{children}
</CorporateProvider>
);
};
Here is CorporateProvider component:
import React, { useState, useContext } from "react";
const CorporateContext = React.createContext({corporateId: null, corporateRole: null,
corporateAdmin: null, corporateSuperAdmin: null});
const UpdateCorporateContext = React.createContext({});
export const useCorporateContext = () => {
return useContext(CorporateContext);
};
export const useUpdateCorporateContext = () => {
return useContext(UpdateCorporateContext);
};
export const CorporateProvider = ({ value, children }) => {
const [details, setDetails] = useState(value);
return (
<CorporateContext.Provider value={details}>
<UpdateCorporateContext.Provider value={setDetails}>
{children}
</UpdateCorporateContext.Provider>
</CorporateContext.Provider>
);
};
export default CorporateProvider;
Here is how I try to fetch context value from child component which is wrapped under wrapper component:
const { corporateId } = useCorporateContext();
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}
i send dataSource parameter to flowing functional component, dataSource has data but chartOptions state can not set.
thanks...
import React, { useEffect, useState } from "react";
const Trend = ({ dataSource }) => {
const [chartOptions, setChartOptions] = useState({
series: {
data: dataSource.map(x => {
return ["TEST1", "TEST2"];
})
}
});
console.log(chartOptions);
return (
<div>
<h1>TEST</h1>
</div>
);
};
export default Trend;
You should set it as this, as it sets state before the dataSource arrives.
Try using useEffect and set the state there like
useEffect(() => {
const data = dataSource.map(x => {
return ["TEST1", "TEST2"];
});
setChartOptions(
series: {
data: data
}
);
},[dataSource]);
To calculate the value of your own state from a prop you should use useEffect and include this prop in the hook useEffect within the dependency array so that whenever it changes the value of the state is updated.
Yo can see it in the React documentation, useEffect hook
This could be an implementation:
import React, { useEffect, useState } from "react";
const App = ({ dataSource }) => {
const [chartOptions, setChartOptions] = useState({});
useEffect(() => {
setChartOptions({
series: {
data: dataSource.map(x => {
return ["I'm ", "test2"];
})
}
});
}, [dataSource]);
return (
<div>
<h1>
{chartOptions.series &&
chartOptions.series.data.map(chartOption => <div>{chartOption}</div>)}
</h1>
</div>
);
};
export default App;
Here's an example
PD: If you want a more extensive explanation about useEffect (it is quite complex) and where you will solve doubts about updating the state through props, etc I attach an article by one of the developers of React that I think is very interesting.
Lazy initial state -
If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:
const [state, setState] = useState(() => {
const initialState = {
series: {
data: dataSource.map(x => {
return ["TEST1", "TEST2"];
})
}
}
return initialState;
});