reactjs / reactjs/react.dev

Signal pattern for identifying events instead of using usePrevious for reacting to events

Đang mở
#3,405 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
JavaScript
Star
11.8k
Fork
7.9k
Merge trung bình
1 ngày 11 giờ
Pull request đã merge (30 ngày)
11

Mô tả

Not quite sure if this makes sense, so first I wanted to discuss it before creating a PR.

Problem statement since setState does not accept a callback, if we want to execute a code after an async process has been done, we need to use usePrevious to store the old value e.g. updatingRemoteResource = true set just after the async process had started and then compare it with the new value e.g. updatingRemoteResource = false after the async process is done, and then if they are different, we trigger a new event e.g. fetchUpdatedRemoteResource

I described the issue in more detail here:
https://www.reddit.com/r/reactjs/comments/jzqcyu/decoratively_chaining_asynchronous_hooks_for_flow/

and have searched for it for a while, I couldn't find any alternative solutions e.g. after searching for a while I just found this:
e.g. https://stackoverflow.com/questions/53898810/executing-async-code-on-update-of-state-with-react-hooks

Solution I have an idea borrowed from signal processing that could make this much simpler: if there is a state which we need to keep track of from one render to the next, we define it as an array that can form patterns even if it is a primitive i.e. [], [firstValue], [firstValue, secondValue], etc. that can be used to identify different events e.g. [0] updated to [0,1] signalling an on event or [1], [1,0] signalling an off event. More complex events can be formed e.g. for animations, etc. (perhaps it will be even cleaner with observables, but arrays are good enough I suppose)

In its simplest form though, it's actually just an array with a maximum length of one that gets recreated.

e.g. codesandbox

import React, { useEffect, useState } from "react";

// imagine this is in a remote resource e.g. a database
let remoteNumber = 0;

async function updateRemoteNumber() {
  return new Promise(function (resolve) {
    setTimeout(() => {
      remoteNumber = Math.random();
      resolve("success");
    }, 1000);
  });
}

async function fetchRemoteNumber() {
  return new Promise(function (resolve) {
    setTimeout(() => {
      resolve(remoteNumber);
    }, 1000);
  });
}

export default function App() {
  const [updatingNumber, setUpdatingNumber] = useState([0]);
  const [fetchingNumber, setFetchingNumber] = useState([0]);
  const [reFetchNumber, setRefetchNumber] = useState([0]);
  const [number, setNumber] = useState();

  async function fetchNumber() {
    setFetchingNumber([1]);
    setNumber(undefined);
    const remoteNumber = await fetchRemoteNumber();
    setFetchingNumber([0]);
    setNumber(remoteNumber);
  }

  async function updateNumber() {
    setUpdatingNumber([1]);
    await updateRemoteNumber();
    setUpdatingNumber([1, 0]);
  }

  useEffect(function initialFetch() {
    fetchNumber();
  }, []);

  useEffect(
    function fetchAfterUpdate() {
      if (updatingNumber[1] === 0) {
        console.log("fetchAfterUpdate");
        fetchNumber();
      }
    },
    [updatingNumber]
  );

  useEffect(
    function fetchAfterRefetch() {
      if (reFetchNumber[0]) {
        console.log("fetchAfterRefetch");
        fetchNumber();
      }
    },
    [reFetchNumber]
  );

  const lastFetchNumber = fetchingNumber[fetchingNumber.length - 1];
  const lastUpdatingNumber = updatingNumber[updatingNumber.length - 1];
  const loading = lastFetchNumber || lastUpdatingNumber;

  return (
    <div>
      <p>Number: {number >= 0 ? number : "undefined"}</p>
      <p>Fetching number: {String(Boolean(lastFetchNumber))}</p>
      <p>Updating number: {String(Boolean(lastUpdatingNumber))}</p>
      <button
        onClick={() => {
          setRefetchNumber([1]);
        }}
        disabled={loading}
      >
        Re-fetch number
      </button>
      <button onClick={updateNumber} disabled={loading}>
        Update number
      </button>
    </div>
  );
}

Question Is there any issue with this pattern that I am missing, or are there alternative patterns for achieving the same result? Is it non-obvious enough to be added to the docs as a pattern?

Note I should point that this pattern is useful for cases besides async operations as well e.g. imagine implementing a button for a nested set of collapsible items e.g. a tree chart that collapses/expands all the items. This is inherently not a good use case for declarative state management, because although from one render to the next you want all the items to be expanded, you do not want them to remain expanded if you collapse/expand some items afterwards (this can be implemented by passing callbacks as well, but I find this signal pattern much more straightforward).

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với CodeSandbox được liên kết và ngữ cảnh React Hooks được mô tả trong issue; so sánh mẫu signal được đề xuất với các mẫu thay thế mà câu hỏi đề cập. Công việc được coi là hoàn tất khi đạt được quyết định được maintainer phê duyệt về việc đây có phải là một mẫu React được tài liệu hóa hay không và, nếu đúng, xác định phạm vi tài liệu.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, react
Lĩnh vực
documentation
Loại issue
Tài liệu
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Cần làm rõ
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.