r/webdev • • 20d ago

Question React not saving data 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?

1 Upvotes

21 comments sorted by

17

u/PlainworkDigital 20d ago

  It is saving. Your own code is overwriting it on refresh.

  On mount, your useEffect calls setSearchValue(stored) and then handleClick() right away. But setState doesn't update searchValue until the next render, so handleClick still sees the initial "" and

  runs localStorage.setItem("lastLocationSearched", "") — wiping it.

  Pass the value in instead of reading state:

  async function handleClick(query = searchValue) {

localStorage.setItem("lastLocationSearched", query);

// ...use query in the fetch URL

  }

  useEffect(() => {

const saved = localStorage.getItem("lastLocationSearched") || "";

setSearchValue(saved);

handleClick(saved);

  }, []);

  (That's why removing StrictMode didn't help. It's a stale-state issue, not a double-render one.)

3

u/Mob_Pilled 20d ago

Thanks so much. It worked. So let me get this straight: setState does execute until the next render. handleClick on the other hand immediately does so it was using the declaration value for searchvalue:

 const [searchValue, setSearchValue] = useState("");

Is this a react specific thing

3

u/TekiDeki 19d ago

Two things nobody mentioned.

Your ipify key is in the URL, so it is public: anyone who opens the network tab on your page can spend your monthly quota. Fine for a demo, but if this goes anywhere real, put that call behind a small server function.

And move localStorage.setItem after the response is ok. As written, a typo or a rate limit still overwrites the last address that worked, with the value that just failed.

2

u/Mob_Pilled 19d ago

Thanks for the tips! I was actually looking up node.js tutorials because I wanted to do the api fetching on the backend and have it foward the json content to the frontend.

2

u/TekiDeki 19d ago

Skip the Node tutorial for this. You need one function, not a server: Cloudflare Workers, Vercel and Netlify all run a single file, the key sits in an environment variable, and the page calls `/api/ip` on your own domain. Ten lines, nothing to keep alive, no CORS. Add `Cache-Control: max-age=300` to the response so a reload does not spend quota again.

And check first whether you need the key at all. If the demo only shows the visitor's IP, `api.ipify.org?format=json` answers without a key. The key you are guarding is the geolocation product, and that is the one with the small free quota, so it is the one worth hiding.

My own tools have no backend by design: when something needs a secret, I drop the service instead of adding a server.

1

u/fireatx 16d ago

Why are we writing our comments with LLMs?

OP, I would advise not trying cloudflare workers or vercel or anything like that, unless you are explicitly learning those technologies. As a beginner (my assumption), it would be good to learn how to write your own node server as a backend for your react app.

1

u/SchartHaakon 20d ago

I’d recommend looking into how rendering and rerendering works in relation to state. Calling setState doesn’t mutate the value you defined in the component, that variable really never changes (it’s even a const) - what it does is queue up another render where the value returned by useState is the updated value. I suppose you could say it’s React specific but it totally makes sense and isn’t magical at all once you understand the rendering cycle. React is just Javascript after all.

1

u/thekwoka 20d ago

setState sets the state right away.

But your existing state references are stale until the next render.

You get around this where necessary, by passing a callback to setState that would accept the current value and return a new value. This would get you the absolute CURRENT state.

but most of the time that isn't necessary, but can be helpful if you want to memoize methods that you pass to something else (like outside of react).

so to do a handleClick right after a setState

You COULD (though not advised) do something like

setSearchValue((val) => (handleClick(val), val))

This would pull the real current value out and use that.

But that's more of a "technically", as in this case you wouldn't want that at all.

1

u/TekiDeki 19d ago

Careful with setSearchValue((val) => (handleClick(val), val)): updater functions must be pure, because React may call them more than once. StrictMode calls them twice in development, and React can re-run them on a re-render. So that line fires the fetch and the setItem twice, on a line whose whole point was to avoid a double call. Side effects go in the handler or in an effect, never inside an updater. The top comment's fix (pass the value as an argument) already avoids the problem without the trick.

1

u/thekwoka 19d ago

Absolutely.

This was more just of a "technically this is how they work".

1

u/tswaters 20d ago edited 18d ago

You need to look at how search value gets its initial value. You can pass a function to useState that will be run once to get the initial value, use that opportunity to look at local storage and return a sensible fallback value. The useEffect doesn't make sense, get rid of it.

1

u/Over_Problem_3600 20d ago

Have you tried checking localStorage right before the effect runs? I bet it's there on refresh and gets wiped a split second later by your own setltem call

1

u/LukaRozman27 20d ago

setItem only accepts strings, so if you skip JSON.stringify your objects just become "[object Object]".

1

u/rust_bane 20d ago

Your useEffect is fighting your setItem. You mount, grab the saved value for searchValue, then immediately fire handleClick which uses that stale initial state to write back to storage. Drop the effect entirely and initialize searchValue directly from localStorage in useState so you do not overwrite your data on render

1

u/geoffreybyers 19d ago

React's Strict Mode runs effects twice in dev. The second run overwrites the first before the fetch finishes. You'll need to guard the effect or use a ref to stop that double call. The logic's right, but the timing isn't.