setState () di dalam componentDidUpdate ()

131

Saya sedang menulis skrip yang memindahkan input dropdown di bawah atau di atas tergantung pada ketinggian dropdown dan posisi input di layar. Saya juga ingin mengatur pengubah ke dropdown sesuai dengan arahnya. Tetapi menggunakan setStatebagian dalam componentDidUpdatemenciptakan loop tak terbatas (yang jelas)

Saya telah menemukan solusi dalam menggunakan getDOMNodedan mengatur classname untuk dropdown secara langsung, tetapi saya merasa bahwa harus ada solusi yang lebih baik menggunakan alat Bereaksi. Adakah yang bisa membantu saya?

Ini adalah bagian dari kode kerja dengan getDOMNode(ia sedikit mengabaikan logika penentuan posisi untuk menyederhanakan kode)

let SearchDropdown = React.createClass({
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        el.classList.remove('dropDown-top');
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            el.classList.add('dropDown-top');
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        return (
            <DropDown >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

dan di sini adalah kode dengan setstate (yang menciptakan loop tak terbatas)

let SearchDropdown = React.createClass({
    getInitialState() {
        return {
            top: false
        };
    },
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        if (this.state.top) {
           this.setState({top: false});
        }
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            if (!this.state.top) {
              this.setState({top: true});
           }
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        let class = cx({'dropDown-top' : this.state.top});
        return (
            <DropDown className={class} >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});
Katerina Pavlenko
sumber
9
Saya pikir triknya di sini adalah yang setStateakan selalu memicu re-render. Daripada memeriksa state.topdan menelepon setStatebeberapa kali, cukup lacak apa yang Anda inginkan state.topdalam variabel lokal, lalu sekali pada akhir componentDidUpdatepanggilan setStatehanya jika variabel lokal Anda tidak cocok state.top. Seperti berdiri sekarang, Anda segera me-reset state.topsetelah re-render pertama, yang menempatkan Anda di loop tak terbatas.
Randy Morris
2
Lihat dua implementasi berbeda componentDidUpdatedalam biola ini .
Randy Morris
sial! variabel lokal memecahkan seluruh masalah, bagaimana saya tidak mengetahuinya oleh mysef! Terima kasih!
Katerina Pavlenko
1
Saya pikir Anda harus menerima jawaban di bawah ini. Jika Anda membacanya lagi, saya pikir Anda akan menemukannya menjawab cukup pertanyaan awal.
Randy Morris
Mengapa tidak ada yang menyarankan pindah kondisi ke componentShouldUpdate?
Patrick Roberts

Jawaban:

116

Anda bisa menggunakannya setStatedi dalam componentDidUpdate. Masalahnya adalah entah bagaimana Anda membuat loop tak terbatas karena tidak ada kondisi istirahat.

Berdasarkan fakta bahwa Anda memerlukan nilai yang disediakan oleh browser setelah komponen diberikan, saya pikir pendekatan Anda tentang penggunaan componentDidUpdatesudah benar, hanya perlu penanganan yang lebih baik dari kondisi yang memicu setState.

Damianmr
sumber
4
apa yang kamu maksud dengan 'break condition'? memeriksa apakah negara sudah diatur dan tidak mengatur ulang?
Katerina Pavlenko
Saya setuju dengan ini, satu-satunya komentar tambahan saya adalah bahwa penambahan / penghapusan kelas mungkin tidak perlu componentDidUpdatedan hanya dapat ditambahkan sesuai kebutuhan rendersaja.
Randy Morris
tetapi penambahan / penghapusan kelas tergantung pada posisi dropdown yang diperiksa di componentDidUpdate, Anda menyarankan untuk memeriksanya dua kali? Dan seperti yang saya mengerti, componentDidUpdate disebut SETELAH render (), jadi tidak berguna untuk menambah / menghapus kelas dalam render ()
Katerina Pavlenko
saya telah menambahkan kode saya dengan setstate, dapatkah Anda memeriksanya dan menunjukkan kesalahan saya? atau tunjukkan beberapa contoh yang tidak akan menyebabkan loop
Katerina Pavlenko
2
componentDidUpdate (prevProps, prevState) {if (prevState.x! == this.state.x) {// Do Something}}
Ashok R
69

Tanda componentDidUpdatetangan void::componentDidUpdate(previousProps, previousState). Dengan ini Anda akan dapat menguji alat peraga / negara bagian mana yang kotor dan memanggil yang setStatesesuai.

Contoh:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}
Abdennour TOUMI
sumber
componentDidMounttidak memiliki argumen, dan hanya dipanggil saat komponen dibuat, jadi tidak dapat digunakan untuk tujuan yang dijelaskan.
Jules
@ Jules, Terima kasih! Saya biasa menulis componentDidMount, jadi ketika saya menulis jawabannya, nama terkenal itu mengalir šŸ˜® Sekali lagi, Terima kasih & Hebat!
Abdennour TOUMI
componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ashok R
Saya tahu kekhawatiran Anda @AshokR. Anda mengurangi nama arg. tapi "prev" bisa berarti mencegah bukan sebelumnya .. hhh. .kidding :)
Abdennour TOUMI
58

Jika Anda menggunakan setStatedalam componentDidUpdateitu update komponen, sehingga panggilan untuk componentDidUpdateyang kemudian panggilan setStatelagi mengakibatkan loop tak terbatas. Anda harus menelepon secara kondisional setStatedan memastikan bahwa kondisi yang melanggar panggilan terjadi pada akhirnya, misalnya:

componentDidUpdate: function() {
    if (condition) {
        this.setState({..})
    } else {
        //do something else
    }
}

Jika Anda hanya memperbarui komponen dengan mengirimkan alat peraga untuk itu (itu tidak diperbarui oleh setState, kecuali untuk case di dalam componentDidUpdate), Anda dapat memanggil setStatebagian dalam componentWillReceivePropsalih-alih componentDidUpdate.

mickeymoon
sumber
2
pertanyaan lama tetapi componentWillReceiveProps sudah usang dan componentWillRecieveProps harus digunakan. Anda tidak dapat mengaturState di dalam metode ini.
Brooks DuBois
Maksudmu getDerivedStateFromProps.
adi518
5

Contoh ini akan membantu Anda memahami React Life Cycle Hooks .

Anda bisa setStatedalam getDerivedStateFromPropsmetode yaitu staticdan memicu metode setelah alat peraga berubah componentDidUpdate.

Di dalam componentDidUpdateAnda akan mendapatkan param 3 yang kembali dari getSnapshotBeforeUpdate.

Anda dapat memeriksa kode dan tautan kotak ini

// Child component
class Child extends React.Component {
  // First thing called when component loaded
  constructor(props) {
    console.log("constructor");
    super(props);
    this.state = {
      value: this.props.value,
      color: "green"
    };
  }

  // static method
  // dont have access of 'this'
  // return object will update the state
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
    return {
      value: props.value,
      color: props.value % 2 === 0 ? "green" : "red"
    };
  }

  // skip render if return false
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    // return nextState.color !== this.state.color;
    return true;
  }

  // In between before real DOM updates (pre-commit)
  // has access of 'this'
  // return object will be captured in componentDidUpdate
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return { oldValue: prevState.value };
  }

  // Calls after component updated
  // has access of previous state and props with snapshot
  // Can call methods here
  // setState inside this will cause infinite loop
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
  }

  static getDerivedStateFromError(error) {
    console.log("getDerivedStateFromError");
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    console.log("componentDidCatch: ", error, info);
  }

  // After component mount
  // Good place to start AJAX call and initial state
  componentDidMount() {
    console.log("componentDidMount");
    this.makeAjaxCall();
  }

  makeAjaxCall() {
    console.log("makeAjaxCall");
  }

  onClick() {
    console.log("state: ", this.state);
  }

  render() {
    return (
      <div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
        <p style={{ color: this.state.color }}>Color: {this.state.color}</p>
        <button onClick={() => this.onClick()}>{this.props.value}</button>
      </div>
    );
  }
}

