Using the same data dependently in the same useEffect - javascript

I need to fetch my data in two different ways and render it according to this. At the first load, I need to fetch all the items one by one and increment the count. After that, I need to fetch all the data at once and update the display. So, I wrote something like this (not the actual code but almost the same thing):
import React, { useEffect } from "react";
import axios from "axios";
import { useGlobalState } from "./state";
const arr = Array.from(Array(100), (x, i) => i + 1);
function App() {
const [{ posts }, dispatch] = useGlobalState();
useEffect(() => {
const getInc = () => {
arr.forEach(async id => {
const res = await axios(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
dispatch({
type: "INC",
payload: res.data
});
});
};
const getAll = async () => {
const promises = arr.map(id =>
axios(`https://jsonplaceholder.typicode.com/posts/${id}`)
);
const res = await Promise.all(promises);
dispatch({
type: "ALL",
payload: res.map(el => el.data)
});
};
if (!posts.length) {
getInc();
} else {
getAll();
}
}, [dispatch]);
return (
<>
<div>{posts.length}</div>
</>
);
}
export default App;
I'm simply using Context and useReducer to create a simple store. The above code works as it is but I skip adding posts.length dependency and this makes me think that my logic is wrong.
I tried to use refs to keep track the initialization state but I need to track the data at every route change. Then, I tried to keep it by adding an init state to my store but I couldn't make it work without problems. For example, I can't find a suitable place to dispatch the init. If I try it after a single fetch it triggers the initialization immediately and my other function (getAll) is invoked.
If anyone wants to play with it here is a working sandbox: https://codesandbox.io/s/great-monad-402lb

I added init to your store:
// #dataReducer.js
export const initialDataState = {
init: true,
posts: []
};
const dataReducer = (state, action) => {
switch (action.type) {
case 'ALL':
// init false
return { ...state, posts: action.payload };
case 'INC':
return { ...state, init: false, posts: [...state.posts, action.payload] };
...
}
// #App.js
function App() {
const [{ init, posts }, dispatch] = useGlobalState();
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
...
}

Related

A property of the state changed by useReducer() is never displaying changes even when those are dispatched

I have been working with useReducer() so that I can share some state among some components. There is a property in the requestReducer called isLoading which default/initial state is set to false. There are two functions dispatching actions to the reducer (initializeLoading: changing isLoading to true, and getRequestResponse: changing isLoading back to false). But when the function that the reducer uses to initialize loading -initializeLoading-, is called by CallingComponent that uses the pointer of those functions to call them; the change of state inside the useEffect (which has a dependency for the requestState.isLoading), is never displayed in the console or called neither in the useCurrentRequest hook nor the CallingComponent, when it is supposed to be called; because in that first calling the isLoading prop, from false, turns into true, through the SEND action.type. Moreover when calling the getRequestResponse function (which launches a RESPONSE action.type => isLoading = false) from the CallingComponent, it should switch from true, to false, thereby it should be displayed also by the useEffect that is supposed to capture those changes. But nothing of that happens, instead nothing is displayed.
Here is the useCurrentRequest and the requestReducer:
import {useReducer, useCallback, useEffect} from 'react';
export const initialState = {
isLoading: false,
data: null,
}
const requestReducer = (state = initialState, action) => {
switch (action.type) {
case 'SEND':
return {...state,
isLoading: true,
};
case 'RESPONSE':
return {...state,
isLoading: false,
data: action.responseData
};
default:
return state;
}
}
//This is the hook with the logic that sets state using the reducer
const useCurrentRequest = () => {
const [requestState, dispatchRequestActions] = useReducer(requestReducer, initialState);
//The loading change of state is not displayed in the console
useEffect(() => {
console.log(requestState.isLoading);
}, [requestState.isLoading]);
const initializeLoading = useCallback(() => {
dispatchRequestActions({type: 'SEND'});
console.log(requestState);
},[]);
const getRequestResponse = useCallback(() => {
//some call to an api happens here
//Then the response is given using the reducer
dispatchRequestActions({type: 'RESPONSE', responseData: {someData:true}});
}, []);
return {
initializeLoadingPointer: initializeLoading,
getRequestResponsePointer: getRequestResponse,
isLoading: weatherRequestState.isLoading,
data: weatherRequestState.data,
}
}
export default useCurrentRequest;
And here is the CallingComponent, which uses the function pointers provided by the useCurrentRequest hook:
import {useEffect} from "react";
import useCurrentRequest from "../hooks/useCurrentRequest";
const CallingComponent = props => {
const {initializeLoadingPointer, getRequestResponsePointer, isLoading} = useCurrentWeather();
const getData = () => {
initializeLoadingPointer();
getRequestResponsePointer();
};
useEffect(() => {
console.log(isLoading);
}, [isLoading]);
return (<button onClick={getData}>Get data</button>)
}
The problem basically is that nothing is displayed after the Get Data button is clicked, when there is supposed to display in the console, true and false in that respective order, because of the useEffect() depending on isLoading and those functions changing isLoading, but again nothing is displayed.
I will appreciate any help on this.
Thanks! :)
I finally solved the issue by using async-await, the problem was that since the requests to those functions that were modifying the reducer were happening synchronously, the state was only updated until both of them were finished, and the function that was calling them has already finished too. So what I did was to block some pieces of code using promises via de async-await mode. Basically, I only modified useCurrentRequest() hook by replacing those 2 functions by this:
const useCurrentRequest = () => {
const [requestState, dispatchRequestActions] = useReducer(requestReducer, initialState);
useEffect(() => {
console.log(requestState.isLoading);
}, [requestState.isLoading]);
const sendRequestAsync = useCallback(async (city) => {
const query = `weather?q=${city}`;
console.log(query);
dispatchRequestActions({ type: "SEND" });
try {
const result = await fakeRequest();
dispatchRequestActions({ type: "RESPONSE", responseData: result.data });
} catch (error) {
dispatchRequestActions({ type: "ERROR", errorMessage: error.message });
}
}, []);
const fakeRequest = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ data: "data" });
}, 1000);
});
};
return {
getRequestResponsePointer: sendRequestAsync,
isLoading: weatherRequestState.isLoading,
data: weatherRequestState.data,
}
}
export default useCurrentRequest;
That way I was able to see the changes using the useEffect() hook.

