mingg IT

[react-query + formik] React Formik props isSubmitted with react-query mutate not working 본문

FrontEnd

[react-query + formik] React Formik props isSubmitted with react-query mutate not working

mingg123 2023. 2. 13. 20:32

현재 컴포넌트의 validate를 편하게 하기위해 react-formik을 사용하고 있고, API의 fetch 관련된 부분은 react-query를 사용하고 있었다. 

 

formik 라이브러리 이슈가 계속 발생해서 일단 상황을 프론트엔드 개발자 방에 공유했다.. 

 

 

// React Query 관련 코드이다. 
const userInfoQueryKey = ['address'];
const useAddressQuery = () => {
	const queryClient = QueryClient.useQueryClient();
	return QueryClient.useMutation(
		async (addressFormData: FormData) => {
			await postAddressAsync(addressFormData);
		},
		{
			onSuccess: () => {
				queryClient.invalidateQueries([...userInfoQueryKey]);
				alert('주소가 변경되었습니다.');
			},
		}
	);
};
    
// Formik 관련 코드이다. 
const useAddressTextFieldFormik = () => {
    const updateAddress = useAddressQuery();
    return useFormik<{
        addressNumber: string;
    }>({
        initialValues: {
            addressNumber: '',
        },
        validateOnMount: true,
        enableReinitialize: true,
        onSubmit: async (values) => {
            let addressFormData = new FormData();
            addressFormData.append('addressDetail', values.infoAddressDetail);

            updateAddress.mutate(addressFormData);
        },
    });
};

 

 

이런식으로 되어있고, 컴포넌트에선 해당 formik을 이렇게 쓰고 있었다.

const { isSubmitting, values, handleChange, isValid, handleSubmit, handleBlur } = useAddressFormik();
<Button
    disabled={!isValid}
    isSubmitting={isSubmitting}
    onClick={() => {
        handleSubmit();
    }}
    text={'주소 수정 버튼'}
    type={'submit'}
/>

참고로 버튼의 isSubmmiting은 기본 옵션이 아니라 커스텀하게 만든 것이다. 우선 isSubmitting 옵션은 연타방지 옵션이다.

해당 버튼이  API 를 호출하는 버튼이라면 연타했을 경우, 서버로 불필요하게 많은 요청이 날라가게 되고 이를 막아하기때문에 formik에서 제공하는 옵션 중 하나이다. 

 

 

내가 원했던 상황은 아래처럼 버튼 클릭과 동시에 API가 호출되는 동안은 버튼이 비활성화되고, API응답을 받으면 버튼이 다시 활성화 되는 상황을 원했다. 

그런데 해당 코드는 isSubmitting이 정상적으로 동작하지 않았다.

formik github를 뒤져보기도 했었고, 방법은 여러가지가 많았지만(react-query를 사용하지 않고 바로 api를 호출하는 등), 최대한 formik + react- query를 함께 사용하는 방안을 채택하고 싶었다.

 

처음에 삽질한 해결방법은 

formik의 isSubmitting이 아닌 react-query의 isLoading옵션을 사용했다. 

  • react-query의 isLoading으로 submitting을 해줌
  •  formik의 onSubmit함수 내부 내용을 onClick함수로 옮김.
장점)
  • react-query, formik 둘 다 활용 가능
단점)
  • 어떤 컴포넌트는 formik의 onSubmit 을 사용하고, 어떤 컴포넌트는 onClick 내부에서 기능이 구현되어있어서 코드 파악하는데 헷갈릴 수 있음.
const { isSubmitting, values, handleChange, isValid, handleSubmit, handleBlur } = useAddressFormik();
const updateAddress = useAddressQuery();
<Button
        type='submit'
        disabled={!isValid}
        isSubmitting={updateChildren.isLoading}
        text={주소 수정 버튼'}
        onClick={() => {
            let addressFormData = new FormData();
            addressFormData.append(
                'addressNumber',
                'addressNum'
            );
            updateAddress.mutate(addressFormData);
        }}
    />

 이런식으로 사용했다.

해당 방법을 이용하면 내가 원했던 버튼의 연타방지와, react-query의 mutate 기능을 함께 쓸 수 있다.

허나 코드의 응집성이 낮아지고 파악하기 힘들어진다. 

 

두 번째 해결방법이다.

react-query의 mutateAsync 사용하기

가장 깔끔한 방법이였다. 버튼 컴포넌트 내에서는 기존의 코드대로 formik의 옵션을 이용하여 isSubmitting, handleSubmit 함수를 이요해준다. 

const { isSubmitting, values, handleChange, isValid, handleSubmit, handleBlur } = useAddressFormik();
<Button
        type='submit'
        disabled={!isValid}
        isSubmitting={isSubmitting}
        text={주소 수정 버튼'}
        onClick={() => {
         handleSubmit();
        }}
    />

 

Formik의 onSubmit 함수에서 mutate가 아닌 await updateAddress.mutateAsync 를 사용해준다.

    
// Formik 관련 코드이다. 
const useAddressTextFieldFormik = () => {
    const updateAddress = useAddressQuery();
    return useFormik<{
        addressNumber: string;
    }>({
        initialValues: {
            addressNumber: '',
        },
        validateOnMount: true,
        enableReinitialize: true,
        onSubmit: async (values) => {
            let addressFormData = new FormData();
            addressFormData.append('addressDetail', values.infoAddressDetail);

            await updateAddress.mutateAsync(addressFormData);
        },
    });
};

 

그렇다면 react-query의 Mutate와 mutateAsync의 차이가 무엇일까? 

mutate는 찍어보면 undefined임을 확인할 수 있다. 

const data = await updateChildren.mutate(childrenFormData);
console.log('data', data);

가장 큰 차이는 mutateAsync는 Promise를 반환한다. console.log로 찍어서 확인을 해보면 data에 값이 들어와있다.

const data = await updateChildren.mutateAsync(childrenFormData);
console.log('data', data);

 

 

 이렇게 구현하면 내가 원했던 버튼의 연타방지와, formik의 기능을 함께 사용할 수 있다.

mutateAsync를 사용하게되면 Promise를 반환하게되어 isSubmitting이 되는데.. 이게 왜 되는 걸까?

 

react-formik을 clone떠서 내부를 살펴보았다.

 

submitForm 함수 내부를 보게되면, promiseOrUndefined 가 promise여야 dispatch함수 내부로 들어가서 submitting에 대한 상태를 바꾸어주는데

 

mutate를 하게 되었을 경우에 undefined를 반환함으로  promiseOrUndefined 가 undefined가 되어

return 함수로 들어가서 isSubmitting이 정상적으로 동작하지 않은 것이다. 

이제야 왜 안되는지 알게 되었다. 

 

내부를 좀 더 살펴보면 reducer 로 상태를 관리하고 있다. 

여튼 오픈소스를 자주 좀 더 들여다보는 습관을 가져야 할 것 같다. 문제에 닥쳤을때 어떤식으로 구현되어있는지 유추해볼 수 있어야 실력이 늘 것 같다. 

Comments