리덕스 툴킷 썽크 createAsyncThunk()를 이용하여 로그인을 구현하던 도중 틀린 유저 정보를 입력해도 로그인이 정상적으로 동작하는 상황이 발생하였다.
async의 try catch를 사용해도 같은 문제가 발생했다.
공식문서를 찾아보니rejectWithValue와 unwrap을 사용해야 하는 것을 알게 되었다.
createAsyncThunk()는 promise와 같이 pending, fulfilled, rejected의 세가지 상태를 가지며 rejected 상태를 반환하기 위해 rejectWithValue와 unwrap을 사용해야 하는 것이다.
const onClick = () => {
dispatch(fetchUserById(userId)).then(() => {
// do additional work
})
}
썽크는 발송될 때 값을 반환할 수 있습니다. 일반적인 사용 사례는 썽크에서 프로미스를 반환하고 구성 요소에서 썽크를 발송한 다음 추가 작업을 수행하기 전에 약속이 해결될 때까지 기다리는 것입니다.
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.unwrap()
.then((originalPromiseResult) => {
// handle result here
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
호출 논리는 이러한 작업을 원래 프로미스 내용인 것처럼 동작을 처리하려고 할 수 있습니다. 디스패치된 썽크에 의해 반환된 프로미스에서 unwrap 속성을 사용하여 fulfilled된 payload를 추출 하거나 error를 throw할 수 있습니다.
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
// Use `err.response.data` as `action.payload` for a `rejected` action,
// by explicitly returning it using the `rejectWithValue()` utility
return rejectWithValue(err.response.data)
}
}
)
에러 동작을 핸들링하기 위해 rejectWithValue를 리턴해야 합니다.
이 rejectWithValue는 thunkAPI의 값입니다.
const handleSubmit = async (values) => {
try {
await dispatch(asyncUserFetch(values))
.unwrap()
.then((res) => {
history.push('/home');
console.log(res)
})
.catch((err) => {
console.log(err)
});
} catch (error) {
console.log(error);
}
};
썽크에서 반환된 프로미스를 받는 방법은 async와 promise 둘 다 사용 가능하다.
const asyncUserFetch = createAsyncThunk(
'loginSlice/asyncLoginFetch',
async (values, { rejectWithValue }) => {
const { email, password } = values;
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw error;
if (data) {
// user 테이블을 가져옴.
const { id } = data.session.user;
const user = await supabase.from('profiles').select().eq('id', id);
// 리턴 값은 exraReducers와 dispatch구문의 프로미스 반환 값으로 반환된다.
return user.data[0];
}
} catch (error) {
return rejectWithValue(error);
}
}
);
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
// 비동기인 reducer 작성
extraReducers: (builder) => {
builder.addCase(asyncUserFetch.pending, (state) => {
state.isLoadingLogin = true;
});
builder.addCase(asyncUserFetch.fulfilled, (state, action) => {
state.userInfo = { ...action.payload };
state.isLoadingLogin = false;
state.isSuccessLogin = true;
state.isLoggedIn = true;
});
builder.addCase(asyncUserFetch.rejected, (state) => {
state.isLoadingLogin = false;
state.isErrorLogin = true;
});
},
});
썽크에서 fulfilled된 return 값은 extraReducers의 action으로 넘겨지게 되고, 또한
dispatch의 프로미스 then의 res 반환 값으로 넘겨 줄 수 있다.
썽크에서 rejected된 return 값 역시 마찬가지 이다.
하지만 중요한 점은 rejectWithValue속성을 사용해야만 return 값이 정상적으로 반환된다.
const handleSubmit = async (values) => {
try {
await dispatch(asyncUserFetch(values))
.unwrap()
.then((res) => {
history.push('/home');
console.log(res)
})
.catch((err) => {
console.log(err)
});
} catch (error) {
console.log(error);
}
};
다시 한 번 디스패치한 부분을 살펴보면 then으로 fulfilled된 반환 값을 받은 경우 홈 화면으로 페이지를 이동하도록 하고, 콘솔을 찍어주도록 하였고
catch로 rejected된 반환 값을 받은 경우 콘솔을 찍어주도록 처리하였다.