-2

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

730wavy
  • 944
  • 1
  • 19
  • 57

2 Answers2

1

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?

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • also see the [axios.post](https://axios-http.com/docs/post_example) documentation. you were making your life a lot harder than it needs to be. – Mulan Aug 23 '22 at 17:20
  • the callbacks in my between.js file are written that way because that's similar to how they had it in the documentation to the packaged I linked. I have the useCreate function because I have other values from my use-between data that I am using. – 730wavy Aug 23 '22 at 17:30
  • @730wavy i looked at [use-between](https://www.npmjs.com/package/use-between) and they are not calling `set*` functions without arguments. the functions they are using add *new* behaviour to the `set*` functions they are wrapping. there's no reason to wrap a `set*` function from `useState` like you did because `useState` already returns memoized `set*` callbacks – Mulan Aug 23 '22 at 17:34
  • I updated my question to try and make things clearer – 730wavy Aug 23 '22 at 18:47
0

You can utilize useState and useEffect hooks. useEffect will be re-rendered with respect to its dependencies. For example:

const [myFirstState,setMyFirstState] = useState()
const [mySecondState,setMySecondState] = useState()

useEffect(()=> {
  const myAsyncFunc = async () => {
    // await something here
    let awaitedResult = await someFunction();
    setMyFirstState(awaitedResult)
  }
  myAsyncFunc();
},[])

useEffect(()=> {
  // do stuff with myFirstState
  const myOtherAsyncFunc = async () => {
    // await something here
    let awaitedOtherResult = await someOtherFunction();
    setMySecondState(awaitedOtherResult)
  }
  myOtherAsyncFunc();
},[myFirstState])

useEffect(()=> {
  // do stuff with mySecondState
},[mySecondState])
ilketorun
  • 324
  • 3
  • 14
  • 2
    your "second" effect will still run when the component mounts. you will want to do something like `if (myFirstState) myOtherAsyncFunc()`. and don't forget to _always_ handle promise rejections, ie `myAsyncFunc().catch(console.error)` or some other handler – Mulan Aug 23 '22 at 16:58
  • Thank you for sharing improvements! – ilketorun Aug 23 '22 at 17:05