Throttling dispatch in redux producing strange behaviour - reactjs

I have this class:
export default class Search extends Component {
throttle(fn, threshhold, scope) {
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
}
}
render() {
return (
<div>
<input type='text' ref='input' onChange={this.throttle(this.handleSearch,3000,this)} />
</div>
)
}
handleSearch(e) {
let text = this.refs.input.value;
this.someFunc();
//this.props.onSearch(text)
}
someFunc() {
console.log('hi')
}
}
All this code does it log out hi every 3 seconds - the throttle call wrapping the handleSearch method takes care of this
As soon as I uncomment this line:
this.props.onSearch(text)
the throttle methods stops having an effect and the console just logs out hi every time the key is hit without a pause and also the oSearch function is invoked.
This onSearch method is a prop method passed down from the main app:
<Search onSearch={ text => dispatch(search(text)) } />
the redux dispatch fires off a redux search action which looks like so:
export function searchPerformed(search) {
return {
type: SEARCH_PERFORMED
}
}
I have no idea why this is happening - I'm guessing it's something to do with redux because the issue occurs when handleSearch is calling onSearch, which in turn fires a redux dispatch in the parent component.

The problem is that the first time it executes, it goes to the else, which calls the dispatch function. The reducer probably immediately update some state, and causes a rerender; the re-render causes the input to be created again, with a new 'throttle closure' which again has null 'last' and 'deferTimer' -> going to the else every single time, hence updating immediately.

As Mike noted, just not updating the component can you get the right behavior, if the component doesn't need updating.
In my case, I had a component that needed to poll a server for updates every couple of seconds, until some state-derived prop changed value (e.g. 'pending' vs 'complete').
Every time the new data came in, the component re-rendered, and called the action creator again, and throttling the action creator didn't work.
I was able to solve simply by handing the relevant action creator to setInterval on component mount. Yes, it's a side effect happening on render, but it's easy to reason about, and the actual state changes still go through the dispatcher.
If you want to keep it pure, or your use case is more complicated, check out https://github.com/pirosikick/redux-throttle-actions.

Thanks to luanped who helped me realise the issue here. With that understood I was able to find a simple solution. The search component does not need to update as the input is an uncontrolled component. To stop the cyclical issue I was having I've used shouldComponentUpdate to prevent it from ever re-rendering:
constructor() {
super();
this.handleSearch = _.throttle(this.handleSearch,1000);
}
shouldComponentUpdate() {
return false;
}
I also moved the throttle in to the constructor so there can only ever be once instance of the throttle.
I think this is a good solution, however I am only just starting to learn react so if anyone can point out a problem with this approach it would be welcomed.

Related

How to design my React project better without using shouldComponentUpdate