Is it a good practice to use flags when working with many async functions at the same time in React?

I'm working on a React project and I reuse a fetchAPIcall action since I make 3 different initial API calls, and further, I plan on using more to add and edit my Items.
So to have control over the correct order of the API call I tried using a flag at the end, being a state of the component.
And since I am using many API calls, to add some Items to favorites and be removed quickly by a like button, I'd like to know what is the best practice when using many async functions or API calls?
I can think of only 1) using flags, and 2) having the API call-actions separate for each, but in my case that would be a lot of code (get user)(get, add, delete FavList)(get Items)(add, edit remove Item).
By the way, the API is mine, made it in rails.
Here are the main code&files for my issue:
This is from my GetItemsNFavlist Component, this is where I load all the info of items and favList items. I made it into a component that I call because I thought it was a good idea so when I add an Item to the Favorites List I can just call this component to update my FavoritesList (but that 'updating' part isn't working great just yet, I'm having to go back to the User and again to the Fav List to see the update or even logout and in again to see the change).
Here I call the action "fetchAPIcall" and I check the status and response data with the "fetchCall" store object. Also here I do 2 API calls, 1) to get all the Items and 2) to get the FavoritesList for the User:
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import * as MyActions from '../actions';
const GetItemsNFavlist = props => {
const {
actions, items, fetchCall, favList, user,
} = props;
const [apiFlag, setApiFlag] = useState({ itm: false, fvl: false });
const itemsUrl = 'https://findmyitem-api.herokuapp.com/items';
const favListUrl = `https://findmyitem-api.herokuapp.com/users/${user.id}/favorites_lists`;
useEffect(() => { // #1
if (!apiFlag.itm && !apiFlag.fvl) actions.fetchAPIcall(itemsUrl, 'get', {});
}, []);
useEffect(() => {
if (!fetchCall.apiData && items[0]) {
actions.fetchAPIcall(favListUrl, 'get', {});
setApiFlag({ itm: true, fvl: false });
}
}, [items]);
useEffect(() => {
if (fetchCall.apiData && !items[0] && !favList[0]) {
actions.setItems(fetchCall.apiData);
actions.fetchAPIreset();
}
if (apiFlag.itm && fetchCall.apiData && !favList[0]) actions.setFavList(fetchCall.apiData);
});
useEffect(() => {
if (favList[0]) {
actions.fetchAPIreset();
setApiFlag({ itm: true, fvl: true });
}
}, [favList]);
return (<> </>);
};
GetItemsNFavlist.propTypes = {
user: PropTypes.objectOf(PropTypes.any).isRequired,
actions: PropTypes.objectOf(PropTypes.any).isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
favList: PropTypes.arrayOf(PropTypes.any).isRequired,
fetchCall: PropTypes.objectOf(PropTypes.any).isRequired,
};
const mapStateToProps = ({
user, items, fetchCall, favList,
}) => ({
user, items, fetchCall, favList,
});
function mapActionsToProps(dispatch) {
return {
actions: bindActionCreators({ ...MyActions }, dispatch),
};
}
export default connect(mapStateToProps, mapActionsToProps)(GetItemsNFavlist);
And these are my actions (actions/index.js), where I have the API call function:
import axios from 'axios';
const addUsername = username => ({
type: 'SET_NAME',
username,
});
const setUserInfo = user => ({
type: 'SET_USER',
user,
});
const setItems = items => ({
type: 'SET_ITEMS',
items,
});
const setFavList = favList => ({
type: 'SET_FAVLIST',
favList,
});
const fetchAPIbegin = callHeader => ({
type: 'FETCH_API_BEGIN',
callHeader,
});
const fetchAPIsuccess = payload => ({
type: 'FETCH_API_SUCCESS',
payload,
});
const fetchAPIfailure = error => ({
type: 'FETCH_API_FAILURE',
payload: error,
});
const fetchAPIsuccesResp = payload => ({
type: 'FETCH_API_SUCCESS_RESP',
payload,
});
function handleErrors(response) {
if (!response.ok && response.error) { throw Error(JSON.stringify(response)); }
return response;
}
function fetchAPIcall(url, restAct, options) {
return dispatch => {
dispatch(fetchAPIbegin(url, options));
setTimeout(() => axios[restAct](url, options)
.then(handleErrors)
.then(rsp => {
dispatch(fetchAPIsuccesResp(rsp));
return rsp;
})
.then(resp => resp.data)
.then(jsonResp => dispatch(fetchAPIsuccess(jsonResp)))
.catch(err => dispatch(fetchAPIfailure(`${err}`))), 1000);
};
}
const fetchAPIreset = () => ({ type: 'FETCH_API_RESET' });
export {
addUsername,
setUserInfo,
setItems,
setFavList,
fetchAPIcall,
fetchAPIbegin,
fetchAPIsuccess,
fetchAPIfailure,
fetchAPIreset,
fetchAPIsuccesResp,
};
And Just in case, this is the link to my repo: find-my-item repo.
Thanks in advance!!
Best regards

