React: How to store array when the page refreshes - javascript

I have a array in React which is to be shown using useEffect. That thing got from the previous component to next component. Now, I am using useEffect to set the array on page load using useState. But when I refreshes the page, the array gets undefined and the page goes blank. How to get the array again, so that it never throws me error even if I reload the page.
I have used sessionStorage and localStorage. But nothing is working out for me.
This is the code I have done so far:
const location = useLocation();
useEffect(() => {
console.log(location.groupsname);
sessionStorage.setItem('groups', JSON.stringify(location.groupsname));
const a = JSON.parse(sessionStorage.getItem('groups'));
console.log(a);
setimportedgroups(a);
}, []);
How should I resolve that?. Thank you.

Issue
You are close. The issue here is that you are wiping out any saved storage value when the component mounts again.
useEffect(() => {
// setting storage is synchronous
sessionStorage.setItem('groups', JSON.stringify(location.groupsname));
// getting the value just set
const a = JSON.parse(sessionStorage.getItem('groups'));
console.log(a);
setimportedgroups(a);
}, []);
If, upon page reload, location.groupsname is undefined then this is what is stored and loaded back from storage.
Solution
You'll want to use a state initializer function and two useEffect hooks, 1 to persist to storage local state when it updates, and a second to update local state when the location groupsname updates and has a defined value.
const location = useLocation();
// initialize state from storage on mount
const [importedGroups, setImportedGroups] = useState(() => {
const groups = sessionStorage.getItem('groups');
return JSON.parse(groups) ?? {};
});
// persist importedGroups state to storage
useEffect(() => {
sessionStorage.setItem('groups', JSON.stringify(importedGroups));
}, [importedGroups]);
// update local state when location.groupsname updates
useEffect(() => {
if (location.groupsname) {
setImportedGroups(location.groupsname);
}
}, [location.groupsname]);

Related

React: Mapping a component with local storage data

I'm currently creating a history list component for a form in a react app and am having some trouble with the local storage.
Essentially, I want the app to render a list of past inputs from the user's local storage data. My current idea is based in duplicating the local storage data is a state variable.
const [history, setHistory] = useState([]);
On form submit I call this function with the form input as the parameter (the input is a single string)
const setLocalStorage = (input) => {
const hist = JSON.parse(localStorage.getItem('history')) || [];
console.log(hist)
hist.push(input)
localStorage.setItem('history', JSON.stringify(hist))
setHistory(hist);
}
This is meant to put the history from local story into hist, push the input that was just submitted into the existing array hist, and update the local storage with the new value. The state variable should then be updated with the most updated array of strings with the setHistory(hist) call.
Also, I want local storage to be pulled on first render so I can use that data to render the history list on initial load. I have a useEffect hook for this as shown:
useEffect(() => {
setHistory(JSON.parse(localStorage.getItem('history')))
console.log(history)
}, []);
The problem I'm facing is that the state never seems to get updated? I can instead do a console log for JSON.parse(localStorage.getItem('history')) and get the local storage array returned but this of course isn't helpful for data usage. I know that the local storage is properly being pulled from this but I'm unable to update the state for some reason. I need the state updated so I can conditionally render and use the array for mapping each item on the history list. When I console log "history" I get an empty array.
TL;DR
Concisely, what is the cleanest method to have local storage and state values maintain equivalency? Hope my post was clear enough to understand!
I'm remaking and updating a regular JS app on React for practice so I'm able to provide a live link of how I want this simple component to work.
https://giovannimalcolm.github.io/weather-dashboard/
The second returned parameter of useState is similar to the this.setState which is asynchronous. You may see that state is not changed even setHistory is called. Passing function instead of the value will avoid this issue as it will be executed after the state is updated. This might be useful for better understanding Passing function to setState()
useEffect(() => {
const hist = JSON.parse(localStorage.getItem('history'))
setHistory(prevHistory => [...prevHistory, ...hist])
}, []);

Trouble chaining useEffects to use updated state from previous effects

