We are using redux-promise-middleware and having some difficulty working out what to do about error handling, all errors returned as 400 codes are just handled as 'FULFILLED'. I get that this is probably the correct behaviour but surely there is a way to catch an error in a promise in this setup. As we are not using redux-thunk, I am specifically asking how you would handle say a 400 error being returned from a promise.
our setup is very basic but I'm sure we should be able to
const export doSomething = object => {
const promise = API.doSomething(object)
return {
type: "DO_SOMETHING",
payload: {promise: promise}
}
}
reducer
export default (state = initialstate, action) {
switch(action.type){
case "DO_SOMETHING_FULFILLED":
return action.payload
case "DO_SOMETHING_REJECTED":
return console.log(action.payload)
default:
return initialstate
}
any help is greatly appreciated.
First of all you don't need to set payload to {promise: promise}
Just put the promise directly
Try this for first
export const doSomething = object => {
return {
type: "DO_SOMETHING",
payload: API.doSomething(object)
}
}
This is just a little decoration suggestion(not obligated)
export const doSomething = object => ({
type: "DO_SOMETHING",
payload: API.doSomething(object)
})
Now the answer:
First of all you need check if API.doSomething(object) throws an Error
Or returns Promise.reject() on case of 400 respond code
If it doesn't you need to make it throw
if(res.code === 400) {
throw new Error()
}
// Or just use a library like axios
Promise doesn't know what happened inside you code,
in order to get to DO_SOMETHING_REJECTED
Is to make the API.doSomething(object) throw in 400
Related
import api from "api";
import { Loader } from "components/base";
import { useReducer } from "react";
import { useQuery } from "react-query";
function reducer(state, { type, payload }) {
switch (type) {
case "init":
return state.concat(payload);
default:
throw new Error();
}
}
function Table() {
const [workers, dispatch] = useReducer(reducer, []);
const fetchWorkers = async () => {
const workersData = await api.index();
return workersData;
};
const { status, data, error } = useQuery("", fetchWorkers);
switch (status) {
case "loading":
return <Loader />;
case "error":
return <p className="text-red-600">{error.message}</p>;
case "success":
dispatch({ type: "init", payload: data });
return <p>Success!</p>;
default:
return <p>💩</p>;
}
}
export default Table;
The above code causes infinite re-renders? Why? 😕
Once useQuery communicates that status === "success", why can't I just dispatch and initialize my data? How else should I be doing this instead? 😖
I removed useQuery and just did with a useEffect without any issue - the data came back and I dispatched.
What is different here with useQuery?
Your code is causing infinite re-renders because you're calling dispatch every render after the data has loaded:
case "success":
// since the switch() statement runs every render, as long as it's "success"
// we'll call dispatch, update the reducer, and then force a re-render
// thus causing a loop
dispatch({ type: "init", payload: data });
return <p>Success!</p>;
There are two ways you can avoid this.
Don't put the data into your reducer. It doesn't look like you have any reason to do that in your sample code, and in case the data is needed in more places, you could potentially just call useQuery there as well (I'm not terribly familiar with the library but I imagine they have some caching strategy).
If you really need the data in the reducer, do the dispatch within an effect so it only runs the moment status changes to success:
React.useEffect(() => {
if (status === 'success') {
dispatch({ type: 'init', payload: data });
},
}, [status]); // since `status` is in the dependency, this effect only runs when its value changes
General lesson is try to avoid calling state-changing functions directly within the render body of your component--prefer effects and callbacks.
I am having a problem, I believe many of you have faced it somehow, so I am using axios to get my data :
let data = axios.get(
"http://localhost:8080/api/taskExecution?&search=&page=1&size=8"
).then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
let tasks = [];
data.then(response => {
tasks = response;
});
console.log(tasks);
return tasks;
Something like this, response data returns array of 8 items, but tasks is still empty which is normal because the axios request takes some time, I can use setTimeout for 100ms and inside it, I put console.log(tasks); and it will work but is not a proper solution, because what if the server takes 5s to returns the data?
This code is in my reducer, so I have to get my tasks and return them so I can display them, and show a loader when the request is executed.
This is my reducer :
import update from "immutability-helper";
import Axios from "axios";
export default function getTasksReducer(state = [], action) {
switch (action.type) {
case "GET_TASKS":
let data = Axios.get(
"http://localhost:8080/api/taskExecution?&search=&page=1&size=8"
).then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
let tasks = [];
data.then(response => {
tasks = response;
});
console.log(tasks);
return tasks;
default:
return state;
}
}
I need some help in this code and also in the conception of it, I mean where should I change my loader state and so forth.
I guess you can spent some time in understanding Promises and async/awiat
For eg: if you do this, your console will have all tasks listed.
data.then(response => {
tasks = response;
console.log(tasks);
});
Reason for that is the function you are passing to .then function of a promise is not executed immediately, its executed once a Promise is resolved(In your case after finishing execution of the http request.)
To get a more line by line execution feel you can use async/await
let tasks = await Axios.get("http://localhost:8080/api/taskExecution?&search=&page=1&size=8")
.then(response => {
if (response.status !== 200) {
console.log("LOADING");
} else {
return response.data;
}
});
console.log(tasks);
return tasks;
The catch is that you can use await only inside an async function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Alright buddy, so there're a few things I want to raise up:
First, you should always use your reducers only return new state to Redux, and that's it. You just merge new data that comes from actions, with your old state and return it back. And you can't use Promise or async/await in there because Redux doesn't and won't support that behavior.
Second, all the business logic should be placed in your actions. Data fetching(like in your case), computations and that kind of stuff needs be in actions. And now, you've come to the point where you, most likely, should start using libraries like redux-thunk or redux-saga to handle asynchronous operations within your actions. redux-thunk is less complicated than redux-saga, but redux-saga empowers you with a lot of cool features yet it could a bit complicated.
You can go big or start small with these libs, either way, they will force you to move you data fetching into your action. If you want support loading of data, then just dispatch actions that tell Redux: "I'm loading data" or "I got an error while loading data" or "I've loaded data". And when that action comes in, update your store, show loader, data or an error if you need. You can take a look on this or that example of using redux-thunk for data fetching, there's everything you need to get started with async actions.
Hope it helps <3
As mentiond as in the other answers, I would setup my reducer only to handle state update and the action creator function to fetch data. Here is the minimal starting point if I were to do it. you can use the loading state to display loading spinners or progress bars.
my action creator function with the individual actions:
export const GET_TASKS_START = "GET_TASKS";
export const GET_TASKS_SUCCESS = "GET_TASKS_SUCCESS";
export const GET_TASKS_FAILURE = "GET_TASKS_FAILURE";
export const getData = () => dispatch => {
dispatch(GET_TASKS_START);
axios
.get("http://localhost:8080/api/taskExecution?&search=&page=1&size=8")
.then(response => {
dispatch({ type: GET_TASKS_SUCESS, payload: response.data });
})
.catch(err => {
dispatch({ type: GET_TASKS_FAILURE, payload: err.response });
});
};
and the reducer would handle the state update as follows:
import {
GET_TASKS_SUCCESS,
GET_TASKS_FAILURE,
GET_TASKS_START
} from "./Action.js";
const initialState = {
tasks: [],
error: null,
loading: false
};
export default function tasksReducer(state = initialState, action) {
switch (action.type) {
case GET_TASKS_START:
return {
...state,
loading: true
};
case GET_TASKS_SUCCESS:
return {
...state,
loading: false,
tasks: action.payload,
error: null
};
case GET_TASKS_FAILURE:
return {
...state,
loading: false,
error: action.payload
};
}
}
I would suggest console logging and checking the responses (data and errors ) and modify the two functions accordingly
First, I want to call the action 'CLICK_SEARCH' to get the response from the server
then pass the response to 'CLICK_SEARCH2' to update the store.
But its failure, the console result showing 'CLICK_SEARCH' instead of 'CLICK_SEARCH2' and action.payload is undefined. Anyone help? thx
function reducreForSeach(state = initialState3, action) {
if (typeof state === 'undefined') {
return 0
}
switch(action.type) {
case 'CLICK_SEARCH': {
axios.post('abc/url', {
responseType: 'document',
headers: { 'X-Requested-With': 'XMLHttpRequest'},})
.then(function(response) {
dispatch({
type: 'CLICK_SEARCH2',
payload: response.data});
}
case 'CLICK_SEARCH2': {
console.log(action.type);
console.log(action.payload);
}
}
Have you tried declaring constants for action types? Simple as this one:
const CLICK_SEARCH = 'CLICK_SEARCH';
const CLICK_SEARCH2 = 'CLICK_SEARCH2';
function reducreForSeach(state = initialState3, action) {
//...other parts of the code
switch(action.type) {
case CLICK_SEARCH: {
//...
dispatch({ type: CLICK_SEARCH2,
//...
case CLICK_SEARCH2: {
//...
dispatch is never defined or fed to the function reducreForSeach, so it will always fail to call CLICK_SEARCH2.
Chaining actions are not directly supported by Redux. You can implement by yourself, but I suggest you to look into popular libraries like redux-thunk or redux-saga to learn how to call actions based on other action's result. They are all well-documented and recommended in Redux's official website
You don't want to do such things in reducers as those are meant to be pure functions.
You can dispatch other actions from within action generators by using redux-thunk which enables you to return a function from your action generators.
The function you return will be called with dispatch as its props and allows you to further dispatch actions, from within the callback.
e.g.
export const mySearchActionGenerator = keyword => dispatch => {
dispatch ({
type: actionTypes.MY_SEARCH_ACTION
keyword
});
dispatch ({
type: actionTypes.MY_SEARCH_ACTION
keyword
});
}
Another option would be using redux-saga which allows far more complex setups.
I am currently developing an application that is a copy of Instagram. It works with React-Redux, calls an external API via axios to fetch photos that are actually blog posts. I need to also pass the amount of likes (so 0) for each one, that I am adding in my fetchPhotos action creator, which causes my application to crash. This works fine whenever the action creator is only returning type and payload.
When I console logged the action it actually turned out that the promise is now not being resolved and thus followed the error.
Action creator:
export function fetchPhotos() {
const response = axios.get("https://dog.ceo/api/breed/husky/images");
return {
type: FETCH_PHOTOS,
payload: response,
likes: 0
};
}
Reducer:
export default function(state = [], action) {
switch(action.type) {
case FETCH_PHOTOS:
console.log(action);
return [action.payload.data.message, action.likes];
default:
return state;
}
}
In App:
const history = createBrowserHistory();
const store = createStore(
connectRouter(history)(reducers),
compose(applyMiddleware(routerMiddleware(history), ReduxPromise))
);
Is there any way to make the action creator actually resolve this promise inside the action.payload?
For documentation:
As #fshauge mentioned, the payload has to be a promise and adding the property of likes breaks it. I found this in the issues, which has solved my issue. The likes property actually has to go into meta, so the end result that functions correctly is:
export function fetchPhotos() {
const response = axios.get("https://dog.ceo/api/breed/husky/images");
return {
type: FETCH_PHOTOS,
payload: response,
meta: {
likes: 0
}
};
}
According to the documentation of redux-promise, it expects either a promise or a Flux Standard Action (FSA) where the payload is a promise. Since adding the 'likes' property breaks FSA-compliancy, it has to be inside the payload somehow.
I suggest returning a promise directly from the action creator with the 'likes' property embedded as follows:
export async function fetchPhotos() {
const response = await axios.get("https://dog.ceo/api/breed/husky/images");
return {
type: FETCH_PHOTOS,
payload: {
data: response.data,
likes: 0
}
};
}
redux-promise will automatically call dispatch when the promise resolves, and you can use the following code in your reducer:
export default function (state = [], action) {
switch (action.type) {
case FETCH_PHOTOS:
console.log(action);
return [action.payload.data.message, action.payload.likes];
default:
return state;
}
}
I'm currently building out a Redux application sort of like Reddit. To learn about state management.
I have now come across an issue I can't get my head around, when I vote on a post I can see the UI changing(+1/-1 vote) but just a second late in prompt with an error.
Unhandled Rejection (TypeError): Cannot read property 'id' of undefined
My component is structured as following
API file
export const submitPostVote = (option, id) => {
return axios.post(`${API_URL}/posts/${id}`, {option}, { headers })
}
Action Creator (using redux-thunk)
export function postPostVote(option, id) {
const request = API.submitPostVote(option, id)
return (dispatch) => {
request.then(({data}) => {
dispatch({type: SUBMIT_POST_VOTE, payload: data})
});
};
}
Reducer
export default function(state = {}, action) {
const {payload} = action
switch (action.type){
case SUBMIT_POST_VOTE:
return {...state, [payload.data.id]: payload.data}
Component that uses it
import { postPostVote } from '../actions';
<span
className='fa fa-angle-up voteArrow'
onClick={() => this.props.postPostVote('upVote', id)}
></span>
export default connect(null, {postPostVote})(PostComponent);
So I'm trying to pass along the option(upVote || downVote) as well as an id but I cant understand why it returns the error after a can see the change
Much Appreciated,
Petter
It looks like you've got a mismatch between how you're defining fields in the action, and using the action in the reducer.
You're defining your action as {type: SUBMIT_POST_VOTE, payload: data}, but in the reducer, using action.payload.data.id. I assume that payload.data won't actually exist, and what you really need is action.payload.id.