How to render the page after fetching the data

I have the following function to check the users
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem("token");
const email = localStorage.getItem("email");
if (token === undefined) {
dispatch(logout());
} else {
const expirationDate = new Date(localStorage.getItem("expirationDate"));
if (expirationDate <= new Date()) {
dispatch(logout());
} else {
dispatch(authSuccess(email, token));
dispatch(
checkAuthTimeout(
(expirationDate.getTime() - new Date().getTime()) / 1000
)
);
}
}
};
};
And for that i did the special initialization function that will take all the needed functions and dispatch them
initialState = {
initialized: false
};
const appReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZED_SUCCESS:
return {
...state,
initialized: true
}
default:
return state;
}
}
export const initializedSuccess = () => ({type: INITIALIZED_SUCCESS});
export const initializeApp = () => (dispatch) => {
let promise = dispatch(authCheckState());
Promise.all([promise])
.then(() => {
dispatch(initializedSuccess());
});
}
And after that i am putting initialization function to the App's componentDidMount function
componentDidMount() {
this.props.initializeApp();
}
and after i am doing that
const mapStateToProps = (state) => ({
initialized: state.app.initialized
})
but after that i am still getting the data too late and i have to refresh the page to see the data. How to implement this feature?
You should fetch data in the component itself. Use Redux for storing the data so that you can use it later or manipulate it. (If you need to do so!)
This how the process should be,
Make an API request on componentDidMount.
Store the data in redux.
Use that data in the component coming in the form of props using Redux.