// Parent component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 1 };

    this.tick = () => {
      this.setState({
        date: new Date(),
        value: this.state.value + 1
      });
    };
  }

  componentDidMount() {
    setTimeout(this.tick, 2000);
  }

  render() {
    return (
      <div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
        <p>Parent</p>
        <Child value={this.state.value} />
      </div>
    );
  }
}

function App() {
  return (
    <React.Fragment>
      <Parent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Nikhil Mahirrao
sumber
2

Saya akan mengatakan bahwa Anda perlu memeriksa apakah negara sudah memiliki nilai yang sama yang Anda coba tetapkan. Jika sama, tidak ada titik untuk mengatur negara lagi untuk nilai yang sama.

Pastikan untuk mengatur negara Anda seperti ini:

let top = newValue /*true or false*/
if(top !== this.state.top){
    this.setState({top});
}
gradosevic
sumber
-1

Saya memiliki masalah serupa di mana saya harus memusatkan toolTip. Bereaksi setState di componentDidUpdate tidak menempatkan saya di infinite loop, saya mencoba kondisi itu berfungsi. Tetapi saya menemukan menggunakan ref callback memberi saya solusi yang lebih sederhana dan bersih, jika Anda menggunakan fungsi inline untuk ref callback Anda akan menghadapi masalah nol untuk setiap pembaruan komponen. Jadi gunakan referensi fungsi dalam ref callback dan atur status di sana, yang akan memulai render ulang

Sanjay
sumber