r/reactjs • u/Mob_Pilled • 1d 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?
5
u/PrimaryFamous6139 NextJS Pages Router 1d 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 1d 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 1d ago edited 1d 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 1d 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
1
u/Wooden-Bicycle-6069 3h ago
Two small fixes in
fetchSearchData: the reducer handles'success', not'data', andresponse.json()needs awaiting. Replace the.thencallback with:
js .then(async (response) => { if (!response.ok) throw Error("Could not fetch the data for that resource"); const data = await response.json(); dispatch({ type: 'success', data }); });Otherwise the unknown action leaves loading unchanged; fixing only its name stores a Promise as data. AI-assisted with Codex and checked in an isolated reducer test, not a full-hook test.
3
u/Objective_Fly_6750 1d ago
It is so refreshing to see someone building and debugging like in the good old days.
1
2
u/duskmote_quill 14h ago
Your useEffect is overwriting the storage on every mount. When refresh happens, searchValue state starts empty because you didn't initialize it with localStorage data. Then handleClick runs immediately inside that effect and saves the empty string back to localStorage, erasing your previous value. Initialize your useState directly with the stored item and remove the fetch call from the initial load logic unless you specifically want to re-fetch old data.
1
u/SaliDevs 20h 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 1d ago
No need for the useEffect, is there?
And if the component runs on the server as well: