I have a browser application with the React-Redux architecture. Many events occur in the application (e.g. timeouts, web socket incoming messages, web workers messages, XHR responses, etc.). Each event emits a Redux action that changes the Redux store that causes a React render.
The problem is that the events occur so often that React doesn't have enough time to render the UI (for example, an event occurs every 5ms (or more often) and React takes 10ms to render the UI). It makes the whole page stop responding because the render process doesn't stop (it always has something to render) and the browser has no time to draw the DOM or handle a DOM event.
What are the approaches or ready solutions to solve this problem considering that I can reduce neither the events frequency nor the React render time?
When you update the state from a websocket message, record the time and only do another update if a certain amount of time has passed since then.
In this example the state is updated only if 1000ms have passed since the last update:
client.onUpdate(data => {
if (!this.lastUpdate ||
new Date().getTime() - this.lastUpdate.getTime() > 1000) {
this.setState({ data });
this.lastUpdate = new Date();
}
})
You can simply debounce your handler.
debounced function means it can be invoked only once in X period of time.
For the sake of the example I will not implement the details of debouncing a function, but rather use lodash.
// function that handles the server update.
const serverUpdateHandler = (dispatch) => {
dispatch({
type: SERVER_UPDATE
})
}
// create a new function that is debounced. aka - will invoke after 250 milliseconds after the lace invokation
const debouncedHandler = _.debounce(serverUpdateHandler,250);
// pass socket.io the debounced function
socket.on('server-update',debouncedHandler)
Related
useInterval
useInterval from this blog post by Dan Abramov (2019):
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
A Potential Bug
The interval callback may be invoked between the commit phase and the useEffect invocation, causing the old (and hence not up to date) callback to be called. In other words, this may be the execution order:
Render phase - a new value for callback.
Commit phase - state committed to DOM.
useLayoutEffect
Interval callback - using savedCallback.current(), which is different than callback.
useEffect - savedCallback.current = callback;
React's Life Cycle
To further illustrate this, here's a diagram showing React's Life Cycle with hooks:
Dashed lines mean async flow (event loop released) and you can have an interval callback invocation at these points.
Note however, that the dashed line between Render and React updates DOM (commit phase) is most likely a mistake. As this codesandbox demonstrates, you can only have an interval callback invoked after useLayoutEffect or useEffect (but not after the render phase).
So you can set the callback in 3 places:
Render - Incorrect because state changes have not yet been committed to the DOM.
useLayoutEffect - correct because state changes have been committed to the DOM.
useEffect - incorrect because the old interval callback may fire before that (after layout effects) .
Demo
This bug is demonstrated in this codesandebox. To reproduce:
Move the mouse over the grey div - this will lead to a new render with a new callback reference.
Typically you'll see the error thrown in less than 2000 mouse moves.
The interval is set to 50ms, so you need a bit of luck for it to fire right between the render and effect phases.
Use Case
The demo shows that the current callback value may differ from that in useEffect alright, but the real question is which one of them is the "correct" one?
Consider this code:
const [size, setSize] = React.useState();
const onInterval = () => {
console.log(size)
}
useInterval(onInterval, 100);
If onInterval is invoked after the commit phase but before useEffect, it will print the wrong value.
This does not look like a bug to me, although I understand the discussion.
The answer above that suggests updating the ref during render would be a side effect, which should be avoided because it will cause problems.
The demo shows that the current callback value may differ from that in useEffect alright, but the real question is which one of them is the "correct" one?
I believe the "correct" one is the one that has been committed. For one reason, committed effects are the only ones that are guaranteed to have cleanup phase later. (The interval in this question doesn't need a cleanup effect, but other things might.)
Another more compelling reason in this case, perhaps, is that React may pre-render things (either at lower priority, or because they're "offscreen" and not yet visible, or in the future b'c of animation APIs). Pre-rendering work like this should never modify a ref, because the modification would be arbitrary. (Consider a future animation API that pre-renders multiple possible future visual states to make transitions faster in response to user interaction. You wouldn't want the one that happened to render last to just mutate a ref that's used by your currently visible/committed view.)
Edit 1 This discussion mostly seems to be pointing out that when JavaScript isn't synchronous (blocking), when it yields between rendering, there's a chance for other things to happen in between (like a timer/interval that was previously scheduled). That's true, but I don't think it's a bug if this happens during render (before an update is "committed").
If the main concern is that the callback might execute after the UI has been committed and mismatch what's on the screen, then you might want to consider useLayoutEffect instead. This effect type is called during the commit phase, after React has modified the DOM but before React yields back to the browser (aka so no intervals or timers can run in between).
Edit 2 I believe the reason Dan originally suggested using a ref and an effect for this (rather than just an effect) was because updates to the callback wouldn't reset the interval. (If you called clearInterval and setInterval each time the callback changed, the overall timing would be interrupted.)
To attempt to answer your final question strictly:
I can't see any logical harm updating the callback in render() as opposed to useEffect(). useEffect() is never called anything other than after render(), and whatever it is called with will be what the last render was called with, so the only difference logically is that the callback may be more out-of-date by the time the useEffect() is called.
This may be exacerbated by the coming concurrent mode, if there may be multiple calls to render() before a call to useEffect(), but I'm not even sure it works like that.
However: I would say there is a maintenance cost to doing it this way: it implies that it is ok to cause side effects in render(). In general that is not a good idea, and all necessary side effects should really be done in useEffect(), because, as the docs say:
the render method itself shouldn’t cause side effects ... we typically want to perform our effects after React has updated the DOM
So I would recommend putting any side effect inside a useEffect() and having that as a coding standard, even if in certain situations it is OK. And particularly in a blog post by a react core dev that is going to be copied and pasted by "guide" many people it is important to set the right example ;-P
Alternative solution
As for how you can fix your problem, I will just copy and paste my suggested implementation of setInterval() from this answer, which should remove the ambiguity by calling the callback in a separate useEffect(), at which point all state should be consistent and you don't have to worry about which is "correct". Dropping it into your sandbox seemed to solve the problem.
function useTicker(delay) {
const [ticker, setTicker] = useState(0);
useEffect(() => {
const timer = setInterval(() => setTicker(t => t + 1), delay);
return () => clearInterval(timer);
}, [delay]);
return ticker;
}
function useInterval(cbk, delay) {
const ticker = useTicker(delay);
const cbkRef = useRef();
// always want the up to date callback from the caller
useEffect(() => {
cbkRef.current = cbk;
}, [cbk]);
// call the callback whenever the timer pops / the ticker increases.
// This deliberately does not pass `cbk` in the dependencies as
// otherwise the handler would be called on each render as well as
// on the timer pop
useEffect(() => cbkRef.current(), [ticker]);
}
Here is a modification of your example which shows that both/neither approaches are correct: https://codesandbox.io/s/useintervalbug-neither-are-correct-zu2zt?file=/src/App.js
The use of refs is not what you would do in reality but it was necessary to easily detect and report the problem. They do not materially affect the behaviour.
In this example the parent component has created the new "correct" callback, finished rendering and wants that new callback to be used by the child and the timer.
Ultimately there is a race condition between when the "correct" callback ultimately gets passed to useInterval and when the browser decides to call the callback. I do not think it is possible to avoid this.
It makes no difference if you memoize the callback unless of course it has no dependencies and never changes.
I need to obtain data from websocket and I want to use RxJS to do so.
There is a websocket 1 for the latest initial data (~1000 records) and websocket 2 for the incremental updates.
I have created two observables:
initalState$ that goes to websocket 1 and fetches the initial data and then completes.
updateEvent$ that goes to websocket 2 and continuously receives updates.
My initial implementation was:
initialState.subscribe(initialData=> {
console.log(initialData);
updateEvent.subscribe(updateEvent => {
console.log(updateEvent);
});
});
The issue that I'm facing is that there is a gap after fetching the initalState and receiving the first update (updateEvent).
(I might lose update that happens after I fetch the initial data and before the subscribe).
Is there some practical way that I can create a new Observer that subscribes to both of my observers at the same time and buffer the updateEvent observer until the initalState completes and then have them in the right order "initial data first" then "updates" ?
Basically making the initialState just the "first" update, but making sure there aren't any missing updates after that.
It looks like you could achieve what you need by using buffer for the second websocket stream until the first one emits. Although, this chain gets a little more complicated because you want to start receiving values only after the first stream emits.
const initialStateShared = initialState.pipe(share());
const updateEventShared = updateEvent.pipe(share());
merge(
initialStateShared,
updateEventShared.pipe( // Buffer the second stream but only once
buffer(initialStateShared),
take(1),
),
updateEventShared.pipe( // Updates from the second stream will be buffered first and then continue comming from here
skipUntil(initialStateShared),
)
).subscribe(...);
If I am understanding it correctly what you want is to trigger both of the requests simultaneously and subscribe to them only if both are already available. I think you are looking for the combineLatest operator.
combineLatest([initialState$, updateEvent$]).subscribe(([initialState, updateEvent] => {
console.log({initialState, updateEvent});
}));
This way the combined observable will wait for both initialState$ and updateEvent$ to have emitted something and after that it will trigger emits if either of the combined observables emits something. See https://www.learnrxjs.io/operators/combination/combinelatest.html for more information.
Note: You should prevent doing a subscribe in another subscribe. It is often a code smell for doing something wrong.
I am having trouble limiting how often a component gets updated.
Component A has a parent Component B.
I want to have Component A only update once per second, as we are using socket IO to update live results, but when there are 100's of users we get too many updates and the page renders way more than we'd like giving us a glitchy look as a bar graph goes up and down very quickly.
Is there a way to ONLY allow Component A to update once per second?
Do we have to control how often Component B passes down props to Component A?
What you're looking is for throttling, which would allow a function to run at most once per second per your requirement.
Where should I throttle?
The principle is to throttle whatever that will trigger a mutation on those states in the container component (the component that hosts the states that become props to the presentation component), not to throttle the rendering itself.
If I throttle in the presentation component, now all the views I desire to be throttled need to change into "throttled components", and these can no longer be pure functional presentation components.
Reusable components for presentation don't need to, or even must not know about throttling. The containers that use them decide on throttling and other behavior. Throttling is only required because of the real time feed, so it should live where the feed is processed by the container, and be limited there without impacting the structure of the rest of the app.
By following this principle, throttling can be disabled or modified from a single place. And there will be no unnecessary state mutations in the container that will end up being throttled by the "throttled components" anyway.
Less important implementation details
You can implement it yourself, or use a library like Lodash that implements throttling (among other things).
You can use throttle to throttle the state updates that would trigger a render on Component A
_.throttle(func, [wait=0], [options={}])
Creates a throttled function that only invokes func at most once per
every wait milliseconds.
So for you func would be the function that would trigger a state update, and wait would be 1000. (1000 ms is 1 second)
You can use shouldComponentUpdate and store the last update time in a variable then comparing now with the last updated time.
class MyComponent extends Component {
constructor() {
this.lastUpdateDate = new Date();
}
shouldComponentUpdate() {
const now = new Date();
var seconds = (now.getTime() - this.lastUpdateDate.getTime()) / 1000;
return seconds >= 1;
}
componentDidUpdate() {
this.lastUpdateDate = new Date();
}
render() {
return <span>My Component</span>;
}
}
I don't understand this example from react-meteor-data
import { createContainer } from 'meteor/react-meteor-data';
export default FooContainer = createContainer(() => {
// Do all your reactive data access in this method.
// Note that this subscription will get cleaned up when your component is unmounted
var handle = Meteor.subscribe("todoList", this.props.id);
return {
currentUser: Meteor.user(),
listLoading: ! handle.ready(),
tasks: Tasks.find({listId: this.props.id}).fetch(),
};
}, Foo);
Why is it recommended to stop subscriptions when a Component is umounted but, in this case, no effort is made to stop anything? How does Meteor handle subscriptions, then? When are the collections cleaned? Are subscriptions stacking up every time the tracker callback is executed?
Or is Meteor smart enough to know when Meteor.subscribe is being called and does magic with the subscriptions?
The ReactMeteorData container runs createContainer's callback inside a reactive Tracker computation.
One of its features is stopping the subscription if the computation is invalidated or stopped.
If the function re-run produces an identical subscription, (same publication, same parameters) the library is smart enough and does not cancel and re-create the same subscription.
When the component is unmounted, the computation is stopped, the subscription is cancelled and not re-created (as the callback is not called again) and therefore automatically unsubscribed.
If you call Meteor.subscribe within a reactive computation, for example using Tracker.autorun, the subscription will automatically be cancelled when the computation is invalidated or stopped; it is not necessary to call stop on subscriptions made from inside autorun. However, if the next iteration of your run function subscribes to the same record set (same name and parameters), Meteor is smart enough to skip a wasteful unsubscribe/resubscribe.
(source: Meteor Docs)
The React v0.12 release announcement included the following:
New Features:
* React.addons.batchedUpdates added to API for hooking into update cycle
However I cannot find any documentation for this API. What is its purpose?
Specifically, any chance that it has an equivalent of Ember.run()?
When responding to synthetic events like onClick and so on, component state changes are batched so lots of calls to this.setState for the same component will only result in one render.
If you are changing state in response to some other async callback (e.g. AJAX or setTimeout) then every call to this.setState will result in a render. You can wrap your work in batchedUpdates(..) to avoid this.
var React = require('react/addons');
var batchedUpdates = React.addons.batchedUpdates;
var request = require('superagent'); // AJAX lib
var req = request('GET', ...).end(function(err, res) {
// invoked when AJAX call is done
batchedUpdates(function(){
.. all setState calls are batched and only one render is done ...
})
});
The default batched update strategy is great for your average website. Sometimes you have extra requirements and need to deviate from that.
The initial reason this was made public is for a requestAnimationFrame batching strategy, which is better for games and sites that need to update often and in many places.
It's just an extensibility point to solve edge case issues.