5
votes

Hi I have a scenario where i need to replace setState callback after setting new state in react hooks with React_hooks in a particular scenario.

The current code is::

const ThemeOptions = () => {
  const [previewType, setPreviewType] = useState("Desktop");
  const [previewUrl, setPreviewUrl] = useState("");
  const [layout, setLayout] = useState(null);
  const [saving, setSaving] = useState(false);

  const handleSave = (newdata) => {
    setLayout(newdata);
    setSaving(true);

    axios
      .put(window.urls.save, this.state.layout)
      .then(
        function(response) { // i want this to change in hooks
           this.setState(
             {
               layout: response.data,
               saving: false,
             },
             function() {
               this.refs["preview"].refresh();
             }
           );
        }.bind(this)
      )
      .catch(
        function(error) {
          console.log(error);
          setSaving(false);
        }.bind(this)
      );
  }

  return (
    <div>
      <Router>
        <div className="theme_options">
          <div className="row" style={{ height: 100 + "vh" }}>
            <Sidebar
              layout={layout}
              onSave={handleSave}
              onReset={this.handleReset}
              previewType={previewType}
              onChangeScreen={handlePreviewScreenChange}
              saving={saving}
            />
            <div className="col main">
              <span className="d-none d-md-block">
                <Preview
                  ref="preview"
                  type={this.state.previewType}
                  url={this.state.previewUrl}
                />
              </span>
            </div>
          </div>
        </div>
      </Router>
    </div>
  )
}

I want the success callback function of axios.put requests to be changed in hooks as in the above code there is usage of setState along with callback in it.

The major code to refactor is::

this.setState(
             {
               layout: response.data,
               saving: false,
             },
             function() {
               this.refs["preview"].refresh();
             }
           );

I was thinking of doing ::

useEffect(()=> {
 // i dont know what to put there...
),[]);

I want the callback in setState() to happen in react hooks. Kindly suggest me an ideal way to do this considering the above code.

2
Are you sure that you want to replace setState by useEffect?Maksym Bezruchko
useEffect works similar to componentDidMount and componentDidUpdate. So I guess you want to put function() { this.refs["preview"].refresh(); } there. See reactjs.org/docs/hooks-effect.html for reference But setState can't be replaced with useEffect - setState is used before render, and useEffect after. The name useEffect is from triggering side-effect like changing page title.Zydnar
I only want the setState callback to happen after state is changed in react hooks. <kindly point me in some direction.user12893845
@Zydnar i am not trying to replace setState with useEffect but i am trying to replicate the callback of setState through useEffect();user12893845

2 Answers

4
votes

so this is a very simple yet difficult puzzle which many person find it hard to tackle. Use the below code and it will for sure run and execute its task perfectly:: in the state add one additional flag of 'isError' :

  const [layout, setLayout] = useState(null);
  const [saving, setSaving] = useState(false);
  const [iserror, setIserror] = useState(true);

and use a useEffect like below. The first time useeffect will run but since the condions in useeffect is of specific type it will not run the IF block at all.and IF block will run only on specific conditions mentioned below. But mind it that useEffect will run everytime any of the dependency array elements change but the IF block will not execute at all.

  useEffect(()=>{
      if(layout && !saving && !iserror){
          console.log('specific case render ');
          this.refs["preview"].refresh();
      }

  },[layout,saving,iserror]);

had we put the default conditions of states in IF block then only IF block inside effect would run which is not the case as mentioned above. As we want the setState callback to run only after some specific condition and not always. If we change the above code to something like this then::

//for understanding purpose only default states
layout==null,
saving == false
isError== true
//

  useEffect(()=>{
      if(layout == null && !saving && iserror){ //observe this default case
          console.log('default case render ');

      }

  },[layout,saving,iserror]);

The handleSave function will do its task with little change::

  const handleSave = (newdata) => {
    setLayout(newdata); // useEffect run as layout changed but conditions 
                      // not satisfied
    setSaving(true);  // useEffect will run since saving changed
                      // not satisfied

    axios
      .put(window.urls.save, layout)
      .then(
        function(response) { // only this case forces the code in 
                             // useEffect to run as 3 below cases are 
                             // satisfied
          setLayout(response.data);
          setSaving(false);
          setIserror(false);

        }.bind(this)
      )
      .catch(
        function(error) {
          console.log(error);
          setSaving(false);// only 2 case satisfied i.e layout before 
                            // axios line and saving but not iserror.
          setIserror(true);
        }.bind(this)
      );
  }

Hope it helps.

1
votes

setState is asynchronous. In your case axios executed before setLayout will get updated

const handleSave = (newdata) => {

    axios
      .put(window.urls.save, this.state.layout)
      .then(
        function(response) { // i want this to change in hooks
             setLayout(response.data);               
             setSaving(false);
             this.refs["preview"].refresh();
        }.bind(this)
      )
      .catch(
        function(error) {
          console.log(error);
          setSaving(false);
        }.bind(this)
      );
  }


useEffect(()=>{
setLayout(newdata);
setSaving(true);
if(newData) handleSave(newData)
},[newData,saving])

and also use setchange while onSave funtion.

 <Sidebar
      layout={layout}
      onSave={(data)=>setLayout(data)}
      onReset={this.handleReset}
      previewType={previewType}
      onChangeScreen={handlePreviewScreenChange}
      saving={saving}
    />