0

I have a collection of my lists in an array called allLists. When I create a new list I add the list to allLists array.

I can only add, update, delete elements from the selectedList.

My question is how do keep allLists updated with the changes made to selectedList.

  • How do I update a single list in a collection of lists?
  • Should I update using index of the array or by listName?
  • Is it best to do this in a useeffect or should i trigger this in a react lifecycle event or is there other ways of doing this?

Codesandbox link: https://codesandbox.io/s/react-handle-lists-of-lists-92lez?file=/src/App.tsx

import { useEffect, useState } from "react";
import "./styles.css";

export interface ListInterface {
  listName: string;
  list: string[];
}

export default function App() {
  const pokemonList = {
    listName: "Pokemon",
    list: ["Pikachu", "Onix", "Mew"]
  };

  const fruitsList = {
    listName: "Fruits",
    list: ["Apple", "Orange", "Banana"]
  };

  const numbersList = {
    listName: "Numbers",
    list: ["One", "Two", "Three"]
  };

  const [selectedList, setSelectedList] = useState<ListInterface>(numbersList);
  const [allLists, setAllLists] = useState<ListInterface[]>([
    pokemonList,
    fruitsList
  ]);

  const addListToAllLists = () => {
    //Add new list to allList
    if (selectedList?.listName !== "") {
      setAllLists([...allLists, selectedList]);
    }
  };

  const updateAllList = (listName: string) => {
    allLists.forEach((list) => {
      if (list.listName === listName) {
        //set updated list
      }
    });
  };

  useEffect(() => {
    if (selectedList) {
      //addListToAllLists();
    }
  }, [selectedList]);

  const addMultiElementToList = () => {
    let newList = ["Four", "Five", "Six"];

    setSelectedList({
      ...selectedList,
      list: selectedList.list.concat(newList)
    });
  };

  const addElementToList = () => {
    let newElement = "New Element";

    setSelectedList({
      listName: selectedList.listName,
      list: [...selectedList.list, newElement]
    });
  };

  const changeSelectedList = (listName: string) => {
    console.log("List", listName);
    allLists.forEach((list) => {
      if (list.listName === listName) {
        console.log("Found List", listName);
        setSelectedList(list);
      }
    });
  };

  return (
    <div className="App">
      <h2>Selected List [{selectedList.listName}]</h2>
      {selectedList?.list?.map((element) => {
        return <p>{element}</p>;
      })}

      <button onClick={() => addElementToList()}>Add Single Element</button>
      <button onClick={() => addMultiElementToList()}>
        Add Multiple Element
      </button>
      <button onClick={() => setSelectedList(numbersList)}>Clear List</button>

      <hr></hr>
      <h2>All Lists</h2>

      <h4>Change Selected List</h4>

      {allLists?.map((list) => {
        return (
          <button onClick={() => changeSelectedList(list.listName)}>
            {list.listName}
          </button>
        );
      })}
    </div>
  );
}

2 Answers 2

1
+50

My question is how do keep allLists updated with the changes made to selectedList.

You always want a "single source of truth" for data. selectedList should not be its own state. It is something that is derived from the lists in allLists and the name or id of the current list.

const [allLists, setAllLists] = useState<ListInterface[]>([
  pokemonList,
  fruitsList,
  numbersList
]);

const [selectedIndex, setSelectedIndex] = useState(0);

const selectedList = allLists[selectedIndex];

How do I update a single list in a collection of lists?

You would copy all of the other lists and replace the single list that are modified with a new version. So you need a copy within a copy. The Redux guide to Immutable Update Patterns is a good resource on handling nested updates.

Since you will have multiple similar functions to update the current list, we can remove a lot of repeated code by creating a helper function.

const updateCurrentList = (newList: string[]) => {
  setAllLists((lists) =>
    lists.map((current, i) =>
      i === selectedIndex ? { ...current, list: newList } : current
    )
  );
};

const addElementToList = () => {
  let newElement = "New Element";

  updateCurrentList([...selectedList.list, newElement]);
};

const addMultiElementToList = () => {
  let newList = ["Four", "Five", "Six"];

  updateCurrentList(selectedList.list.concat(newList));
};

Should I update using index of the array or by listName?

I don't feel strongly on this one. Accessing is definitely faster by index as you avoid the need to find(). When you update you always have to do a map() to copy the other lists, so there's not much difference. Technically the number comparison of the indexes is faster than the string comparison of the names, but this is negligible.

The listName would be better if lists are going to be added or removed because the array index might change but the listName won't.


Is it best to do this in a useEffect or should I trigger this in a react lifecycle event or is there other ways of doing this?

If you follow my advice about keeping data in one place only then there are no dependent updates which need to be done in a useEffect. The primary update of the allLists data is triggered by the event.


import { useState } from "react";
import "./styles.css";

export interface ListInterface {
  listName: string;
  list: string[];
}

export default function App() {
  const pokemonList = {
    listName: "Pokemon",
    list: ["Pikachu", "Onix", "Mew"]
  };

  const fruitsList = {
    listName: "Fruits",
    list: ["Apple", "Orange", "Banana"]
  };

  const numbersList = {
    listName: "Numbers",
    list: ["One", "Two", "Three"]
  };

  const [allLists, setAllLists] = useState<ListInterface[]>([
    pokemonList,
    fruitsList,
    numbersList
  ]);

  const [selectedIndex, setSelectedIndex] = useState(0);

  const selectedList = allLists[selectedIndex];

  // you could make this take a function instead of an array
  // to ensure that it always gets the current value from state
  const updateCurrentList = (newList: string[]) => {
    setAllLists((lists) =>
      lists.map((current, i) =>
        i === selectedIndex ? { ...current, list: newList } : current
      )
    );
  };

  const addElementToList = () => {
    let newElement = "New Element";

    updateCurrentList([...selectedList.list, newElement]);
  };

  const addMultiElementToList = () => {
    let newList = ["Four", "Five", "Six"];

    updateCurrentList(selectedList.list.concat(newList));
  };

  const clearList = () => {
    updateCurrentList([]);
  };

  return (
    <div className="App">
      <h2>Selected List [{selectedList.listName}]</h2>
      {selectedList?.list?.map((element) => {
        return <p>{element}</p>;
      })}

      <button onClick={addElementToList}>Add Single Element</button>
      <button onClick={addMultiElementToList}>Add Multiple Element</button>
      <button onClick={clearList}>Clear List</button>

      <hr></hr>
      <h2>All Lists</h2>

      <h4>Change Selected List</h4>

      {allLists.map((list, i) => {
        return (
          <button onClick={() => setSelectedIndex(i)}>{list.listName}</button>
        );
      })}
    </div>
  );
}

Code Sandbox Link

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks so much. Great explanation! I forgot to ask, how would you handle removing a list from allLists? Here is my example of doing it const removeAList = (arrayIndex: number) => { const arrayAfterDelete = allLists; arrayAfterDelete.splice(arrayIndex, 1); setAllLists(arrayAfterDelete); }
Splice is not a good method to use because it mutates the array. You can only use it if you create a copy of the original array. Usually I use filter. You’ll learn pretty quickly which array methods are “safe” and which you need to avoid.
const removeList = (arrayIndex: number = selectedIndex) => { setAllLists((lists) => lists.filter((_, i) => i !== arrayIndex)); }; If we are adding and removing lists then we also need to tweak the revertList function to search by name rather than index since index will have changed from the initial to the current.
0

Think you can get the answer through this.

https://www.robinwieruch.de/react-state-array-add-update-remove

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.