useEffect goes in infinite loop when combined useDispatch, useSelector of Redux - reactjs

I try to code with react-hook and redux for state management and axios for database requests with Thunk as middleware for handling asynchronicity.I'm having an issue in one component that does a get request to retrieve a list of customers on what used to be componentwillreceiveprop
# Action
export const actGetProductRequest = id => dispatch =>
callApi(`products/${id}`, "GET").then(res =>
dispatch({
type: types.EDIT_PRODUCT,
payload: { product: res.data }
})
);
------
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import useForm from "./useForm";
import { actGetProductRequest } from "../../actions";
...
const { history, match } = props;
const getProduct = useSelector(state => state.itemEditing);
const dispatch = useDispatch();
const { values, setValues, handleChange, handleSubmit } = useForm(
getProduct,
history
);
useEffect(() => {
if (match) {
let id = match.params.id;
dispatch(actGetProductRequest(id));
}
setValues(()=>({...getProduct}));
},[match,dispatch, setValues, getProduct]);
And I tried to call API and return the product, but the site always loop render infinite. Continuous rendering cannot be edited the form. like the
Can anyone please help me out how to resolve this issue...
p.s: with code here, it run ok. But I want to use with redux, and I pass the code calling axios to the dispatch and redux to return the new changed state
useEffect(() => {
if (match) {
let id = match.params.id;
callApi(`products/${id}`, "GET", null).then(res => {
const data = res.data;
setValues(()=>({...data}));
});
}
const clearnUp = () => setValues(false);
return clearnUp;
},[match, setValues]);
p.s: full code
code

Just get the id from the history > match
const { history, match } = props;
const id = match ? match.params.id : null;
You don't need to add dispatch to the dependencies. Instead of match, use id. And you can skip the method references. Since setValues is basically a setState call, it can be skipped too (Read React docs). But if you do want to use any function references in dependecies, make sure you wrap them with useCallback.
useEffect(() => {
if (id) {
dispatch(actGetProductRequest(id));
}
setValues(()=>({...getProduct}));
},[id, getProduct]);
Your main issue might be with the match object. Since, history keeps changing, even if the id is the same.

Related

How can I render the data that I got from the response that I did in redux-thunk and put it in useEffect?

I want to render the fullName string from response from the API call that I did in redux-thunk, and put it in useEffect with dispatch (useDispatch() hook). If I return nothing in JSX and if I console.log the state it is actually passes, and I can see the whole data in the console. But if I want to render any part of the data, I get an error:
Uncaught TypeError: Cannot read properties of null (reading 'fullName').
const Profile = (props) => {
let { userId } = useParams();
const dispatch = useDispatch();
const state = useSelector((state) => state);
useEffect(() => {
dispatch(getUserProfile(userId));
}, []);
return <div>
{state.profile.profile.fullName} //error if i want smth to render, two profile because one from redux's initialState, and one from api.
</div>;
};
export default Profile;
THUNK
export const getUserProfile = (userId) => (dispatch) => {
profileAPI.getProfile(userId).then((response) => {
debugger
dispatch(setProfile(response));
});
};
I tried conditional rendering, but it didnt get me the solution. Ive tried a lot ways of conditional rendering, and no one of them was helpful
Can you Please share Profile Reducer Code also because as per my observation Profile is like an object
export const initialState = {
profile:{fullName:""};
}
export const profileReducer =(state = initialState,action={})=>{
..
}
and you want to get only profileReducer Data then get only profileReducer data by state.profileReducer an get the value as const {profile}
const {profile} = useSelector((state) => state.profileReducer);
for testing check if are you able to getting proflie data by
useState(()=>{console.log("profile",profile)},[profile])

Update single element of the array in redux state