I am trying to construct 1-minute candlestick.
I have a component that will continuously passing a number (the trade price) to his child component.
This child component will keep update its state: (High, Low, Open, Close) base on the new number he gets from the parent. (e.g. if the number coming in, is higher than the current this.state.high, it will update this.state.high to the new number) After every minute a setInterval function it will take the states and construct a candle and pass it down to its own children.
the state are:
high, low, open, close, newCandle
I got it working by using
shouldComponentUpdate(nextProps:props, nextState:state){
if(this.props !== nextProps)
this.updateStates(nextProps.newTradePrice); //will update the high, low, open, close state
if(JSON.stringify(nextState.nextMinuteCandle) !== JSON.stringify(this.state.nextMinuteCandle) ) //once the one minute interval is up, there will be a function that will auto set the newCandle state to a new Candle base on the current high, low, open, close state
return true;
return false;
}
I read in the document that shouldComponentUpdate should only be used for optimization not to prevent something to reRender. I am using this to prevent reRender and infinite loop.
I've been stuck on this for days, I cant figure out a way to design this better. Any advice on how to design this better?
2nd related question:
In fact I am relying on shouldComponentUpdate for almost ALL my component too. This can't be right. e.g.
I have a CalculateAverageVolume child component, that takes in the this.state.newCandle. And update the volume every time the newCandle changes (i.e. every minute)
constructor(props: props) {
super(props);
this.state = {
[...],
currentAverage: 0,
showVolume: true
};
}
onCloseHandler()
{
this.setState({showVolume: false});
}
updateAvg(newCandleStick: CandleStick){
//do caluation and use this.setState to update the this.state.currentAverage
}
shouldComponentUpdate(nextProps:props, nextState:state)
{
if(JSON.stringify(this.props.candleStick) !== JSON.stringify(nextProps.candleStick) || this.state.showVolume !== nextState.showVolume){
this.updateAvg(nextProps.candleStick);
return true;
}
return false;
}
render() {
return (
<>
{(this.state.showVolume &&
<IndicatorCard
cardHeader="Volume"
currentInfo={this.state.currentAverage.toString()}
onCloseHandler={()=>this.onCloseHandler()}>
</IndicatorCard>
)}
</>
);
}
}
Can someone please teach me how to design this or restructure this? This works perfectly, but doesn't seem like the right way to do it
I would simplify the component like below.
import { useMemo, useState, memo, useCallback } from "react";
function Component({ candleStick }) {
// use props here to calculate average
const updateAverage = () => 0; // use candleStick props to calculate avg here
const [showVolume, setShowVolume] = useState();
// Compute the average from prop when component re-renders
// I would also add useMemo if `updateAverage` is an expensive function
// so that when prop remains same and `showVolume` changes we don't need to calculate it again
const currentAverage = useMemo(updateAverage, [candleStick]);
const onCloseHandler = useCallback(() => setShowVolume(val => !val), []);
return showVolume ? (
<IndicatorCard
cardHeader="Volume"
currentInfo={currentAverage}
onCloseHandler={onCloseHandler}
/>
) : null;
}
// If true is returned, component won't re-render.
// Btw React.memo by default would do shallow comparison
// But if deep comparison function is required, I would use lodash or other utility to do the check instead of JSON.stringify.
const arePropsEqual = (prev, next) =>
isEqual(prev.candleStick, next.candleStick);
export default memo(Component, arePropsEqual);
shouldComponentUpdate is usually reserved for discrete events that you can control. Howevr, it seems like you are dealing with a continuous stream of data.
Two ways to handle it:
Pass down a function reference that handles a stream to the child component and let that handle you state updates in your child component.
Use the context API to inform child component about the changes
Reference implementation :
Upadting State with Context API : https://javascript.plainenglish.io/react-context-api-part-2-updating-state-through-a-consumer-7be723b54d7b
Streams : https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
React with Streams : https://blog.bitsrc.io/how-to-render-streams-with-react-8986ad32fffa
I hit the same spot you're in when starting React. The problem here is that React, at least the basic aspects of it, isn't enough when you're talking about data flow. What you need to look into is a React data management framework, of which Redux is probably the most popular. Go look at Redux and make sure you're looking at the latest documentation based around hooks.
You'll say to yourself "Oh! That makes perfect sense" - I know I did.
Other, similar frameworks are React Query and React's own Context API. The main point I'm trying to make is that you really need data management to do the thing you're looking for.

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

set state in a callback of an async function

I am new to React, so bear with me please. I have a component that calls another component that takes a property. This property will get it's value on a callback of a function, something like this:
render(){
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp))
this.setState({myProp: p})
});
return <MyComponent myProp={this.state.myProp}/>
}
myFunc will or will not make an API request and depending on that will call the callback sooner or later. This seems to work fine when API request is made and the callback takes longer to return. However, when the request is not needed and callback returns instantaneously (or almost) I am getting a Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
What am I doing wrong and what is the right way to approach this? Where would be the right place to put this code? Basically what I need is to re-render MyComponenent if this.state.myProp changes
You shouldn't be calling setState inside the render method, you might end up having an infinite loop.
The call to myFunc should be somewhere else (depending on the business logic you have). When the function finishes, it will update the state and then trigger a re-render so MyComponent will get the latest value.
UPDATE
I don't know which conditions will require calling myFunc again, but you can do:
state = {
myProp: null // or some other value that MyComponent can handle as a null state
}
componentDidMount () {
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp)) // This is needed only if you get null back from the callback and you don't want to perform an unnecesary state update
this.setState({myProp: p})
}
}
render(){
const { myProp } = this.state
// You can also do if (!myProp) return null
return <MyComponent myProp={myProp}/>
}

Using setTimeout to avoid re-renders in React

