DEV Community

Manually updating a list in localstorage or using useEffect, which is better?

Pratyush Srivastava on July 16, 2025

My First Dev.to Post — React TODO App with LocalStorage I was brushing up on my React knowledge and thought of making a small project. I...
Collapse
 
brense profile image
Rense Bakker •

You created the extra render cycle yourself, because of the first side effect that you use to set the initial state. You can get rid of that by providing a function to useState to retrieve the initial state from local storage:
joshwcomeau.com/react/persisting-r...

Some general advice: You should really be careful what you use side effects for, using them incorrectly can create very serious bugs and performance issues in your app.

Collapse
 
prs-dev profile image
Pratyush Srivastava •

Thanks for the suggestion, will certainly look in the side effects, so that I'll be more careful next time.

Collapse
 
chilledgrill24 profile image
Chilled grily •

Hello dev

Thread Thread
 
prs-dev profile image
Pratyush Srivastava •

Hey

Thread Thread
 
chilledgrill24 profile image
Chilled grily •

I am up 24hours man

Thread Thread
 
prs-dev profile image
Pratyush Srivastava •

why ??

Collapse
 
link2twenty profile image
Andrew Bone • • Edited

You might be able to find a hook on NPM to handle the syncing of storage for you, like @blocdigital/uselocalstorage, which may make your life a little easier.

Here's a quick example of how it would fit in with your example (though I've not tested it).

import { useState, useEffect } from 'react';
import useLocalStorage from '@blocdigital/uselocalstorage';

export default function ToDo () {
  const storage = useLocalStorage('session');
  const [list, setList] = useState(storage.get('todos') || []);

  /**
  * On submit add a new task
  */
  const handleSubmit = e => {
    e.preventDefault();
    storage.set("todos", [...list, {
      id: Math.random().toString(16).slice(2),
      task: e.task.value,
      completed: false
    }]);
  }

  // keep state in sync with localstorage
  useEffect(()=>{
    const ac = new AbortController();

    if(!storage.get('todos')) storage.init('todos', []);
    storage.addEventListener('set', (key) => key === 'todos' && setList(storage.get(key)), { signal: ac.signal });

    return ()=> ac.abort();
  },[storage]);

  return (
    <div>
      {list.map((props)=> <Item {...props} />)
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
prs-dev profile image
Pratyush Srivastava • • Edited

Thanks for the feedback and snippet, I will certainly look more in the hook and see how can i implement it to make the app better, and after trying maybe write a follow up post too.

Collapse
 
brense profile image
Rense Bakker •

The last time I used that hook it needed 3 render cycles to display the actual local storage value... I hope it has improved since then.

Collapse
 
link2twenty profile image
Andrew Bone •

I don't think the hook in my specific example has had that issue. Or at least it's not an issue I've ever noticed but I did write it.

Collapse
 
brense profile image
Rense Bakker • • Edited

Oh btw, local storage actually produces events, so you can attach event listeners to it in a side effect (what they're actually meant for) and update state from there. Dont forget to remove the event listener in the cleanup function of the side effect though.

Here's a codesandbox, this will also work if you change local storage in other tabs:
codesandbox.io/p/devbox/late-sun-q...

Collapse
 
prs-dev profile image
Pratyush Srivastava • • Edited

I didn’t know about the events associated with localStorage. I’ll definitely explore that more and make sure to handle cleanup properly when adding event listeners in a side effect. Thanks a lot for the sandbox — really helpful!