I have an application that has some complex data fetching. Overall, here is a snapshot of the logic in my application
// dep1 is from redux, dep2 is local state
// useEffect 1
useEffect(() => {
// perform some state variable update to dep2
}, [dep1]);
// useEffect 2
useEffect(() => {
// use some values from deps to fetch data
}, [dep1, dep2]);
The issue I am facing is that when dep1 and/or dep2 update, the state change from useEffect 1 needs to reflect in the request url of the data fetching operation in useEffect 2. useEffect 2 ends up running twice, once with the dep1 update (without the dep2 update from useEffect 1 in the url) and once with the dep2 update. This issue is not specifically noticeable in most cases where we are just rendering, but we end up with double api fetches in cases where data fetching is used in the useEffect. What is some strategy that I can use to circumvent this double API call?
EDIT
Adding more code to allow more specifity for problem:
// useEffect 1
// when the user is changed (user is a prop that is from redux),
// option should be reset to "DEFAULT"
useEffect(() => {
setOption("DEFAULT");
}, [currentUser]);
// useEffect 2
// option is a value that can be set within the UI and is local state.
// setting option to a new value will trigger api call with new value
useEffect(() => {
const data = await getData(option);
}, [currentUser, option]);
The issue when option is not "DEFAULT" and currentUser changes, useEffect 2 will run twice. I would like to find some logic to allow it to run once with option set back to "DEFAULT" if currentUser changed. Is this possible using other react patterns, since it doesn't seem possible with useEffect?
I think you are complicating it alot. I would suggest using a single effect hook and compute the logic and perform data fetching in the same effect.
/ dep1 is from redux, dep2 is local state
// useEffect 1
useEffect(() => {
// perform some state variable update to dep2
// perform data fetching here and if you want to check some condition for dep2 you could do that here as well.
}, [dep1]);
Try setting dep2 inside a react component within the current component and passing it as a prop, so that you only have one useEffect in each component, with state being passed up.
const component = () => {
useEffect(() => {
// do something
}, [dep1]);
return (
<div> <ComponentTwo depTwo={depTwo}/> </div>
)
}
then in ComponentTwo ...
const componentTwo = ({ depTwo }) => {
// useEffect 2
useEffect(() => {
// use some values from deps to fetch data
}, [dep2]);
return (<div> something </div>
)
}
You'll need to import ComponentTwo inside the parent component.

How do I make UseEffect hook run step by step and not last?

I have UseEffect hook that fetches data from DB and I want it to run FIRST in my component, but it runs last.
How do I make it run before "console.log(titleee)"?
Code:
const [cPost, setCPost] = useState([]);
const postId = id.match.params.id;
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setCPost(posts.data);
console.log("test");
})
}, []);
const titleee = cPost.title;
console.log(titleee);
I don't think that's the correct path that you want to take.
In order to show the cPost on your page after the request /posts/+postId finished you can opt-out for two following options.
You can show a "loader" to the user if the cPost data is crucial for your whole component.
const [fetchingCPost, setFetchingCPost] = useState(false)
const [cPost, setCPost] = useState({});
const postId = id.match.params.id;
useEffect(() => {
setFetchingCPost(true)
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setFetchingCPost(false)
setCPost(posts.data);
})
}, []);
return fetchingCPost && <div>Loading</div>
Or you can have some default values set from the start for cPost. Just to make sure that your code doesn't break. I think the first solution might be more UX acceptable.
const [cPost, setCPost] = useState({title: '', description: ''});
If you want to store title as a separate variable you can use useMemo for instance or do it via useState same as with cPost. But even then you can't "create" it after the request finishes, you can simply change its value.
In case you want to use useMemo you can make it dependent on your cPost.
const cPostTitle = useMemo(() => {
return !!cPost.title ? cPost.title : ''
}, [cPost])
You have to change you'r way of thinking when programming in react. It is important to know how react works. React does not support imperative programming, it rather support declarative and top down approach , in which case you have to declare your markup and feed it with you'r data then the only way markup changes is by means of changing you'r data. So in you'r case you are declaring a watched variable using useState hook const [cPost, setCPost] = useState([]); , this variable (cPost) has initial values of [] then react continues rendering you'r markup using initial value, to update the rendered title to something you get from a network request (eg: a rest API call) you use another hook which is called after you'r component is rendered (useEffect). Here you have chance to fetch data and update you'r state. To do so you did as following :
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setCPost(posts.data);
})
}, []);
this code results in a second render because part of data is changes. Here react engine goes ahead and repaint you'r markup according data change.
If you check this sandbox you'll see two console logs , in first render title is undefined in second render it's something we got from network.
Try adding async/await and see if it works. Here is the link btw for your reference https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435

React ı cant reach current state

React.useEffect(
() => {
const users = [
{id:123123123,
name:"mert",
del: <deleteButton onClick={()=>console.log(users)} />
}]
setusers(users)
}, [])
hello i have a problem
I created a useState called [users, setusers]
and in useeffect, I have assigned a delete function for each user.
this delete function needs to reach users state
But the problem starts here. When I want to reach users through the delete function,
I get the initial state of the state, (the state at the time I created the first user object)
In your useEffect hook you define a const variable called users.
In the onClick Event you output the const variable users and not the state variable users.
Which means const users is not equal to state.users
Rename your const variable 'users' and see if anything changes.

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

Categories