How to reduce the number of times useEffect is called? - javascript

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.

Related

need to pass an effect via props or force component reload from outside the root component in preact/react

I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});

Fetch data and setState every time just before component renders in React Native

There is the case , i have a modal which shows some data from its state ( an array ) , and it's state is getting set in componentDidMount() function like docs suggests. I need to show updated data every time when modal opened up.So i was able to do that with componentWillReceiveProps(nextProps) function , like i showed below .
But what if i want to migrate to getDerivedStateFromProps function ? How am i going to achieve same behaviour with it ?
Here is the component code simplified for sake :
export class PastOrdersModal extends Component {
constructor(props) {
super(props);
this.state = {
past: {},
isLoading: false,
modalVisible: false
};
}
async componentWillReceiveProps(nextProps) {
const response = await this.fetchPast();
this.setState({ past: response });
}
async componentDidMount() {
const response = await this.fetchPast();
this.setState({ past: response });
}
...
... some callback functions for handling modalvisible value ....
...
render(){
// here rendering state array with this.state.past.map(......) etc.
}
The fetchPast function makes a GET request to server of mine , all you need to know that it returns an array to me. This is working perfectly. ComponentWillReceiveProps gets called every time because parent component sends modalVisible props everytime.
But componentWillRecieveProps is deprecating and i could not make the same behavior with getDerivedStateFromProps.How should i implement same thing with it.
Note: ı am not going to use redux ,i am not going to use mobx , i know how to use them and it's not what i want. The thing is the behavior i want is soo simple i don't want to pass values another component , i don't want to pass values another screen , i just want to update a simple component that is all but either the framework is pushing it's limits to make simplest tasks hardests thing ever or i am missing really huge point.(probably latter one :) )
Note: I know i am not doing anything with nextProps , that was the only solution i found.
You can write your Component as a function so you can use React Hooks
const PastOrdersModal = (props) => {
const [past, setPast] = useState({});
const [isLoading, setLoading] = useState(false);
const [modalVisible, setModalVisibility] = useState(false);
useEffect(() => {
const fetchPast = async () => {
const response = await fetchPast();
setPast(response);
};
if(modalVisible) fetchPast();
}, [modalVisible])
useEffect(() => {
const fetchPast = async () => {
const response = await fetchPast();
setPast(response);
};
fetchPast();
}, [])
...
... some callback functions for handling modalvisible value ....
...
return <YourComponent />
The variables we created inside [] at the top are the ones we are using for the state. The first one will be the state as itself and the second one is a function responsible for updating that state. The useEffect hook will simulate the life cycle methods for Class Components, it receives a callback that will be executed and a second argument. The second argument is the one that will indicate when it is going to be triggered. For instance, you can see 2 useEffects, the one with the empty array will tell the hook to execute just once, similar as componentDidMount. The other one will be triggered when modalVisible changes, so everytime you change its value it will be executed, that's why we only validate if the modal is visible (is true) we do the fetch, otherwise the fetch won't be executed

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 can I fetch data from a Websocket in React?

I am trying to get realtime data from bitstamp API and store in my orders state. The page gets stuck in a loop and drains resources and react doesn't re-render the page when the orders state change. I can see the data if I log it to the console
This is what I have implemented so far.
const [loading, setLoading] = useState(true);
const [orders, setOrders] = useState([]);
const [subscription, setSubscription] = useState({
event: 'bts:subscribe',
data: {
channel: 'order_book_btcusd'
}
});
const ws = new WebSocket('wss://ws.bitstamp.net');
const initWebsocket = () => {
ws.onopen = () => {
ws.send(JSON.stringify(subscription));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
switch (response.event) {
case 'data':
setOrders(response.data);
setLoading(false);
break;
case 'bts:request_reconnect':
initWebsocket();
break;
default:
break;
}
};
ws.onclose = () => {
initWebsocket();
};
};
useEffect(() => {
initWebsocket();
}, [orders, subscription]);
console.log(orders);
const showResult = () => {
orders.bids.map((el, index) => (
<tr key={index}>
<td> {el[0]} </td>
<td> {el[1]} </td>
</tr>
));
};
This is happening because useEffect execute its callback after each render cycle i.e it runs both after the first render and after every update. So for every first message received it is opening a new WebSocket connection and storing the data in the state which is causing a loop.
You can read more about useEffect here
Edited:-
useEffect(() => {
initWebsocket();
}, [orders, subscription]);
The optional second argument to useEffect is used to detect if anything has changed or not (basically it compares prev state/props and given state/props) and it calls the effect whenever there is a change in value.
So on every orders state update, this effect will get called and which in turn causes a loop.
Solution:-
But in your case, you want to establish WebSocket connection only once after the component has mounted and keep listening to the incoming data irrespective of any state or prop change.
You can pass an empty [] such that it gets called only once on mount and unmount.
useEffect(() => {
initWebsocket();
// cleanup method which will be called before next execution. in your case unmount.
return () => {
ws.close
}
}, []);
From doc:-
This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect.
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often.
In useEffect, check if the WebSocket connection is closed before initializing it.
If you are confused with the working of react hooks, you can use class components and initialize your WebSocket connection in componentDidMount and componentDidUpdate(Check if the connection is closed and initialize it).
PS:
I have implemented a simple Chat Application using React and WebSockets.
https://github.com/Nikhil-Kumaran/ChatApp
Go through the repo to have a better idea.
Related component: https://github.com/Nikhil-Kumaran/ChatApp/blob/master/src/WebSockets.js

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