How to get data from my axios request without using a setTimeout? - reactjs

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

Related

How Do I Use useReducer with useQuery?

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.

useEffect returning context function with null value

I am using a context configuration (not redux) that consists of three files. A state file, reducer file, and context file. It makes an axios call to my Atlas MongoDB collection. I have tested the API using Postman and the get request works as intended, returning a series of transaction data.
The getTransactions() function in the useEffect hook will only return the requested data once. If I make any modifications to the code and save or navigate to a different page and return, the transactions array of objects is now defined as null. My first instinct was that I had an asynchronous syntax error but my understanding of useEffect is that it mitigates async issues.
The function in question looks like this:
const initialState = {
transactions: null,
current: null,
filtered: null,
error: null
};
const getTransactions = async () => {
try {
const res = await axios.get("/api/transactions");
dispatch({ type: GET_TRANSACTIONS, payload: res.data });
} catch (err) {
dispatch({ type: TRANSACTION_ERROR, payload: err.response.msg });
}
};
The reducer handles the type dispatched by the state function inside a switch statement:
case GET_TRANSACTIONS:
return {
...state,
transactions: action.payload,
loading: false,
};
I make the following call in my react component:
useEffect(() => {
getTransactions();
// eslint-disable-next-line
}, []);
My API is running on a node server that uses a route that first checks for user auth and then queries the database. Again, the API has been tested and returns the data as expected given there is a valid JWT auth token in the headers. The route is below:
router.get("/", auth, async (req, res) => {
try {
const transactions = await Transaction.find({ user: req.user.id }).sort({
date: -1,
});
res.json(transactions);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
I need to be able to have the data returned consistently in my component in an array of objects called transactions so it can be mapped to populate a DOM element. I have tried to provide as much of the code here as I felt was relevant but if I'm missing something, I can share that. All required imports have been doublechecked and are correct. I have not included the import lines in this question to conserve space. Thanks in advance!

Redux-promise not resolving a promise when in the action there is a number

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;
}
}

Redux: Is there any standard implementation or library for keeping track of the status of an async request?

In redux async actions in the docs the state of an async request is kept as a property isFetching in the state container for various objects:
{
selectedSubreddit: 'frontend',
postsBySubreddit: {
frontend: {
isFetching: true,
didInvalidate: false,
items: []
},
reactjs: {
isFetching: false,
...
This works fine however I'm looking to build out my application and I'm looking for design patterns that will scale up across multiple objects that must be kept in my state container and synchronized with my api. So I'm looking for standards or libraries that the redux community have adopted.
I found the Flux Standard Action which looks quite reasonable but this is more a standardization of how to handle payloads and errors, not the status of an async request.
Is there any library or pattern that a lot of redux developers are working with? I would think there might be something like { success, isFetching, error }.
Take a look at this library, use it like you want.
In my app I have use it like that, first you add it to your middleware in the store configuration. After this one you setup your action to be a promise and the payload is the promise.
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
After your can setup your reducer like:
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
Hope that can help ;)
Yep, there's a very wide variety of addons for Redux, and many of those related to async behavior. My Redux addons catalog lists pretty much all of them. There's middlewares for handling async behavior, utilities to generate actions describing async work, prebuilt libs to track request status, and more.

Is it a good idea to use browserhostory.push in action helpers?

In my React App I need to take decision based on data I receive from the server.
If data is expected ( Dispatch actions to update state)
If data has error tag ( browserhistory.push('/notfound'); )
If expected data is unable to parsed ( browserhistory.push('/error');)
In my app structure, I am using Redux, React-Router and React-redux-Router libraries but no middleware. I have made actionHelpers to making ajax calls and then dispatch appropriate actions using Action Creator. These actionHelper methods are exposed in Components to change state.
My Questions:
What's the best way to handle these scenarios ?
Is actionHelper the best place to take these decisions ?
I don't want to use any middleware for now but please let me know if its a good idea to use middleware to handle these scenarios.
Actions are not the place where you should do redirections. This behavior should be implemented in the component itself and actions should be left to update the store.
You may want to use the Redux-thunk middleware here which allows you to dispatch a function (which receives dispatch as an argument instead of the object actions. You can then wrap that function in a promise and use it in componentWillMount.
In your actions file:
updateReduxStore(data) {
return {
type: SOME_TYPE,
payload: data.something
};
}
fetchAndValidateData() {
...
}
checkData() {
return function(dispatch) {
return new Promise((resolve, reject) => {
fetchAndValidateData().then((data) => {
try {
if (JSON.parse(data).length > 0) {
dispatch(updateReduxStore(data));
resolve('valid data');
} else if (data.error) {
reject('error in data');
}
}
catch(err) {
reject('malformed data');
}
});
});
};
}
Then in your component:
componentWillMount() {
this.props.checkData()
.then((message) => {
console.log(message); //valid data
})
.catch((err) => {
if (err === 'error in data') {
browserHistory.push('/notfound');
} else if (err === 'malformed data') {
browserHistory.push('/error');
}
});
}
Redux-thunk middleware is made for such use cases.

Resources