punygod
punygod

Reputation: 307

Calling React Hook Asynchronously

Sorry if this is a beginner React question. I am trying to use a React Hook to fetch data and return it to the component asynchronously.

    const CallSubmit = async () =>  {
        return await useSubmitForm(tenantForm, datasetForm, entityForm)
    }

    const CallSubmitHook = async () => {
        const submitStatus = await CallSubmit();
        console.log(submitStatus)
        if (submitStatus === true) {
            setCurrentForm(5)
        } else if (submitStatus === false) {
            setCurrentForm(6)
        }
    }

useSubmitForm is the hook I am having issue with. With this iteration of the code, I get the following error: Invalid hook call. Hooks can only be called inside of the body of a function component.

I've moved the hook call to another function to try to fix this error but it remains. If it needs to be a the top level of the function, how can I conditionally call this once the form is being submitted?

I've tried putting the fetch in a useEffect but the issue is that I need the fetch to get its response back before assigning it to a variable. Please let me know how to best implement this?

EDIT Based on Dave's answer, I've restructured my code.

Hook (Sorry for the complexity here. Need to make 3 separate api calls with the data):

export default async function useSubmitForm(tenant, dataset, entity) {

let tenantRequestParams = {}
let datasetRequestParams = {}
let entityRequestParams = {}

  const [isSending, setIsSending] = useState(false);
  const [formPostResult, setFormPostResult] = useState()
  const postFormData = async (tenant, dataset, entity) => {
    setIsSending(true);
    const result = await axios.get('token url')
    .then((res) => {
      token = res.data
  
      tenantRequestParams = {
        url: "api1",
        config: {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Authorization': `Bearer ${token}`,
          }
        },
        body: JSON.parse(tenant),
      };
    
      datasetRequestParams = {
        url: "api2",
        config: {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Authorization': `Bearer ${token}`,
          }
        },
        body: JSON.parse(dataset),
      };
    
      entityRequestParams = {
        url: "api3",
        config: {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Authorization': `Bearer ${token}`,
          }
        },
        body: JSON.parse(entity),
      };
    }).then(() => {
      console.log(tenantRequestParams.url,
        tenantRequestParams.body,
        tenantRequestParams.config)
  
      function postTenant() {
        return axios
        .post(
          tenantRequestParams.url,
          tenantRequestParams.body,
          tenantRequestParams.config
        )
        .then((res) => {
          console.log(res);
          return res;
        })
        .catch((error) => {
          console.error(error);
          return error;
        });
      }
  
      function postDataset() {
        return axios
        .post(
          datasetRequestParams.url,
          datasetRequestParams.body,
          datasetRequestParams.config
        )
        .then((res) => {
          console.log(res);
          return res;
        })
        .catch((error) => {
          console.error(error);
          return error;
        });
      }
  
      function postEntity() {
        return axios
        .post(
          entityRequestParams.url,
          entityRequestParams.body,
          entityRequestParams.config
        )
        .then((res) => {
          console.log(res);
          return res;
        })
        .catch((error) => {
          console.error(error);
          return error;
        });
      }
  
      axios.all([postTenant(), postDataset(), postEntity()])
        .then(res => {
          return res;
        }).catch(e => {
          console.error(e)
          return e;
        })
  
    });
    setFormPostResult(result)
  }
  const postFormDataCallback = useCallback(postFormData, [])

return { isSending, success: formPostResult, postFormData: postFormDataCallback };
}

And from the component (Simplified version):

import useSubmitForm from '../hooks/useSubmitForm';

export default function FormPage(props) {
    const { isSending, success, postFormData } = useSubmitForm();
    const [tenantForm, setTenantForm] = useState('');
    const [datasetForm, setDatasetForm] = useState('');
    const [entityForm, setEntityForm] = useState('');

            return (
                <div>
                    <Form (3 forms, sets values for each to state above)/>
                    <PrimaryButton type="button" onClick={() => {
                        postFormData(tenantForm, datasetForm, entityForm)
                    }}>Submit</PrimaryButton>

                </div>
            )
}

With this implementation, I'm having an issue where the destructuring in the component is not working properly (all 3 are undefined any values). Am I missing something here?

Upvotes: 0

Views: 113

Answers (1)

Dave
Dave

Reputation: 7717

Hooks belong in the scope of your functional component and must always be called and always be called in the same order.

// Everything above is outside the component in global space
const MyComponent = () => {
  // Hooks can only go between these lines... START

  // Hooks can only go between these lines... END

  return (
    <>return your component JSX</>
  )
}

UPDATE In your example above useSubmitForm is the hook. (Hooks start with the word use.) Move the hook to where it belongs. Don't put async in front of the hook. The idea is that you call the hook, it does some work in the background. The hook can return other methods (e.g. postTenantForm() that can you call within your react component.)

UPDATE2 Here is some very rough psudo code. You'll need to read some tutorials to get across the finish line, but this is the outline.

const useExampleHook = () => {
  const [isSending, setIsSending] = useState(false)
  const [formPostResult, setFormPostResult] = useState()
  const postFormData = async (whichForm, theData) => {
      setIsSending(true)
      const result = await ... // post the data
      setFormPostResult(result)
      setIsSending(false)
  }
  const postFormDataCallback = useCallback(postFormData, [])

  return {isSending, success: formPostResult, postFormData: postFormDataCallback}
}


// Everything above is outside the component in global space
const MyComponent = () => {
  // Hooks can only go between these lines... START
  const {isSending, success, postFormData} = useExampleHook()
  // Hooks can only go between these lines... END

  if (isSending) return <>submitting form</>
  if (success) return <>Tada</>

  return (
    <>
      <button type="button" onClick={()=>{postFormData('formA',{some:data})}}>Save</button>
    </>
  )
}

Upvotes: 1

Related Questions