Home > Mobile >  Updating state being one step behind WITHOUT using useEffect?
Updating state being one step behind WITHOUT using useEffect?

Time:04-26

Note: I've seen people suggesting useEffect to this issue but I am not updating the state through useEffect here..

The problem I am having is that when a user selects id 7 for example, it triggers a function in App.tsx and filters the todo list data and update the state with the filtered list. But in the browser, it doesn't reflect the updated state immediately. It renders one step behind.

Here is a Demo

How do I fix this issue (without combining App.tsx and TodoSelect.tsx) ?

function App() {
  const [todoData, setTodoData] = useState<Todo[]>([]);
  const [filteredTodoList, setFilteredTodoList] = useState<Todo[]>([]);
  const [selectedTodoUser, setSelectedTodoUser] = useState<string | null>(null);

  const filterTodos = () => {
    let filteredTodos = todoData.filter(
      (todo) => todo.userId.toString() === selectedTodoUser
    );
    setFilteredTodoList(filteredTodos);
  };

  useEffect(() => {
    const getTodoData = async () => {
      console.log("useeffect");
      try {
        const response = await axios.get(
          "https://jsonplaceholder.typicode.com/todos"
        );
        setTodoData(response.data);
        setFilteredTodoList(response.data);
      } catch (error) {
        console.log(error);
      }
    };
    getTodoData();
  }, []);

  const handleSelect = (todoUser: string) => {
    setSelectedTodoUser(todoUser);

    filterTodos();
  };

  return (
    <div className="main">
      <TodoSelect onSelect={handleSelect} />
      <h1>Todo List</h1>
      <div>
        {" "}
        {filteredTodoList.map((todo) => (
          <div>
            <div>User: {todo.userId}</div>
            <div>Title: {todo.title}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

In TodoSelect.tsx

export default function TodoSelect({ onSelect }: TodoUsers) {
  const users = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
  return (
    <div>
      <span>User: </span>
      <select
        onChange={(e) => {
          onSelect(e.target.value);
        }}
      >
        {users.map((item) => (
          <option value={item} key={item}>
            {item}
          </option>
        ))}
      </select>
    </div>
  );
}

CodePudding user response:

There's actually no need at all for the filteredTodoList since it is easily derived from the todoData state and the selectedTodoUser state. Derived state doesn't belong in state.

See Identify the Minimal but Complete Representation of UI State

Let’s go through each one and figure out which one is state. Ask three questions about each piece of data:

  1. Is it passed in from a parent via props? If so, it probably isn’t state.
  2. Does it remain unchanged over time? If so, it probably isn’t state.
  3. Can you compute it based on any other state or props in your component? If so, it isn’t state.

Filter the todoData inline when rendering state out to the UI. Don't forget to add a React key to the mapped todos. I'm assuming each todo object has an id property, but use any unique property in your data set.

Example:

function App() {
  const [todoData, setTodoData] = useState<Todo[]>([]);
  const [selectedTodoUser, setSelectedTodoUser] = useState<string | null>(null);

  useEffect(() => {
    const getTodoData = async () => {
      console.log("useeffect");
      try {
        const response = await axios.get(
          "https://jsonplaceholder.typicode.com/todos"
        );
        setTodoData(response.data);
      } catch (error) {
        console.log(error);
      }
    };
    getTodoData();
  }, []);

  const handleSelect = (todoUser: string) => {
    setSelectedTodoUser(todoUser);
  };

  return (
    <div className="main">
      <TodoSelect onSelect={handleSelect} />
      <h1>Todo List</h1>
      <div>
        {filteredTodoList
          .filter((todo) => todo.userId.toString() === selectedTodoUser)
          .map((todo) => (
            <div key={todo.id}>
              <div>User: {todo.userId}</div>
              <div>Title: {todo.title}</div>
            </div>
          ))
        }
      </div>
    </div>
  );
}

CodePudding user response:

State update doesn't happen synchronously! So, selectedTodoUser inside filterTodos function is not what you're expecting it to be because the state hasn't updated yet.

Make the following changes:

Pass todoUser to filterTodos:

const handleSelect = (todoUser: string) => {
  setSelectedTodoUser(todoUser);

  filterTodos(todoUser);
};

And then inside filterTodos compare using the passed argument and not with the state.

const filterTodos = (todoUser) => {
  let filteredTodos = todoData.filter(
    (todo) => todo.userId.toString() === todoUser
  );
  setFilteredTodoList(filteredTodos);
};

You probably won't need the selectedTodoUser state anymore!

  • Related