There is a pattern that I use often that I feel must be an anti-pattern but I don't know a better alternative.
Occasionally my components may receive one or more events that mean that a re-render is necessary. Sometimes it is the case that I do not know how many times the event handlers will be called. To prevent multiple re-renders as a result of many calls to the handlers I do something like this:
_myEventHandler() { // may be called multiple times between renders
if (!this._updateQueued) {
this._updateQueued = true;
this._updateTimer = setTimeout(() => {
this._updateQueued = false;
this.forceUpdate();
}, 0);
}
}
The problem here is I feel it can't be performant due to the latency between code stopping and the event loop starting.
A real world example of this is when I am using react-visibility-sensor and I have multiple elements change their visibility at once, I don't want to re-render for each element, instead I want just one re-render once all the updates have been received.
Is there another better way to deal with multiple calls?
BTW: if you are going to use the above hack don't forget to call clearTimout(this._updateQueued) in your componentWillUnmount
A debounce will reduce the number of times a certain piece of code is run, regardless of how often it is called. Here is a rather simple implementation.
const debounce = (callable, time) => {
let timeout;
return function() {
const functionCall = () => callable.apply(this, arguments);
clearTimeout(timeout);
timeout = setTimeout(functionCall, time);
};
};
And this is how to use it.
const debouncedIteration = debouce(() => {
console.log("Iteration"); // This will be called once every 1000 milliseconds.
}, 1000);
while (true) {
debouncedIteration();
}
You can avoid re-renders using this lifecycle method shouldComponentUpdate (as also mentioned by #fungusanthrax). Keep this inside your react component :
shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
}
using isEqual from lodash here, make sure to include it.
This will only re-render your component when there's a change in props or state value.
To install lodash:
npm install -S lodash
and import isEqual in your component file :
import isEqual from 'lodash/isEqual';

How do I integrate rxjs observables with a plain React Component?

I am new to Rxjs and am trying to learn how to integrate it with a simple React component without any external wrapper/library. I got this working here:
const counter = new Subject()
class App extends Component {
state = {
number: 0
}
componentDidMount() {
counter.subscribe(
val => {
this.setState({ number: this.state.number + val })
}
)
}
increment = () => {
counter.next(+1)
}
decrement = () => {
counter.next(-1)
}
render() {
return (
<div style={styles}>
Current number {this.state.number}
<br /> <br />
<button onClick={this.increment}>Plus</button>
<button onClick={this.decrement}>Minus</button>
</div>
)
}
https://codesandbox.io/s/02j7qm2xw
I trouble is that this uses Subjects which is a known anti-pattern according to experts like Ben Lesh:
https://medium.com/#benlesh/on-the-subject-of-subjects-in-rxjs-2b08b7198b93
I tried doing this:
var counter = Observable.create(function (observer) {
// Yield a single value and complete
observer.next(0);
// Any cleanup logic might go here
return function () {
console.log('disposed');
}
});
class App extends Component {
state = {
number: 0
}
componentDidMount() {
counter.subscribe(
val => {
this.setState({ number: this.state.number + val })
}
)
}
increment = () => {
counter.next(+1)
}
decrement = () => {
counter.next(-1)
}
// - render
}
But this fails with the error: counter.next is not a function
So How would I use new Observable() or Observable.create()and use it to setState with a plain React component?
Because .next() is an Observer's method, NOT Observables.
The reason why Subject works simply because Subject itself is both an observer and an observable. When you call subject.next(), you are simply just updating the observable part, and notify all the observers about the change.
It can be quite confusing sometimes when comes to Observable and Observers. To make it simple, think of this way: Observable is someone who produces the data, a.k.a. data producers; while Observer is someone who consume the data, a.k.a. data consumer. In a simple analogy, consumer eats what is produced. For the same token, Observer(consumer) observes(eats) the observable (produced).
In your context (or at least React/Redux paradigm), Subject works better. That is because Subject has state. It keep tracks of the value over the production of data (job of the Observable). Every time the observable (the one inside Subject) changes, or update, any observers that subscribes to the Subject will get notified. See the pattern similar to redux here? Every time your redux store is updated, your view gets notified (and hence updated). In fact, if you are very used to reactive programming, you can eliminate the use of redux store completely, and fully replace them by Subjects and/or BehaviourSubjects.
For the post from Ben Lesh, he is merely stating this: Always use an Observable if possible, only use Subject when it is really needed. In that particular post, he is stating that a click event can just be an Observable; using Subject will be inappropriate. However, in your context, which is react/redux, using Subject is fine - because the Subject is used to keep track of the state of the store, and NOT the click event handler.
TLDR:
Use Subject if you want to keep track of a state of a variable
.next() is Observer's method, not Observable.

Resources