React functional components with hooks - getting response from function written in reducer

Need help in getting response from a function written inside reducer function
functional component
import {
getAssets,
} from '../../reducers';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = () => {
dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In my reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case ASSETS_LIST: {
return {
...state,
successToast: true,
isLoading: false,
data: action.payload,
};
}
}
export const listsDispactcher = () => dispatch => {
dispatch({ type: SHOW_LOADER });
performGet(ENDPOINT URL)
.then(response => {
debugger;
const payload = response.data;
dispatch({
type: ASSETS_LIST,
payload: {
...payload,
data: payload.results,
},
});
dispatch({ type: HIDE_LOADER });
})
.catch(err => {
dispatch({ type: GET_ASSETS_ERROR, payload: err });
);
});
};
when i click the link ,am getting my api called in function in reducer and its getting response in newtwork tab in developer console , but how to get the response (that is successToast,data,isLoading )in my functional component and to pass the same to child components ?
I advice you to change the structure of your project. Place all your network calls in a file and call them from your component. It is better for readability and understandability
import {
getAssets,
} from './actions';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = async () => {
const data = await dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In ./actions.js
const getAssets =()=>async dispatch =>{
const res = await axios.get();
dispatch(setYourReduxState(res.data));
return res.data;
}
Now your component will get the data of network call. and Your redux state also will get update
For functional components, to access state stored centrally in redux you need to use useSelector hook from react-redux
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
Official doc:
https://react-redux.js.org/api/hooks#useselector-examples
Also found this working example for you to refer.
https://codesandbox.io/s/8l0sv

Can't use map function after Dispatch Action, React + Redux

So i'm doing a API GET request and set the data on reducer, but the component render twice, first before dispatch and another after, the first one is causing map function problem
what can i do to avoid render twice and solve map function problem?
App.js
componentDidMount(){
this.props.carregarLojas();
}
render(){
const { lojasTeste } = this.props;
//rendering 2 times
console.log(lojasTeste);
return(
<div>
lojasTeste.map((i, index) => (
<h1>{i.name}</h1>
))
</div>
)
}
const mapStateToProps = store => ({
lojasTeste: store.lojaState.lojasTeste
});
const mapDispatchToProps = dispatch => {
return {
carregarLojas: () => {
dispatch(carregarLojas());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Action.js
export const setarLojas = (lojas) =>{
return {
type: SETAR_LOJAS,
data: lojas
}
}
export const carregarLojas = () => {
return (dispatch) => {
return API.get('loja')
.then(response => {
dispatch(setarLojas(response.data))
})
.catch(error => {
throw(error);
})
}
Reducer.js
const initialState ={
lojasTeste: {}
}
export const lojaReducer = (state = initialState, action) => {
switch (action.type){
case SETAR_LOJAS:
return {
...state,
lojasTeste: action.data
}
default:
return state;
}
}
The double render is totally normal:
Your component render once, then call the carregarLojas method which is async. When resolved, the method will update your redux store, which is connected with the props of your component (mapStateToProps). When a prop is updated, it cause automatically a rerender.
Also, for your map problem, you didn't initialized lojasTeste as an array, but as an object. You can't use map on an object (cf https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)

Categories