5

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.

user12893845
  • 176
  • 2
  • 13
  • 1
    Are you sure that you want to replace ```setState``` by ```useEffect```? – Maksym Bezruchko Feb 13 '20 at 17:56
  • useEffect works similar to componentDidMount and componentDidUpdate. So I guess you want to put `function() { this.refs["preview"].refresh(); }` there. See https://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 Feb 13 '20 at 17:56
  • I only want the setState callback to happen after state is changed in react hooks. – user12893845 Feb 13 '20 at 18:01
  • @Zydnar i am not trying to replace setState with useEffect but i am trying to replicate the callback of setState through useEffect(); – user12893845 Feb 13 '20 at 18:22

2 Answers2

5

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.

arjun sah
  • 407
  • 3
  • 11
1

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}
    />
PrakashT
  • 883
  • 1
  • 7
  • 17
  • But what is going to be inside the axios success callback. – user12893845 Feb 13 '20 at 18:18
  • 1
    @user12893845 i updated my code. But i don't know why you are using this.setState. I hope above code will work. – PrakashT Feb 13 '20 at 18:26
  • My problem got fixed. But i was in the middle of the code refactor from old react to react hooks thats why you are seeeing setState which i need to change.Thanks a lot for your input. – user12893845 Feb 14 '20 at 11:15