I have a redux state that contains an array of objects, for each of these object I call an api to get more data
objects.forEach((obj, index) => {
let newObj = { ...obj };
service.getMoreData()
.then(result => {
newObj.data = result;
let newObjects = [...this.props.objectsList] ;
let index = newObjects.findIndex(el => el.id === newObj.id);
if (index != -1) {
newObjects[index] = newObj;
this.props.updateMyState({ objectsList: newObjects });
}
})
When I get two very close responses the state is not updated correctly, I lose the data of the first response.
What is the right way to update a single element of the array? Thanks!
So since i don't know what service is and there isn't that much here to go off, here is what I would do from my understanding of what it looks like your doing:
So first let's set up a reducer to handle the part of redux state that you want to modify:
// going to give the reducer a default state
// array just because I don't know
// the full use case
// you have an id in your example so this is the best I can do :(
const defaultState = [{ id: 123456 }, { id: 123457 }];
const someReducer = (state=defaultState, action) => {
switch (action.type) {
// this is the main thing we're gonna use
case 'UPDATE_REDUX_ARRAY':
return [
...action.data
]
// return a default state == the state argument
default:
return [
...state
]
}
}
export default someReducer;
Next you should set up some actions for the reducer, this is optional and you can do it all inline in your component but I'd personally do it this way:
// pass data to the reducer using an action
const updateReduxArray = data => {
return {
type: 'UPDATE_REDUX_ARRAY',
data: data
}
}
// export like this because there might
// be more actions to add later
export {
updateReduxArray
}
Then use the reducer and action with React to update / render or whatever else you want
import { useState } from 'react';
import { updateReduxArray } from 'path_to_actions_file';
import { useEffect } from 'react';
import { axios } from 'axios';
import { useSelector, useDispatch } from 'react-redux';
const SomeComponent = () => {
// set up redux dispatch
const dispatch = useDispatch();
// get your redux state
const reduxArray = useSelector(state => state.reduxArray) // your gonna have to name this however your's is named
// somewhere to store your objects (state)
const [arrayOfObjects, updateArrayOfObjects] = useState([]);
// function to get data from your API
const getData = async () => {
// I'm using axios for HTTP requests as its pretty
// easy to use
// if you use map you can just return the value of all API calls at once
const updatedData = await Promise.all(reduxArray.map(async (object, index) => {
// make the api call
const response = axios.get(`https://some_api_endpoint/${object.id}`)
.then(r => r.data)
// return the original object with the addition of the new data
return {
...response,
...object
}
}))
// once all API calls are done update the state
// you could just update redux here but this is
// a clean way of doing it incase you wanna update
// the redux state more than once
// costs more memory to do this though
updateArrayOfObjects(updatedData)
}
// basicity the same as component did mount
// if you're using classes
useEffect(() => {
// get some data from the api
getData()
}, [ ])
// every time arrayOfObjects is updated
// also update redux
useEffect(() => {
// dispatch your action to the reducer
dispatch(updateReduxArray(arrayOfObjects))
}, [arrayOfObjects])
// render something to the page??
return (
<div>
{ reduxArray.length > 0
? reduxArray.map(object => <p>I am { object.id }</p>)
: <p>nothing to see here</p>
}
</div>
)
}
export default SomeComponent;
You could also do this so that you only update one object in redux at a time but even then you'd still be better off just passing the whole array to redux so I'd do the math on the component side rather than the reducer .
Note that in the component I used react state and useEffect. You might not need to do this, you could just handle it all in one place when the component mounts but we're using React so I just showcased it incase you want to use it somewhere else :)
Also lastly I'm using react-redux here so if you don't have that set up (you should do) please go away and do that first, adding your Provider to the root component. There are plenty of guides on this.

How to stop useSelector from invoking multiple times

I have a component like below -
const MyComponent = () => {
const bankingAccounts = useSelector(state => state.dogs);
useEffect(() => {
console.log(dogs);
}, [dogs]);
return <div>Demo</div>;
};
Here dogs returns an array of object. There are some other things going on in my app which updating the some properties of dog in the store and its is resulting in reinvoking of the above useSelector.
I have tried passing a custom equality function in useSelector, but no luck.
useSelector(state, areEqual)
How to fix this issue?
If you don't want the reactivity, then you can use useStore [will only return the current state] instead of useSelector() [ is called every time there is an update in the state.]
const { getState } = useStore()
const bankingAccounts = getState().dogs;

Solve the react-hooks/exhaustive-deps when updating redux in useEffect

Can you help me solve useEffect riddles?
I want to satisfy the linter react-hooks/exhaustive-deps rule but also have a component which does not re-render all the time. My component should aggregate data if it is loading for the first time. This data is fetched from redux, then the summation is done and the data is stored back to redux.
As explained in this question, I could move the aggregation to the component where the data is added, but this would force me to update the arrays more often, once either stocks or transaction is added and this even I never view the aggregate.
This question also points to a post by Dan Abramov not to disable the linter rule. Which let me came to the conclusion just leaving something out in the useEffect array is not the way to go — as it is the case in my example.
Now the question is how to solve this?
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { ITransactionArray } from "../../../store/account/types";
import { IStockArray, STOCKS_PUT } from "../../../store/portfolio/types";
import { useTypedSelector } from "../../../store/rootReducer";
import { AppDispatch } from "../../../store/store";
const aggregateStockQuantities = (
stocks: IStockArray,
transactions: ITransactionArray
): IStockArray => {
console.log("run Value/aggregateStockQuantities");
const newStocks = [...stocks];
newStocks.forEach((s) => {
s.quantity = 0;
return s;
});
transactions.forEach((t) => {
newStocks.forEach((s) => {
if (s.quantity !== undefined && t.isin === s.isin) {
s.quantity += t.quantity;
}
return s;
});
});
return newStocks;
};
const Test: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
const { stocks } = useTypedSelector((state) => state.portfolio);
const { transactions } = useTypedSelector((state) => state.account);
const stockQuantities = aggregateStockQuantities(stocks, transactions);
useEffect(() => {
dispatch({
type: STOCKS_PUT,
payload: stockQuantities,
});
}, [dispatch]);
// only works if I leave out stockQuantities
// but then warning: React Hook useEffect has a missing dependency: 'stockQuantities'. Either include it or remove the dependency array react-hooks/exhaustive-deps
// Render
}
Update #1: Had to change the aggregateStockQuantities so quantity is null at the start.
I would advice making a selector that only selects aggregateStockQuantities from the redux state.
And move the const stockQuantities = aggregateStockQuantities(stocks, transactions); inside the useEffect hook.

Custom hook to get data from store, if no then dispatch action and return data

I'm trying out React hooks API. To be specific, trying out a custom hook. The scenario is that I have an API for contact-types which is being used in multiple components. In all those components I have to check if the contact-types exists in the store. And if not, then dispatch an action. To avoid checks, and I wrote a hook that does the same. It checks the store if data is present, if yes, then returns it. If not, then dispatches an action to fetch it and when it's updated in the store, returns that value.
Here's the hook-
export const useContactTypes = () => {
const contactTypesFromStore = store.getState().contacts.types;
const [contactTypes, setContactTypes] = useState(contactTypesFromStore);
useEffect(() => {
if (!contactTypesFromStore) { //contact not in store
store.dispatch({ type: 'FETCH_CONTACT_TYPES' }); //dispatch action to fetch contact types
}
}, []);
store.subscribe(() => {
const newValues = store.getState().contacts.types;
if (contactTypesFromStore !== newValues) {
setContactTypes(newValues);
}
});
return contactTypes;
};
The requirements are met. But-
Is there any other approach for this?
Is this the best way to write a custom hook?
And should I worry about using redux subscribe like this?
There is no need to subscribe to the store or use the store directly, you can use the built in hooks from react-redux which will handle the subscription for you.
Note that the hooks are added in v7.1.0
react-redux hooks
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
export const useContactTypes = () => {
const contactTypes = useSelector(({ contacts }) => contacts.types, shallowEqual);
const disptach = useDispatch();
useEffect(() => {
if (!contactTypes) { // contact not in store
dispatch({ type: 'FETCH_CONTACT_TYPES' }); // dispatch action to fetch contact types
}
}, []);
return contactTypes;
}

Resources