Prevent multiple api calls inside useEffect reactjs - javascript

I am working with react hooks and stuck at one place.
This is the function which is setting the state from the params.
useEffect(() => {
if (isInteger(searchParams.get("pageNo"))) {
setState({ ...state, activePage: parseInt(searchParams.get("pageNo")) });
setCount(count + 1); //setting here again
firstRef.current = true;
} else {
setState({ ...state, activePage: 1 });
}
}, []);
This is my useEffect function to call api here the api name is videoGridState. Here the api is calling single time with no issue.
useEffect(() => {
if (firstRef.current) {
videoGridState();
firstRef.current = false;
} else {
const timer = setTimeout(() => {
videoGridState(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [count]);
It is calling the api two times. One on the first load and second when it is setting the state from params. How can I prevent it from calling from multiple times within a sort method?

If you are calling the api two times, the issue maybe the prop count has change or is one of three things
This component appears twice in your page
This one should be obvious.
Something higher up the tree is unmounting and remounting
The component is being forced to unmount and remount on its initial render. This could be something like a "key" change happening higher up the tree. you need to go up each level with this useEffect until it renders only once. then you should be able to find the cause or the remount.
React.Strict mode is on
StrictMode renders components twice (on dev but not production) in
order to detect any problems with your code and warn you about them
(which can be quite useful).

Related

Filtering Table data with useEffect Hook causing component to re-render infinitely

I'm trying to use a search bar component to dynamically filter the content of a table that's being populated by API requests, however when I use this implementation the component re-renders infinitely and repeatedly sends the same API requests.
The useEffect() Hook:
React.useEffect(() => {
const filteredRows = rows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
});
if (filteredRows !== rows){
setRows(filteredRows);
}
}, [rows, search]);
Is there something I've missed in this implementation that would cause this to re-render infinitely?
Edit 1:
For further context, adding in relevant segments of code from that reference this component which might cause the same behaviour.
Function inside the parent component that renders the table which calls my API through a webHelpers library I wrote to ease API request use.
function fetchUsers() {
webHelpers.get('/api/workers', environment, "api", token, (data: any) => {
if (data == undefined || data == null || data.status != undefined) {
console.log('bad fetch call');
}
else {
setLoaded(true);
setUsers(data);
console.log(users);
}
});
}
fetchUsers();
Edit 2:
Steps taken so far to attempt to fix this issue, edited the hook according to comments:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);
Edit 3:
Solution found, I've marked the answer by #Dharmik pointing out how Effect calls are managed as this caused me to investigate the parent components and find out what was causing the component to re-render repeatedly. As it turns out, there was a useEffect hook running repeatedly by a parent element which re-rendered the page and caused a loop of renders and API calls. My solution was to remove this hook and the sub-components continued rendering as they should without loops.
It is happening because you've added rows to useEffect dependency array and when someone enters something into search bar, The rows get filtered and rows are constantly updating.
And because of that useEffect is getting called again and again. Remove rows from the useEffect dependency array and it should work fine.
I would like to complement Dharmik answer. Dependencies should stay exhaustive (React team recomendation). I think a mistake is that filteredRows !== rows uses reference equality. But rows.filter(...) returns a new reference. So you can use some kind of deep equality check or in my opinion better somethink like:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

How to access current state of component from useEffect without making it a dependency?

I've got the following component (simplified) which, given a note ID, would load and display it. It would load the note in useEffect and, when a different note is loaded or when the component gets unmounted, it saves the note.
const NoteViewer = (props) => {
const [note, setNote] = useState({ title: '', hasChanged: false });
useEffect(() => {
const note = loadNote(props.noteId);
setNote(note);
return () => {
if (note.hasChanged) saveNote(note); // bug!!
}
}, [props.noteId]);
const onNoteChange = (event) => {
setNote({ ...note, title: event.target.value, hasChanged: true });
}
return (
<input value={note.title} onChange={onNoteChange}/>
);
}
The issue is that within the useEffect I use note, which is not part of the dependencies so it means I always get stale data.
However, if I put the note in the dependencies then the loading and saving code will be executed whenever the note is modified, which is not what I need.
So I'm wondering how can I access the current note, without making it a dependency? I've tried to replace the note with a ref, but it means the component no longer updates when the note is changed, and I'd rather not use references.
Any idea what would be the best way to achieve this? Maybe some special React Hooks pattern?
You can't get the current state because this component does not render on the app render that removes it. Which means your effect never runs that last time.
Using an effect cleanup function is not a good place for this sort of thing. That should really be reserved for cleaning up that effect and nothing else.
Instead, whatever logic you have in the app that changes the state to close the NoteViewer should also save the note. So in some parent component (perhaps a NoteList or something) you'd save and close like:
function NoteList() {
const [viewingNoteId, setViewingNoteId] = useState(null)
// other stuff...
function closeNote() {
if (note.hasChanged) saveNote(note)
setViewingNoteId(null)
}
return <>{/* ... */}</>
}

How to reload a react component without using any event handler?

Is it possible to reload a component in react without using the help of a function like onClick. I want my component to reload again after it just got loaded.
I tried using
window.location.reload(false); in the constructor. I even tried using useEffect() but they make the page reload infinitely.
Is there any work around?
I have seen people accomplish this by setting a dummy setState method that is strictly used to trigger a refresh. Consider this component:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
const onClick = () => {
rerender(dummyState + 1);
}
React.useEffect( () => {
console.log("dummyState's state has updated to: " + dummyState)
}, [dummyState])
return(
<div>
<button onClick={onClick}>reRender</button>
</div>
)
}
Whenever you want to rerender the component you just need to trigger a change in the dummyState. Clicking the button will cause the components state to change, which will cause the component to rerender (i used a console.log() for proof). It is worth noting that simply calling a method that instantly changes the state of the component will result in an infinite loop.
From reading your comment above, it sounds like you are passing a value as a prop to a child component and you would like the value to be recalculated as soon as any interaction with that component occurs. Ideally, the interaction itself should cause the recalculation. But if you would just like to quickly recalculate the value and rerender the component as soon as it renders then i think this would work:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
//the empty brackets will cause this useEffect
//statement to only execute once.
React.useEffect( () => {
rerender(dummyState + 1);
}, [])
return(
<div>
<p>dummyState</p>
</div>
)
}
You could also recalculate the value in the useEffect method, as it will get called as soon as the component initially renders but not on any subsequent rerenders (due to the empty brackets as the second parameter to the useEffect method)
Have you tried to enter some dependecies to your useEffect hook ?
Like :
useEffect(() => {
console.log('hello');
}, [isLoad];
Here you're component will re-render only if isLoad is changing.

React functional component is taking snapshot of state at the time of registering handler on websocket

react functional component is taking snapshot of state at the time of subscription.
For ex. PFB code.
If i click setSocketHandler button and then press setWelcomeString button. Now if i receive message over socket when i log welcomestring it is empty.
But if i click setWelcomeString button and then click setSocketHandler button. Now if i receive message on socket Welcome is getting logged on console.
I have seen same behaviour in project so just created this simple app to prove.
If i use class component which is commented below.. everything works fine.
So my question is why react functional component is working on a state at the time of reg and not on actual state at the time message is received.
This is very weird. How to make it work in functional component correctly.
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
const io = require('socket.io-client');
const socket = io.connect('http://localhost:3000/');
const App : React.FunctionComponent = () => {
const [welcomeString, setWelcomeString] = useState("");
const buttonCliecked = () => {
console.log("clocked button");
setWelcomeString("Welcome")
}
const onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(welcomeString);
});
}
return (
<div>
<header className="component-header">User Registration</header>
<label>{welcomeString}</label>
<button onClick={buttonCliecked}>setWelcomeString</button>
<button onClick={onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
/*class App extends React.Component {
constructor(props) {
super(props);
this.state = {
welcomeString:""
}
}
buttonCliecked = () => {
console.log("clocked button");
this.setState({ welcomeString:"Welcome"})
}
onsockethandlerclicked = () => {
console.log("socket handler clicked");
socket.on('out', () => {
console.log("Recived message")
console.log(this.state.welcomeString);
});
}
render() {
return (
<div>
<header className="component-header">User Registration</header>
<label>{this.state.welcomeString}</label>
<button onClick={this.buttonCliecked}>setwelcomestring</button>
<button onClick={this.onsockethandlerclicked}>setSocketHandler</button>
</div>
);
}
}*/
export default App;
For those of us coming from a Redux background, useReducer can seem deceptively complex and unnecessary. Between useState and context, it’s easy to fall into the trap of thinking that a reducer adds unnecessary complexity for the majority of simpler use cases; however, it turns out useReducer can greatly simplify state management. Let’s look at an example.
As with my other posts, this code is from my booklist project. The use case is that a screen allows users to scan in books. The ISBNs are recorded, and then sent to a rate-limited service that looks up the book info. Since the lookup service is rate limited, there’s no way to guarantee your books will get looked up anytime soon, so a web socket is set up; as updates come in, messages are sent down the ws, and handled in the ui. The ws’s api is dirt simple: the data packet has a _messageType property on it, with the rest of the object serving as the payload. Obviously a more serious project would design something sturdier.
With component classes, the code to set up the ws was straightforward: in componentDidMount the ws subscription was created, and in componentWillUnmount it was torn down. With this in mind, it’s easy to fall into the trap of attempting the following with hooks
const BookEntryList = props => {
const [pending, setPending] = useState(0);
const [booksJustSaved, setBooksJustSaved] = useState([]);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
if (packet._messageType == "initial") {
setPending(packet.pending);
} else if (packet._messageType == "bookAdded") {
setPending(pending - 1 || 0);
setBooksJustSaved([packet, ...booksJustSaved]);
} else if (packet._messageType == "pendingBookAdded") {
setPending(+pending + 1 || 0);
} else if (packet._messageType == "bookLookupFailed") {
setPending(pending - 1 || 0);
setBooksJustSaved([
{
_id: "" + new Date(),
title: `Failed lookup for ${packet.isbn}`,
success: false
},
...booksJustSaved
]);
}
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
We put the ws creation in a useEffect call with an empty dependency list, which means it’ll never re-fire, and we return a function to do the teardown. When the component first mounts, our ws is set up, and when the component unmounts, it’s torn down, just like we would with a class component.
The problem
This code fails horribly. We’re accessing state inside the useEffect closure, but not including that state in the dependency list. For example, inside of useEffect the value of pending will absolutely always be zero. Sure, we might call setPending inside the ws.onmessage handler, which will cause that state to update, and the component to re-render, but when it re-renders our useEffect will not re-fire (again, because of the empty dependency list)—as a result that closure will go on closing over the now-stale value for pending.
To be clear, using the Hooks linting rule, discussed below, would have caught this easily. More fundamentally, it’s essential to break with old habits from the class component days. Do not approach these dependency lists from a componentDidMount / componentDidUpdate / componentWillUnmount frame of mind. Just because the class component version of this would have set up the web socket once, in componentDidMount, does not mean you can do a direct translation into a useEffect call with an empty dependency list.
Don’t overthink, and don’t be clever: any value from your render function’s scope that’s used in the effect callback needs to be added to your dependency list: this includes props, state, etc. That said—
The solution
While we could add every piece of needed state to our useEffect dependency list, this would cause the web socket to be torn down, and re-created on every update. This would hardly be efficient, and might actually cause problems if the ws sends down a packet of initial state on creation, that might already have been accounted for, and updated in our ui.
If we look closer, however, we might notice something interesting. Every operation we’re performing is always in terms of prior state. We’re always saying something like “increment the number of pending books,” “add this book to the list of completed,” etc. This is precisely where a reducer shines; in fact, sending commands that project prior state to a new state is the whole purpose of a reducer.
Moving this entire state management to a reducer would eliminate any references to local state within the useEffect callback; let’s see how.
function scanReducer(state, [type, payload]) {
switch (type) {
case "initial":
return { ...state, pending: payload.pending };
case "pendingBookAdded":
return { ...state, pending: state.pending + 1 };
case "bookAdded":
return {
...state,
pending: state.pending - 1,
booksSaved: [payload, ...state.booksSaved]
};
case "bookLookupFailed":
return {
...state,
pending: state.pending - 1,
booksSaved: [
{
_id: "" + new Date(),
title: `Failed lookup for ${payload.isbn}`,
success: false
},
...state.booksSaved
]
};
}
return state;
}
const initialState = { pending: 0, booksSaved: [] };
const BookEntryList = props => {
const [state, dispatch] = useReducer(scanReducer, initialState);
useEffect(() => {
const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
ws.onmessage = ({ data }) => {
let packet = JSON.parse(data);
dispatch([packet._messageType, packet]);
};
return () => {
try {
ws.close();
} catch (e) {}
};
}, []);
//...
};
While slightly more lines, we no longer have multiple update functions, our useEffect body is much more simple and readable, and we no longer have to worry about stale state being trapped in a closure: all of our updates happen via dispatches against our single reducer. This also aids in testability, since our reducer is incredibly easy to test; it’s just a vanilla JavaScript function. As Sunil Pai from the React team puts it, using a reducer helps separate reads, from writes. Our useEffect body now only worries about dispatching actions, which produce new state; before it was concerned with both reading existing state, and also writing new state.
You may have noticed actions being sent to the reducer as an array, with the type in the zero slot, rather than as an object with a type key. Either are allowed with useReducer; this is just a trick Dan Abramov showed me to reduce the boilerplate a bit :)
What about functional setState()
Lastly, some of you may be wondering why, in the original code, I didn’t just do this
setPending(pending => pending - 1 || 0);
rather than
setPending(pending - 1 || 0);
This would have removed the closure problem, and worked fine for this particular use case; however, the minute updates to booksJustSaved needed access to the value of pending, or vice versa, this solution would have broken down, leaving us right where we started. Moreover, I find the reducer version to be a bit cleaner, with the state management nicely separated in its own reducer function.
All in all, I think useReducer() is incredibly under-utilized at present. It’s nowhere near as scary as you might think. Give it a try!
Happy coding!

Categories