Context overrites my localStorage on refresh

Issue

I have context setup in my project. It’s a todo list. And when i setup the context it’s an empty array.

Everytime i submit a todo item it is set in localStorage. Now when i refresh the page i’m setting the localStorage to the empty array of the context and it overrides what i had in localStorage.

Horrible explanation but i hope you get the gist. I’ll upload some code below.

Context:

import React from "react";

export const TodoContext = React.createContext();

export function TodoComponent({ children }) {
  const [todoItems, setTodoItems] = React.useState([]);

  return (
    <TodoContext.Provider value={[todoItems, setTodoItems]}>
      {children}
    </TodoContext.Provider>
  );
}

Form:

function Form() {
  const [todoItems, setTodoItems] = useContext(TodoContext);
  const [todoInput, setTodoInput] = useState("");

  useEffect(() => {
    const localTodoItems = JSON.parse(localStorage.getItem("todos"));
    setTodoItems(localTodoItems);
  }, []);

  useEffect(() => {
    localStorage.setItem("todos", JSON.stringify(todoItems));
  }, [todoItems]);

  const handleSubmit = (e) => {
    e.preventDefault();

    if (todoInput.length >= 1) {
      setTodoItems([
        ...todoItems,
        { id: todoItems.length + 1, name: todoInput },
      ]);

      setTodoInput("");
    } else {
      return false;
    }
  };

  return (
    <>
      <FormWrapper>
        <form action="" onSubmit={handleSubmit}>
          <input
            type="text"
            name=""
            id=""
            placeholder="Todo"
            value={todoInput}
            onChange={(e) => setTodoInput(e.target.value)}
          />
          <input type="submit" value="Toevoegen" />
        </form>
      </FormWrapper>
    </>
  );
}

List:

function List() {
  const [todoItems, setTodoItems] = useContext(TodoContext);

  useEffect(() => {
    const localTodoItems = JSON.parse(localStorage.getItem("todos"));

    setTodoItems(localTodoItems);
  }, []);

  useEffect(() => {
    localStorage.setItem("todos", JSON.stringify(todoItems));
  }, [todoItems]);

  const deleteButton = (e, id) => {
    e.preventDefault();

    const newArray = todoItems.filter((item) => {
      if (item.id !== id) {
        return item;
      }
    });
    setTodoItems(newArray);
  };

  return (
    <>
      <ListWrapper>
        <ul className="list">
          {todoItems?.map((item) => {
            return (
              <>
                <li className="list__item" key={item.id}>
                  <div className="todo">
                    <span className="todo__label">{item.name}</span>
                    <button
                      className="todo__delete"
                      onClick={(e) => deleteButton(e, item.id)}
                    >
                      Delete
                    </button>
                  </div>
                </li>
              </>
            );
          })}
        </ul>
      </ListWrapper>
    </>
  );
}

Thanks!

Solution

I managed to recreate and fix the problem. The problem was that the TODOComponent initialized the todoItems to []: const [todoItems, setTodoItems] = React.useState([]); which caused the Form and List components to react to the state change and set the localstorage to an empty array.

Updated initialization:

export function TodoComponent({ children }) {
const [todoItems, setTodoItems] = React.useState(getTodoItems());

  function getTodoItems() {
    return JSON.parse(localStorage.getItem("todos"));
  }

  return (
    <TodoContext.Provider value={{ todoItems, setTodoItems }}>
      {children}
    </TodoContext.Provider>
  );
}

The Form and List components also performs extra work. So by only letting the Form component be the "producer" and the List being the "Consumer" the program can avoid unnecessary work.

Form:

function Form() {
  const { todoItems, setTodoItems } = useContext(TodoContext);
  const [todoInput, setTodoInput] = useState("");

  useEffect(() => {
    localStorage.setItem("todos", JSON.stringify(todoItems));
  }, [todoItems]);

  const handleSubmit = (e) => {
    e.preventDefault();

    if (todoInput.length >= 1) {
      setTodoItems([
        ...todoItems,
        { id: todoItems.length + 1, name: todoInput },
      ]);

      setTodoInput("");
    } else {
      return false;
    }
  };

  return (
    <div>
      <form action="" onSubmit={handleSubmit}>
        <input
          type="text"
          name=""
          id=""
          placeholder="Todo"
          value={todoInput}
          onChange={(e) => setTodoInput(e.target.value)}
        />
        <input type="submit" value="Toevoegen" />
      </form>
    </div>
  );
}

List:

function List() {
  const { todoItems, setTodoItems } = useContext(TodoContext);

  const deleteButton = (e, id) => {
    e.preventDefault();

    const newArray = todoItems.filter((item) => {
      if (item.id !== id) {
        return item;
      }
    });
    setTodoItems(newArray);
  };

  if (!todoItems.length) {
    return <div>No items</div>;
  }

  return (
    <>
      <ul className="list">
        {todoItems?.map((item, index) => {
          return (
            <li className="list__item" key={index}>
              <div className="todo" key={item.key}>
                <span className="todo__label">{item.name}</span>
                <button
                  className="todo__delete"
                  onClick={(e) => deleteButton(e, item.id)}
                >
                  Delete
                </button>
              </div>
            </li>
          );
        })}
      </ul>
    </>
  );
}

Hope this helps:)

Answered By – Fredrik Morstad

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published