React - setState () trên thành phần chưa được gắn kết


92

Trong thành phần phản ứng của tôi, tôi đang cố gắng triển khai một spinner đơn giản trong khi yêu cầu ajax đang được thực hiện - tôi đang sử dụng trạng thái để lưu trữ trạng thái tải.

Vì lý do nào đó, đoạn mã này bên dưới trong thành phần React của tôi đã tạo ra lỗi này

Chỉ có thể cập nhật một thành phần được gắn hoặc lắp. Điều này thường có nghĩa là bạn đã gọi setState () trên một thành phần chưa được gắn kết. Đây là một điều không cần lựa chọn. Vui lòng kiểm tra mã cho thành phần không xác định.

Nếu tôi thoát khỏi lệnh gọi setState đầu tiên, lỗi sẽ biến mất.

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }

Câu hỏi đặt ra là tại sao tôi lại gặp lỗi này khi thành phần lẽ ra đã được gắn kết (vì nó được gọi từ componentDidMount) Tôi nghĩ rằng có thể an toàn để thiết lập trạng thái sau khi thành phần được gắn kết?


trong hàm tạo của tôi, tôi đang đặt "this.loadSearches = this.loadSearches.bind (this);" - tôi sẽ thêm điều đó vào câu hỏi
Marty

bạn đã thử đặt tải thành null trong hàm tạo của mình chưa? Điều đó có thể hoạt động. this.state = { loading : null };
Pramesh Bajracharya

Câu trả lời:


69

Không nhìn thấy chức năng kết xuất là một chút khó khăn. Mặc dù đã có thể phát hiện ra điều gì đó bạn nên làm, nhưng mỗi khi sử dụng một khoảng thời gian, bạn phải xóa nó khi ngắt kết nối. Vì thế:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

Vì các lệnh gọi lại thành công và lỗi đó vẫn có thể được gọi sau khi ngắt kết nối, bạn có thể sử dụng biến khoảng thời gian để kiểm tra xem nó có được gắn kết hay không.

this.loadInterval && this.setState({
    loading: false
});

Hy vọng điều này sẽ hữu ích, cung cấp chức năng kết xuất nếu điều này không hoạt động.

Chúc mừng


2
Bruno, không thể bạn chỉ cần thử nghiệm cho sự tồn tại của bối cảnh "này" .. ala && this.setState này .....
james emanon

6
Hoặc đơn giản hơn:componentWillUnmount() { clearInterval(this.loadInterval); }
Greg Herbowicz 10/101717

@GregHerbowicz nếu bạn đang ngắt kết nối và gắn thành phần với bộ đếm thời gian, nó vẫn có thể được kích hoạt ngay cả khi bạn thực hiện thao tác xóa đơn giản.
corlaez

14

Câu hỏi đặt ra là tại sao tôi lại gặp lỗi này khi thành phần lẽ ra đã được gắn kết (vì nó được gọi từ componentDidMount) Tôi nghĩ rằng có thể an toàn để thiết lập trạng thái sau khi thành phần được gắn kết?

không được gọi từ componentDidMount. Của bạn componentDidMounttạo ra một hàm gọi lại sẽ được thực thi trong ngăn xếp của trình xử lý bộ hẹn giờ, không phải trong ngăn xếp componentDidMount. Rõ ràng, vào thời điểm lệnh gọi lại ( this.loadSearches) của bạn được thực thi, thành phần đã được ngắt kết nối.

Vì vậy, câu trả lời được chấp nhận sẽ bảo vệ bạn. Nếu bạn đang sử dụng một số API không đồng bộ khác không cho phép bạn hủy các hàm không đồng bộ (đã được gửi cho một số trình xử lý), bạn có thể làm như sau:

if (this.isMounted())
     this.setState(...

Điều này sẽ loại bỏ thông báo lỗi mà bạn báo cáo trong mọi trường hợp mặc dù có cảm giác giống như quét mọi thứ dưới tấm thảm, đặc biệt nếu API của bạn cung cấp khả năng hủy (như setIntervalvới clearInterval).


12
isMountedlà một phản vật chất mà facebook khuyên không nên sử dụng: facebook.github.io/react/blog/2015/12/16/…
Marty

1
Vâng, tôi nói rằng "cảm giác giống như quét mọi thứ dưới tấm thảm".
Marcus Junius Brutus

5

Đối với ai cần một tùy chọn khác, phương thức gọi lại của thuộc tính ref có thể là một giải pháp thay thế. Tham số của handleRef là tham chiếu đến phần tử div DOM.

Để biết thông tin chi tiết về refs và DOM: https://facebook.github.io/react/docs/refs-and-the-dom.html

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}

5
Sử dụng ref cho hiệu quả "isMounted" giống hệt như chỉ sử dụng isMounted nhưng ít rõ ràng hơn. isMounted không phải là anti-pattern vì tên của nó mà vì nó là anti-pattern để giữ các tham chiếu đến thành phần chưa được gắn kết.
Pajn

3
class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}

Có cách nào để đạt được điều này cho một thành phần chức năng không? @john_per
Tamjid

Đối với một thành phần hàm, tôi sẽ sử dụng ref: const _isMounted = useRef (false); @Tamjid
john_per

1

Đối với hậu thế,

Trong trường hợp của chúng tôi, lỗi này liên quan đến Reflux, callbacks, redirects và setState. Chúng tôi đã gửi một setState đến một lệnh gọi lại onDone, nhưng chúng tôi cũng gửi một chuyển hướng đến lệnh gọi lại onSuccess. Trong trường hợp thành công, lệnh gọi lại onSuccess của chúng tôi thực thi trước onDone . Điều này gây ra chuyển hướng trước setState đã thử . Do đó, lỗi, setState trên một thành phần chưa được gắn kết.

Reflux store action:

generateWorkflow: function(
    workflowTemplate,
    trackingNumber,
    done,
    onSuccess,
    onFail)
{...

Gọi trước khi sửa:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    this.setLoading.bind(this, false),
    this.successRedirect
);

Gọi sau khi sửa:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    null,
    this.successRedirect,
    this.setLoading.bind(this, false)
);

Hơn

Trong một số trường hợp, vì isMounted của React "không được chấp nhận / chống lại mẫu", chúng tôi đã áp dụng việc sử dụng biến _mounted và tự mình giám sát biến _mounted.


1

Chia sẻ một giải pháp được kích hoạt bởi phản ứng móc .

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

giải pháp tương tự có thể được mở rộng cho bất cứ khi nào bạn muốn hủy các yêu cầu trước đó về các thay đổi id tìm nạp, nếu không sẽ có các điều kiện đua giữa nhiều yêu cầu trong chuyến bay ( this.setStateđược gọi là không theo thứ tự).

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

điều này hoạt động nhờ các bao đóng trong javascript.

Nhìn chung, ý tưởng ở trên gần với phương pháp makeCancelable do tài liệu phản ứng đề xuất, trong đó nêu rõ

isMounted là một phản vật chất

tín dụng

https://juliangaramendy.dev/use-promise-subscription/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.