React-마운트 해제 된 구성 요소의 setState ()


92

내 반응 구성 요소에서 ajax 요청이 진행되는 동안 간단한 회 전자를 구현하려고합니다-im은 상태를 사용하여 로딩 상태를 저장합니다.

어떤 이유로 내 React 구성 요소의 아래 코드 부분 에서이 오류가 발생합니다.

마운트되거나 마운트 된 구성 요소 만 업데이트 할 수 있습니다. 이것은 일반적으로 마운트되지 않은 구성 요소에서 setState ()를 호출했음을 의미합니다. 이건 안돼. 정의되지 않은 구성 요소에 대한 코드를 확인하십시오.

첫 번째 setState 호출을 제거하면 오류가 사라집니다.

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>
      );
  }

질문은 구성 요소가 이미 마운트되어야 할 때 (componentDidMount에서 호출 됨) 왜이 오류가 발생합니까? 구성 요소가 마운트되면 상태를 설정하는 것이 안전하다고 생각합니까?


내 생성자에서 "this.loadSearches = this.loadSearches.bind (this);"를 설정하고 있습니다. -문제에 그것을 추가하십시오
Marty

당신이 설정 시도가 로딩을 생성자에 null로? 작동 할 수 있습니다. this.state = { loading : null };
Pramesh Bajracharya

답변:


69

렌더링 기능을 보지 않고는 조금 힘들다. 해야 할 일을 이미 발견 할 수 있지만 인터벌을 사용할 때마다 언 마운트시이를 지워야합니다. 그래서:

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

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

이러한 성공 및 오류 콜백은 마운트 해제 후에도 계속 호출 될 수 있으므로 간격 변수를 사용하여 마운트되었는지 확인할 수 있습니다.

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

이것이 도움이되기를 바랍니다. 이것이 작업을 수행하지 않으면 렌더링 기능을 제공하십시오.

건배


2
브루노, "this"컨텍스트의 존재를 테스트 할 수 없습니까? ala this && this.setState .....
james emanon

7
또는 간단히 :componentWillUnmount() { clearInterval(this.loadInterval); }
Greg Herbowicz

@GregHerbowicz 타이머로 구성 요소를 마운트 해제하고 마운트하는 경우 간단한 지우기를 수행하더라도 여전히 발사 될 수 있습니다.
corlaez

14

질문은 구성 요소가 이미 마운트되어야 할 때 (componentDidMount에서 호출 됨) 왜이 오류가 발생합니까? 구성 요소가 마운트되면 상태를 설정하는 것이 안전하다고 생각합니까?

그것은되어 있지 에서 호출 componentDidMount. 귀하의 componentDidMount급부상하지 않는 스택, 타이머 핸들러의 스택에서 실행됩니다 콜백 함수 componentDidMount. 분명히 콜백 ( this.loadSearches)이 실행될 때 구성 요소가 마운트 해제되었습니다.

그래서 받아 들여진 대답은 당신을 보호 할 것입니다. 비동기 함수 (일부 핸들러에 이미 제출 됨)를 취소 할 수없는 다른 비동기 API를 사용하는 경우 다음을 수행 할 수 있습니다.

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

이는 특히 API가 취소 기능을 제공하는 경우 (에서 setInterval와 같이 clearInterval) 러그 아래에서 물건을 훑어 보는 것처럼 느껴지지만 모든 경우에보고하는 오류 메시지를 제거합니다 .


13
isMounted안티 패턴은 페이스 북을 사용하지 않을에 조언이다 : facebook.github.io/react/blog/2015/12/16/...
마티

1
네, "러그 아래에서 물건을 쓸어 내리는 것 같은 느낌이 든다"고 말합니다.
마르쿠스 유니 우스 브루투스

5

다른 옵션이 필요한 사람에게는 ref 속성의 콜백 메서드가 해결 방법이 될 수 있습니다. handleRef의 매개 변수는 div DOM 요소에 대한 참조입니다.

refs 및 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
효과적으로 "isMounted"를 위해 ref를 사용하는 것은 isMounted를 사용하는 것과 정확히 동일하지만 덜 명확합니다. isMounted는 이름 때문에 안티 패턴이 아니지만 마운트되지 않은 구성 요소에 대한 참조를 보유하는 안티 패턴이기 때문입니다.
Pajn 2017-10-17

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() {
    ...
  }
}

기능 구성 요소에 대해 이것을 달성하는 방법이 있습니까? @john_per
Tamjid

함수 구성 요소의 경우 ref를 사용합니다. const _isMounted = useRef (false); @Tamjid
john_per

1

후세를 위해

우리의 경우이 오류는 Reflux, 콜백, 리디렉션 및 setState와 관련이 있습니다. onDone 콜백에 setState를 보냈지 만 onSuccess 콜백에 대한 리디렉션도 보냈습니다. 성공한 경우 onSuccess 콜백은 onDone 전에 실행 됩니다. 이로 인해 시도 된 setState 이전에 리디렉션발생합니다 . 따라서 마운트되지 않은 구성 요소에 대한 오류 setState.

환류 저장 조치 :

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

수정 전 전화 :

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

수정 후 전화 :

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

어떤 경우에는 React의 isMounted가 "deprecated / anti-pattern"이기 때문에 _mounted 변수를 사용하여 직접 모니터링했습니다.


1

반응 후크로 활성화 된 솔루션을 공유합니다 .

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)
}, [])

fetch id 변경에 대한 이전 요청 을 취소 할 때마다 동일한 솔루션을 확장 할 수 있습니다 this.setState.

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

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

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

이것은 자바 스크립트의 클로저 덕분에 작동합니다 .

일반적으로 위의 아이디어 는 react doc에서 권장 하는 makeCancelable 접근 방식 에 가깝습니다.

isMounted는 Antipattern입니다.

신용

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.