Excessive number of pending callbacks - reactjs

I'm trying to arrange the data gotten from firebase but after I arrange it the app becomes slow and if I click on a button it gives an error saying "Excessive number of pending callbacks".
useEffect(() => {
if (chats.length && users.length) {
const list = [];
chats.forEach((chat) => {
if (chat.members.includes(userId)) {
chat.members.forEach((y) => {
if (y !== userId) {
console.log("receiver: " + y);
users.forEach((z) => {
if (z.id === y) {
console.log(z);
list.push({
chat: chat,
acc: z,
user: user
});
}
});
console.log(list);
}
});
}
});
setUserChats(list);
}
}, [chats, users]);
users and chats are both states that I got from firebase on snapshot also in useEffect

One guess: Your dependencies don't work in the way you expect them to. chats and users aren't primitives, so depending on how they get created and passed, it's possible useEffect is run on every render, no matter whether chat and users have changed in structure or not. So this is what might happen:
useEffect() will be called on every rerender due to your invalid dependencies.
useEffect() calls setUserChats() on every turn. In theory, setUserChats() will check whether the new state actually differs, but in the same manner as useEffect it "fails" in the comparison and takes every list as a new state.
setState() will trigger a new render. Rinse, repeat with 1)
What you need to understand it that useEffect checks whether dependencies have changed (and setUserChats() does so as well to decide whether new state actually differs from the old one). This check relies on the identity/equal reference, i.e. on oldValue === newValue. For non-primitive values that means: it doesn't matter if oldValue and newValue look alike or are perfect clones even - if they don't share the same address in the memory, they are taken as non-equal.
Check out this thread or this library for solutions. In your case, a simple (but not really nice) solution would be to change your dependencies to [JSON.stringify(chats), JSON.stringify(users)], but there are more elaborate, performant and reliable solutions out there.
(Additionally, you forget to add userId to the dependencies. So something like [userId, JSON.stringify(chats), JSON.stringify(users)] might be more appropriate.)
Another thought though: I don't see why all that logic requires to be put into a useEffect() anyway. Just calculate the list in the component itself and call setUserChats(list). Or does it take too long?

Related

React - Slow keyboard input even with useMemo()

I have this code which has plenty of inputs, one for each price value to modify it.
As the amount of inputs is high (3 prices per tarification, 3 tarifications per area), in order not to rerender everything each time, I'm using useMemo on the function that updates the value of the inputs and aside of this, I'm using useReducer to avoid having a very long code to control inputs.
However, inserting characters (or numbers in this case) in the inputs is not instant as it should be, instead it takes a short time for them to appear, not to mention consecutive inputs.
const handleUpdate = useMemo(
() => (property, valu, obid) => {
dispatch({ type: "UPDATE_DATA", property, payload: valu, id: obid });
},
[dispatch]
);
And the reducer:
function reducer(state, action) {
switch (action.type) {
...
case "UPDATE_DATA":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.id) {
return { ...item, [action.property]: action.payload };
}
return item;
}),
};
}
}
I suggest checking the whole code as the problem (or solution) could be somewhere else. In order to see the whole code, you can refer to this sandcodebox link. Excuse the bad css formating as I copied just some part of it.
Note that the fetch function has been replaced by a long array simulating the data.
https://codesandbox.io/s/unruffled-feynman-g9nox2?file=/src/App.js
The point of useMemo generally is to cache the values of expensive calculations done during rendering. However, in your case, you do not have any expensive rendering calculations; you are just rendering a really large tree every time an input changes. In fact, because all your state is on the App component, you rerender the entire app every time.
The way to optimize this in React is to skip rendering components when possible. To do so, split unrelated pieces of the page into distinct components. Once you separate the logic, wrap it with React.memo(), which is a different optimization technique that can skip the rendering of a component altogether.
To me, the most obvious changes you can make are:
Move TodosDatos outside the App component because it is constant and doesn't need to be redefined on every render (which can be memory-intensive).
Move your <Table> into a new component that you memoize with React.memo(). Make sure to pass all the table's dependency values into the new component's props.
I implemented these changes here: https://codesandbox.io/s/green-breeze-mmum6n?file=/src/App.js. You should notice now that typing is nearly instantaneous. You can probably optimize it in several other places too for even better performance.

