We have a ReactJS app that uses redux-saga. In redux-saga we perform all the data grabbing (http webrequests). Moreover: We use React hooks.
Scenario:
We have a button that dispatches an action to redux. Redux-saga handles this action and fires a webrequest.
Now, this webrequest fails (for instance server is down).
What we want to do now is to change the text of the button. So in fact, we want to get back to the "scope" where the action has been dispatched.
Question:
What is the appropriate way to get back from the redux-saga to the button ?
I have found this:
https://github.com/ricardocanelas/redux-saga-promise-example
Is this appropriate to use with hooks in year 2021 ?
That example you posted sounds needlessly convoluted. Error handling with redux-sagas can be relatively straightforward. We'll consider 3 parts of the application - the UI, the store, and the actions/saga chain.
const SomeUIThing = () => {
const callError = useSelector(state => state.somewhere.error);
const dispatch = useDispatch();
return (
<button onClick={
dispatch({
type: "API_CALL_REQUEST"
})
}>
{callError ? 'There was an error' : 'Click Me!'}
</button>
)
}
You can see that the UI is reading from the store. When clicked, it will dispatch and API_CALL_REQUEST
action to the store. If there is an error logged in the store, it will conditionally render the button of the text, which is what it sounds like you wanted.
You'll need some actions and reducers to be able to create or clear an error in the store. So the initial store might look like this:
const initialState = {
somewhere: {
error: undefined,
data: undefined
}
}
function reducer(state = initialState, action){
switch(action.type){
case "API_CALL_SUCCESS":
return {
...state,
somewhere: {
...state.somewhere,
data: action.payload
}
}
case "API_CALL_FAILURE":
return {
...state,
somewhere: {
...state.somewhere,
error: action.payload
}
}
case "CLEAR":
return {
...state,
somewhere: initialState.somewhere
}
default:
return state;
}
}
Now your reducer and your store are equipped to handle some basic api call responses, for both failures and successes. Now you let the sagas handle the api call logic flow
function* handleApiCall(){
try {
// Make your api call:
const response = yield call(fetch, "your/api/route");
// If it succeeds, put the response data in the store
yield put({ type: "API_CALL_SUCCESS", payload: response });
} catch (e) {
// If there are any failures, put them in the store as an error
yield put({ type: "API_CALL_ERROR", payload: e })
}
}
function* watchHandleApiCall(){
yield takeEvery("API_CALL_REQUEST", handleApiCall)
}
This last section is where the api call is handled. When you click the button in the UI, watchHandleApiCall
listens for the API_CALL_REQUEST
that is dispatched from the button click. It then fires the handleApiCall
saga, which makes the call. If the call succeeds, a success action is fired off the to store. If it fails, or if there are any errors, an error action is fired off to the store. The UI can then read any error values from the store and react accordingly.
So as you see, with a try/catch block, handling errors within sagas can be pretty straightforward, so long as you've set up your store to hold errors.