fosbie
fosbie

Reputation: 1024

Calling React setState from outside of function body

I'm fairly new to React and I'm trying to get some dynamic content working based on a call to an API to validate some input data.

Because of the way I'm using the yup validation I think the code that handles the response from the API call has to be outside of the react function body. This means that I can't use "setOfferState" as shown in my example below.

I've tried doing it with just a basic variable "offer" and this seems to work OK most of the time, but I'm not convinced this will always work as I sometimes get errors in the console and have to reload the page. I understand that this is not the right way to set variables in React.

Both offer and offerState are shown in the example below, but offerState doesn't work (Error: Invalid hook call. Hooks can only be called inside of the body of a function component.)


let offer = '';
const [offerState, setOfferState] = useState(0);

const schema = yup.object().shape({
  discount: yup.string().max(20).test('custom', null,
    function (value) {
      offer = '';
      setOfferState('');
      return new Promise((resolve, reject) => {
        const api = new ApiFacade();
        api.validateCode(value).then((result) => {
          offer = result.value.offerDescription;
          setOfferState(result.value.offerDescription);
        })
      })
    })
});

const YourDetails = () => {
  const formApi = useForm({
    resolver: yupResolver(schema)
  });

  const { handleSubmit, setValue, trigger } = formApi;

  return (
    <FormProvider {...formApi}>

        <form onSubmit={handleSubmit(onSubmit)}>
          
              <Input name="discount" placeholder="Referral/Discount Code" />          
              <Button type="button" onClick={async () => { const result = await trigger("discount"); }}>Check</Button>
          
          {/* {offer.length > 0 && ( */}
            <Typography>{offer}</Typography>
            <Typography>{offerState}</Typography>
          {/* )} */}
          <StepNav handleNext={handleSubmit(onSubmit)} />
        </form>
    </FormProvider>
  );
};

export default YourDetails;

I would be grateful for any suggestions to resolve the state issue or how to move the validation code or validation response code into the function body.

Upvotes: 0

Views: 134

Answers (1)

Soufiane Boutahlil
Soufiane Boutahlil

Reputation: 2604

Move your schema into the function body:

const YourDetails = () => {
let offer = '';
const [offerState, setOfferState] = useState(0);

const schema = yup.object().shape({
  discount: yup.string().max(20).test('custom', null,
    function (value) {
      offer = '';
      setOfferState('');
      return new Promise((resolve, reject) => {
        const api = new ApiFacade();
        api.validateCode(value).then((result) => {
          if (result.hasFailed) {
            resolve(this.createError({ message: `Error checking code` }));
          } else {
            if (result.value.errorCode) {
              resolve(this.createError({ message: result.value.errorMessage }));
            } else {
              offer = result.value.offerDescription;
              setOfferState(result.value.offerDescription);
              resolve(true);
            }
          }
      })
    })
});

  const formApi = useForm({
    resolver: yupResolver(schema)
  });

  const { handleSubmit, setValue, trigger } = formApi;

  return (
    <FormProvider {...formApi}>

        <form onSubmit={handleSubmit(onSubmit)}>
          
              <Input name="discount" placeholder="Referral/Discount Code" />          
              <Button type="button" onClick={async () => { const result = await trigger("discount"); }}>Check</Button>
          
          {/* {offer.length > 0 && ( */}
            <Typography>{offer}</Typography>
            <Typography>{offerState}</Typography>
          {/* )} */}
          <StepNav handleNext={handleSubmit(onSubmit)} />
        </form>
    </FormProvider>
  );
};

export default YourDetails;

Upvotes: 2

Related Questions