React useEffect runs too often

In short:
I have a side effect I want to happen when a value changes, but only if a second value is true. Since both values must be in the dependency array, the side effect is also triggered when the first value is unchanged and the second value is turned 'on', but I don't want it to.
In detail:
I'm creating a game in React. I want to play (short) sound effects at various events, such as winning and losing. I have a simple helper function to play sounds, which works:
function playSound(url) {
new Audio(url).play();
}
All the game logic is in a custom hook useGame. This hook returns, among other things, a click-handler for when the user makes a move (it handles updating all the relevant states in the hook) and a winner boolean (indicates whether the game is won).
I can't call playSound in the click-handler, because that closure only has access to the pre-click states and doesn't know if the game is won after the click. So I put it in a side-effect, with a dependency array to ensure it only happens once when the game is won.
useEffect(() => {
if (winner) {
playSound(winSound);
}, [winner]);
[Note: Internally, winner is stateless; it's calculated directly from the (stateful) board. So there are no setWinner calls I can tag along with.]
This all works fine up to here. But now I want to add a user setting to mute all sounds.
const [soundIsOn, setSoundIsOn] = useState(true);
Now I have to add this as a condition to calling playSound (or pass it as an argument to it).
useEffect(() => {
if (winner && soundIsOn) {
playSound(winSound);
}, [winner, soundIsOn]);
Note that the the dependency array now needs to include soundIsOn. The problem arises when the sound is off, the user wins the game, and then the user turns sound on later. This effect will be called since soundIsOn changes, resulting in the win sound.
I've read through all the documentation and looked for similar questions. The only potential solution I've seen is to use a ref inside a custom hook to keep track of previous states and check which state has changed, but that seems a bit messy and unsatisfying. Is there a 'proper' way to deal with this type of thing? Ideally either by modifying the effect, or somehow working it into the click-handler.
Solution #1
Manage trigger event with other state not winner object itself.
const [winner, _setWinner] = useState(...);
const [shouldPlaySound, setShouldPlaySound] = useState(false);
// wrapper function (it is not required)
const setWinner = (winner) => {
setShouldPlaySound(true);
_setWinner(winner)
}
...
// if winner is selected
setWinner(...);
...
// Side effect
useEffect(() => {
if(shouldPlaySound && soundIsOn){
playSound(winSound);
}
setShouldPlaySound(false);
}, [shouldPlaySound, soundIsOn])
Also new setWinner wrapper function doesn't need to be defined with useCallback because it includes only setStates of useState.
But defining setWinner wrapper is not required. This is your choice for code readbility.
Solution #2
But I think if you need to play sound when winner is selected, just call playSound when winner is selected.
// when winner is selected
setWinner(winner);
if(soundIsOn){
playSound(winSound);
}

How to compare oldValues and newValues on React Hooks useEffect? Multiple re-renders

The kinda the same problem as described here
How to compare oldValues and newValues on React Hooks useEffect?
But in my case, usePrevious hook does not help.
Imagine the form with several inputs, selects, and so on. You may want to look at https://app.uniswap.org/#/swap to make a similar visualization. There are several actions and data updates that will be happened on almost any change, which will lead to several re-renders, at least 4. For example.
I have 2 inputs, each represents a token. Base(first one) and Quote(second one).
This is a state for Base
const [base, setBase] = useState({
balance: undefined,
price: undefined,
value: initState?.base?.value,
token: initState?.base?.token,
tokenId: initState?.base?.tokenId,
});
and for Quote
const [quote, setQuote] = useState({
balance: undefined,
price: undefined,
value: initState?.quote?.value,
token: initState?.quote?.token,
tokenId: initState?.quote?.tokenId,
});
They gonna form a pair, like BTC/USD for example.
By changing token (instead of BTC I will choose ETH) in the select menu I will trigger several actions: fetching wallet balance, fetching price, and there are gonna be a few more rerenders with input view update and modal window close. So at least 4 of them are happening right now. I want to be able to compare base.token and basePrv with
const basePrv = usePrevious(base?.token); but on the second re-render base.token and basePrv gonna have the same token property already and it is an issue.
I also have the swap functionality between the inputs where I should change base with quote and quote with base like that
setBase(prevState => ({
...prevState,
base: quote
}));
setQuote(prevState => ({
...prevState,
quote: base
}));
In that case, there are no additional requests that should be triggered.
Right now I have useEffect with token dependency on it. But it will be fired each time when the token gonna be changed which will lead to additional asynchronous calls and 'tail' of requests if you gonna click fast. That's why I need to compare the token property that was before the change to understand should I make additional calls and requests because of the formation of new pair (BTC/USD becomes ETH/USD) or I should ignore that because it was just a "swap" (BTC/USD becomes USD/BTC) and there is no need to make additional calls and fetches. I just had to, well, swap them, not more.
So in my story, usePrevious hook will return the previous token property only once, and at the second and third time, it would be overwritten by multiple re-renders(other properties would be fetched) to the new one. So at the time when useEffect gonna be triggered, I would have no chance to compare the previous token property and the current one, because they will show the same.
I have several thoughts on how to solve it, but I am not sure is it right or wrong, because it seemed to me that the decisions look more imperative than declarative.
I can leave everything as it is (requests would be triggered always on any change no matter what it was. Was it a swap or user changed a pair). I can disable the swap button until all of the requests would be finished. It would solve the problem with requests 'tail'. But it is a kinda hotfix, that gonna be work, but I do not like it, because it would lead to additional unnecessary requests and it would be slow and bad for UX.
I can use a state to keep the previous pair on it right before the update by setBase or setQuote happens. It will allow me to use useEffect and compare previous pair to the current one to understand did the pair was changed, or just swapped and take the decision should I make fetches and calls or not.
I can get rid of useEffect with base.token and quote.token dependencies and handle everything inside of onClick handler. Because of that, the swap functionality would not trigger useEffect, and calls and fetches would be fired only if the user gonna click and choose something different. But as I said this option seemed a little bit odd to me.
I tried to use closure here, to "remember" the previous state of tokens, but it is kinda similar to use the current component state. Also, you have to initialize closure outside of the functional component body, and I do not see a possibility to transfer the init state into it that way, so the code becomes more spaghettified.
So any other ideas guys? I definitely missing something. Maybe that much of re-renders is an antipattern but I am not sure how to avoid that.
There could be multiple solutions to your problem. I would suggest to pick one which is easier to understand.
1. Modify the usePrevious hook
You can modify the usePrevious hook to survive multiple renders.
Tip: use JSON.stringify to compare if you think the value will be a complex object and might change the reference even for same real value.
function usePrevious(value) {
const prevRef = useRef();
const curRef = useRef();
if (value !== curRef.current){
// or, use
// if ( JSON.stringify(value) !== JSON.stringify(curRef.current)){
prevRef.current = curRef.current;
curRef.current = value;
}
return prevRef.current;
}
2. Sort useEffect dependency array
Since you're using tokens(strings) as dependency array of useEffect, and you don't mind their order (swap shouldn't change anything), sort the dependency array like
useEffect(
() => {
// do some effect
},
[base.token, quote.token].sort()
)
3. Store the currently fetched tokens.
While storing the API response data, also store the tokens(part of request) associated with that data. Now, you'll have 2 sets of tokens.
currently selected tokens
currently fetched tokens
You can chose to fetch only when the currently fetched tokens don't fulfil your needs. You can also extend this and store previous API request/responses and pick the result from them if possible.
Verdict
Out of all these, 3rd seems a nice & more standardised approach to me, but an overkill for your need (unless you want to cache previous results).
I would have gone with 2nd because of simplicity and minimalism. However, It still depends on what you find easier at the end.

ReactJS - Inefficient useEffect runs four times

I have a useEffect function that must wait for four values to have their states changed via an API call in a separate useEffect. In essence the tasks must happen synchronously. The values must be pulled from the API and those stateful variables must be set and current before the second useEffect can be called. I am able to get the values to set appropriately and my component to render properly without doing these tasks synchronously, I have a ref which changes from true to false after first render (initRender), however I find the code to be hacky and inefficient due to the fact that the second useEffect still runs four times. Is there a better way to handle this?
//Hook for gathering group data on initial page load
useEffect(() => {
console.log("UseEffect 1 runs once on first render");
(async () => {
const response = await axios.get(`${server}${gPath}/data`);
const parsed = JSON.parse(response.data);
setGroup(parsed.group);
setSites(parsed.sites);
setUsers(parsed.users);
setSiteIDs(parsed.sitesID);
setUserIDs(parsed.usersID);
})();
return function cleanup() {};
}, [gPath]);
//Hook for setting sitesIN and usersIN values after all previous values are set
useEffect(() => {
console.log("This runs 4 times");
if (
!initRender &&
sites?.length &&
users?.length &&
userIDs !== undefined &&
siteIDs !== undefined
) {
console.log("This runs 1 time");
setSitesIN(getSitesInitialState());
setUsersIN(getUsersInitialState());
setLoading(false);
}
}, [sites, siteIDs, users, userIDs]);
EDIT: The code within the second useEffect's if statement now only runs once BUT the effect still runs 4 times, which still means 4 renders. I've updated the code above to reflect the changes I've made.
LAST EDIT: To anyone that sees this in the future and is having a hard time wrapping your head around updates to stateful variables and when those updates occur, there are multiple approaches to dealing with this, if you know the initial state of your variables like I do, you can set your dependency array in a second useEffect and get away with an if statement to check a change, or multiple changes. Alternatively, if you don't know the initial state, but you do know that the state of the dependencies needs to have changed before you can work with the data, you can create refs and track the state that way. Just follow the examples in the posts linked in the comments.
I LIED: This is the last edit! Someone asked, why can't you combine your different stateful variables (sites and sitesIN for instance) into a single stateful variable so that way they get updated at the same time? So I did, because in my use case that was acceptable. So now I don't need the 2nd useEffect. Efficient code is now efficient!
Your sites !== [] ... does not work as you intend. You need to do
sites?.length && users?.length
to check that the arrays are not empty. This will help to prevent the multiple runs.

How can I make a function wait for another function?

I have this function:
const submitImport = async (value) => {
const { actions, navigation, account } = this.props;
this.setState({ isImporting: true });
actions.importWallet(value.mnemonicPhrase, value.password, 1);
console.log('acc', account);
actions.showNotification({
message: 'Account has been successfully Imported',
isError: false,
});
};
importWallet is adding new properties to account object but when I call this function the first time the account object is empty but when I click it the second time it is okay. So my guess is importWallet needs time to finish and return the value. I tried to use async await but it did not work. Also, I tried to use return new Promise but it still did not work. Maybe I did it the wrong way idk.
Any suggestions on how to solve this issue please?
I am assuming the importWallet function induces some change in the account prop.
All prop changes require atleast one render cycle for the updated values to get visible as state/prop changes are asynchronous in react. In your case you are trying to access account as soon invoking actions.importWallet. Hence as it is within the same render, it has not yet been updated. But as you mentioned, it will be available from the subsequent renders.
Also you cannot make it synchronous with async-await / promises as the asynchronous nature of react state updates is not exposed.
Your usecase may be achieved by some refractor :
Try obtaining the new value of account in the return statement of account.importWallet. That way you can use it even before the prop updates.
const updatedAccount = actions.importWallet(value.mnemonicPhrase, value.password, 1);
In case you are using React Hooks, you can create an effect with useEffect and add account as a dependency. This will invoke the effect when the dependency value changes (dependency should be a primitive data type for consistent effect invocation).

Resources