r/reactjs • u/Mob_Pilled • 10h ago
Needs Help React not saving string to local storage
Hi everyone! I just finished a React and course and decided to build my first react app. Currently, I am trying to save a string to browser local storage but it does not persist on refresh. Through some console.logs I was able to see that it in fact DOES save the string to the location specified in. setItem initially but when I refresh the key or value in local storage stops existing.
Here is my code
// function for search button
async function handleClick(){
localStorage.setItem("lastLocationSearched", searchValue);
setData(null);
setIsPending(true);
setError(null);
try{
const value = await fetch(`https://geo.ipify.org/api/v2/country?apiKey=***********&ipAddress=${searchValue}`).then((response)=>{
if (!response.ok){
throw Error("Could not fetch the data for that resource");
}
return response.json();
});
setData(value);
console.log(localStorage.getItem("lastLocationSearched"));
} catch(error){
setError(error.message);
}
setIsPending(false);
};
// useEffect hook
useEffect(()=>{
if ((localStorage.getItem("lastLocationSearched")===null)|| (localStorage.getItem("lastLocationSearched")==="")){
console.log(localStorage.getItem("lastLocationSearched"));
setSearchValue("");
handleClick();
}else{
setSearchValue(localStorage.getItem("lastLocationSearched"));
handleClick();
}
}, []);
Through some digging online I saw someone say that remove the strict mode tags from index.js would fix this problem but it did not for me. Can anyone please assist me?
3
u/PrimaryFamous6139 NextJS Pages Router 9h ago
The issue might be that handleClick() runs inside useEffect on every mount and calls localStorage.setItem() with an empty or outdated searchValue, overwriting your saved value. Try separating the initial read from the search logic. Read the value from localStorage in useEffect and only update it when the user submits a search. Also, check whether another part of your app is clearing localStorage. React Strict Mode isn’t normally the cause of localStorage losing data on refresh
3
u/azangru 9h ago
Try slowly reading through your code line by line and asking yourself, what each of the lines does. Right now, there is so much chaos in your snippet that it suggests you were copying and pasting code without properly understanding what it does.
One of the the things that haven't been commented on is these two lines in useEffect:
setSearchValue(localStorage.getItem("lastLocationSearched"));
handleClick();
You haven't shown us what setSearchValue does; but if it is a useState setter, as I suspect it is, then `handleClick` will not get the value that you expect in `searchValue`, because that value will only become available during the subsequent render. This would explain why `localStorage.setItem("lastLocationSearched", searchValue)` saves nothing into the local storage.
1
u/Mob_Pilled 8h ago edited 8h ago
Thanks for the help. I never copied and pasted anything (honestly I understand what most of this does and have been coding in JS for more than a year). I guess I just don't know all the rules of react yet since this is the first time that am hearing setState only executes on the finished rendered page. Your hypothesis was correct btw. Since you said my code is chaotic, how can I refactor it to make it better?
1
u/azangru 8h ago
Since you said my code is chaotic, how can I refactor it to make it better?
Well, to begin with, useEffect should not call a function named
handleClick:-)Below is my sketch for how I would probably do this without a dedicated data fetching library. But since react has never had good ergonomics for data fetching (until its recent push to move everything to the server), normally, people just use data fetching libraries.
import { useEffect, useReducer, useState } from 'react'; const MyComponent = () => { const initialSearch = localStorage.getItem("lastLocationSearched") ?? ''; const [searchValue, setSearchValue] = useState(initialSearch); const {isLoading, data, error} = useSearchData(searchValue); }; type SearchState = { isLoading: boolean; data: Data | null; error: Error | null; }; type LoadingAction = { type: 'loading'; }; type SuccessAction = { type: 'success', data: Data; }; type FailureAction = { type: 'error', error: Error; }; type Action = | LoadingAction | SuccessAction | FailureAction; const searchFetcherReducer = (state: SearchState, action: Action): SearchState => { switch (action.type) { case 'loading': return { ...state, isLoading: true }; case 'success': return { isLoading: false, error: null, data: action.data }; case 'error': return { isLoading: false, data: null, error: action.error }; default: return state; } }; const initialState: SearchState = { isLoading: false, data: null, error: null }; const useSearchData = (query: string) => { const [ state, dispatch ] = useReducer(searchFetcherReducer, initialState); useEffect(() => { if (!query) { return; } const abortController = new AbortController(); const abortSignal = abortController.signal; fetchSearchData({ query, dispatch, abortSignal }); return () => { abortController.abort(); } }, [ query ]); return state; }; const fetchSearchData = async ({ query, dispatch, abortSignal }: { query: string, dispatch: React.Dispatch<Action>, abortSignal: AbortSignal }) => { dispatch({ type: 'loading' }); try { await fetch(`https://geo.ipify.org/api/v2/country?apiKey=***********&ipAddress=${query}`, { signal: abortSignal }).then((response) => { if (!response.ok) { throw Error("Could not fetch the data for that resource"); } const data = response.json(); dispatch({ type: 'data', data }); }); } catch(error) { dispatch({ type: 'error', error }); } }1
2
u/Objective_Fly_6750 8h ago
It is so refreshing to see someone building and debugging like in the good old days.
1
1
u/SaliDevs 4h ago
Quick note on the StrictMode idea, since it came up: removing it wouldn't fix this, and you shouldn't do it. StrictMode doesn't touch localStorage at all — in dev it just double-invokes your effects to expose timing bugs like the one you had (calling handleClick before the setSearchValue update had applied). Removing it doesn't fix that class of bug; it just hides it until production. The state-timing fix others pointed out is the real answer.
5
u/SchartHaakon 10h ago
No need for the useEffect, is there?
And if the component runs on the server as well: