Search code examples
reactjsreact-hooksasync-await

how to run functions after usestate is set and use the state value


I have a function that runs 2 async functions. The first one needs to set the useState data so that the second one can use that data. Then I am trying to get that variable and pass it to a third function. To pass state variables I am using npm use-between, which works like useState. However, I am having trouble getting and passing the variables.

between.js -

import { useState, useCallback } from 'react'
import { useBetween } from 'use-between';

const useValues = () => { 
    
    const [hash, setHash] = useState(false);
    const [finalHash, setFinalHash] = useState(false);
    const sH = useCallback(() => setHash(), []);
    const sFH = useCallback(() => setFinalHash(), []);
  
    return {
      hash,
      finalHash,
      sH,
      sFH
    };
  };
  
  export const useSharedValues = () => useBetween(useValues);

create.js -

    import { useState, useCallback } from 'react';
    import { useSharedValues } from './utils/between';
    import FormData from 'form-data';
    import axios from 'axios';
    
    function useCreate() {  
    const {
    hash,
    finalHash,
    sH,
    sFH } = useSharedValues();
           
        async function create1() {
 myCanvas.toBlob(async function(blob) { // extra function I run to get an image then upload file
                await axios({
                     method: "post",
                     url: "url",
                 }).then((response) => {
                   sH(response.data.Hash); // trying to set state here
                 });
})
        }
       
    async function create2() {      
        var objects = [
               {
                   "hash": hash, // trying to use state in next function
               }
        ]
       
       var data = JSON.stringify(objects)
          
       await axios({
           method: "post",
           url: "url",
           data: data,
       }).then((response) => {
       sFH(response.data.IpfsHash); // set final hash to use in 3rd function
       })
       }
       
    const create = useCallback(async () => {
        await create1(); 
        await create2(); // run after create1 has finished
        console.log(finalHash);
    }, [create1, create2]);
       
    return {create};   
}

Which is being called in another component in a different file -

import useCreate from "../create.js";

const {create} = useCreate();

    const onPressed = async () => {
    await create();
    await The3rdFunction(); // 3rd function needs to wait for final hash variable so it can use it
    };

Right now the functions are returning undefined. From my understanding this is because how useState works, and it doesn't show the value 'til the next render. So I'm unsure on how to achieve my goal?

Based on the code suggested by @ilketorun, I modified my code and added more to illustrate further -

between.js

....
const sH = useCallback((event) => setHash(event.target.value), []);
...

create.js

 function useCreate() {
        const {
            aSharedValue,
          } = useSharedValues();
    
    async function create1() {
        ....
        canvas.toBlob(async function(blob) { // here I upload a file which gives me the hash when done
            const formData = new FormData();
            formData.append('file', blob);
          
              return axios({
              method: "post",
              url: "url",
              data: formData,
              headers: { ... },
          }).then(res => res.data.Hash);
          
          });
    
    }
    
    async function create2(hash) { 
        var objects = [
            {
                "hash": hash,
                "value": aSharedValue
            }
        ]
    
    var data = JSON.stringify(objects)
    
    return axios({
        method: "post",
        url: "url",
        data: data,
        headers: { .... },
    }).then(res => res.data.Hash)
    }
    
    async function create3() {
      const hash = await create1()
      const finalHash = await create2(hash)
      return {hash, finalHash}
    }
    
    return { create1, create2, create3 }
    }
    
    export default useCreate;

Then I try running it in another component -

const onPressed = async () => {  
const finalHash = await create3()
const { success } = NextFunction(finalHash);
}

hash is returning undefined and I'm unable to get the finalHash in the NextFunction


Solution

  • decouple concerns

    Create a myapi module that handles your get/post requests and returns appropriate responses. Coordinating multiple requests and setting state should be a separate concern -

    // myapi.js
    import axios from "axios"
    
    function create1() {
      return axios.post("url").then(res => res.data.Hash)
    }
    
    function create2(hash) {
      return axios.post("url", {hash}).then(res => res.data.IpfsHash)
    }
    
    async function create3() {
      const hash = await create1()
      const finalHash = await create2(hash)
      return {hash, finalHash}
    }
    
    export { create1, create2, create3 }
    

    These ~15 lines of code effectively replace the entirety of your ~50 lines create.js file.

    Now inside your component -

    import * as myapi from "./myapi"
    
    function MyComponent(...) {
      // ...
    
      useEffect(_ => {
        myapi.create3()
          .then(({hash, finalHash}) => {
            setHash(hash)
            setFinalHash(finalHash)
          })
          .catch(console.error)
      }, [])
    }
    

    useValues appears incorrect

    In your useValues hook, you are wrapping setHash and setFinalHash in useCallback -

    1. The set* function returned from useState is already a "callback". There's no need to memoize it.
    2. The functions you create do not pass arguments to the set* functions. This is evident by setHash() and setFinalHash() which have no arguments.
    const useValues = () => { 
      const [hash, setHash] = useState(false)
      const [finalHash, setFinalHash] = useState(false)
      const sH = useCallback(() => setHash(), [])       // <-
      const sFH = useCallback(() => setFinalHash(), []) // <-
      return {
        hash,
        finalHash,
        sH,
        sFH
      }
    }
    

    Should be changed to -

    const useValues = () => {
      const [hash, setHash] = useState(false)
      const [finalHash, setFinalHash] = useState(false)
      return {
        hash,
        finalHash,
        sH: setHash,       // <--
        sFH: setFinalHash  // <--
      }
    

    Which begs the question, do you even need this?