amol rane
amol rane

Reputation: 335

react hook state is not updating on first click

I am new to react and I am facing problem with useState hook. I have one from (using useForm), what I want to achieve is when user click on submit button it will send form data to api and collect a response from api then update state and send that updated state as params in url redirection (props.history.push). The only thing I stuck with is my useState hook is not updating on first click. I found similar questions but that did not solve my problem or one can say I did not understood it very well, pls help.

import React, {useState, useRef, useCallback, useEffect} from 'react'
import {useForm} from 'react-hook-form'
import CreateProcessComponent from '../../components/ProcessComponents/CreateProcess/CreateProcessComponent.jsx'
import {createProcessApiCall} from '../../services/ProcessService'


function postData (dt, err, param){
     var new_param = JSON.stringify(param)
        const result = createProcessApiCall(new_param)
            .then(response => {
                dt(response.data.data)
                console.log(response.data.data)
          })
          .catch(e => {
            console.log(e)
            err(true)
          })
        }
      
    

export default function CreateProcessContainer(props){

    const {register, handleSubmit, errors} = useForm()
    const [postApiData, setApiData] = useState({})
    const [isError, setIsError] = useState(false);

  
  
    const sendRequest= (data) => {
      let prm = {"ProcessName":data.processName,"ProcessDescription":data.processDescription,"Code":"null", "CreatedBy":"null"}
      postData(setApiData, setIsError, prm)
      props.history.push({pathname:'/test',state:{postApiData}})
    }


  

    return (
          <main>
            {isError && <div>Something went wrong ...</div>}
            { (!isError)  ?
            <CreateProcessComponent 
                register={register} 
                handleSubmit={handleSubmit} 
                errors={errors}
                onSubmit={sendRequest}/>
                :
                null }

            </main>

    )
}

in above code CreateProcessComponent is my form. pls let me know what I am doing wrong and what fixes I need to add

Upvotes: 1

Views: 1239

Answers (3)

amol rane
amol rane

Reputation: 335

Below changes solved my problem, let me know if I can improve the code

import React, {useState, useRef, useCallback, useEffect} from 'react'
import {useForm} from 'react-hook-form'
import CreateProcessComponent from '../../components/ProcessComponents/CreateProcess/CreateProcessComponent.jsx'
import {createProcessApiCall} from '../../services/ProcessService'



    

export default function CreateProcessContainer(props){

    const {register, handleSubmit, errors} = useForm()
    const [postApiData, setApiData] = useState({})
    const [isError, setIsError] = useState(false);


    function postData (dt, err, param){
      var new_param = JSON.stringify(param)
         createProcessApiCall(new_param)
             .then(response => {
                 dt(response.data.data)
                //  dt(response.data.data)
                 console.log(response.data.data)
           })
           .catch(e => {
             console.log(e)
             err(true)
           })
         }
       

useEffect(()=>{if (Object.keys(postApiData).length > 0)
  props.history.push({pathname:'/test',state:postApiData})},[postApiData])
  
  
  
    const sendRequest= (data) => {
      let prm = {"ProcessName":data.processName,"ProcessDescription":data.processDescription,"Code":"null", "CreatedBy":"null"}
      postData(setApiData, setIsError, prm)
    }


    return (
          <main>
            {isError && <div>Something went wrong ...</div>}
            { (!isError)  ?
            <CreateProcessComponent 
                register={register} 
                handleSubmit={handleSubmit} 
                errors={errors}
                onSubmit={sendRequest}/>
                :
                null }
            </main>

    )
}

Upvotes: 0

Drew Reese
Drew Reese

Reputation: 202761

Issue

sendRequest is updating state, but you are trying to grab the updated state for the navigation push on the next line. React state updates are asynchronous and the updated postApiData state value won't be available until at least a render cycle later. postData is also doing some asynchronous processing but it doesn't return a promise that can be awaited on.

So basically sendRequest fires off a postData call, then immediately does the navigation since this function is completely synchronous

const sendRequest= (data) => {
  ...
  postData(setApiData, setIsError, prm) // <-- setApiData updates state
  props.history.push({
    pathname: '/test',
    state: { postApiData } // <-- current state
  });
}

Solution 1

  1. Make sendRequest an async function and await a response from postData in a try/catch
  2. If successful then continue with navigation, if promise rejects then set error in catch
  3. Return the Promise from postData instead of passing state update functions

postData

Unpack the response data and return it

const postData = param => {
  const new_param = JSON.stringify(param);

  return createProcessApiCall(new_param)
    .then((response) => {
      return response.data.data;
    });
};

CreateProcessContainer

Remove the postApiData state and await the response from postData.

export default function CreateProcessContainer(props) {
  const { register, handleSubmit, errors } = useForm();
  const [isError, setIsError] = useState(false);

  const sendRequest = async (data) => {
    const prm = {
      ProcessName: data.processName,
      ProcessDescription: data.processDescription,
      Code: "null",
      CreatedBy: "null"
    };

    try {
      const postApiData = await postData(prm);
      history.push({ pathname: "/result", state: { postApiData } });
    } catch {
      setIsError(true);
    }
  };

  return (
    <main>
      {isError ? (
        <div>Something went wrong ...</div>
      ) : (
        <CreateProcessComponent
          register={register}
          handleSubmit={handleSubmit}
          errors={errors}
          onSubmit={sendRequest}
        />
      )}
    </main>
  );
}

Solution 2

  1. Make post data take success & failure callbacks to handle the promise resolve/rejection
  2. Pass callbacks from sendRequest

postData

Unpack the response data and pass it to and invoke the success callback. Pass the failure callback to the catch block.

const postData = (param, success, failure) => {
  const new_param = JSON.stringify(param);

  createProcessApiCall("result data from callbacks")
    .then((response) => {
      success(response.data.data);
    })
    .catch(failure);
};

CreateProcessContainer

Remove the postApiData state, and pass the param and success/failure callbacks to postData.

export default function CreateProcessContainer(props) {
  const { register, handleSubmit, errors } = useForm();
  const [isError, setIsError] = useState(false);

  const sendRequest = (data) => {
    const prm = {
      ProcessName: data.processName,
      ProcessDescription: data.processDescription,
      Code: "null",
      CreatedBy: "null"
    };
    postData
      prm,
      (postApiData) => {
        history.push({ pathname: "/result", state: { postApiData } });
      },
      () => setIsError(true)
    );
  };

  return (
    <main>
      {isError ? (
        <div>Something went wrong ...</div>
      ) : (
        <CreateProcessComponent
          register={register}
          handleSubmit={handleSubmit}
          errors={errors}
          onSubmit={sendRequest}
        />
      )}
    </main>
  );
}

Edit react-hook-state-is-not-updating-on-first-click

Upvotes: 1

Gleb Shaltaev
Gleb Shaltaev

Reputation: 254

Try this:

const main = useRef(null);
if(main) {}
<main ref={main}>

PS: Haven't sense

    // set isMounted to false when we unmount the component
    useEffect(() => {
      return () => {
        isMounted.current = false
      }
    }, [])

Your component will destroy, for what sets isMounted?

Upvotes: 1

Related Questions