Should I use useEffect()? - reactjs

I understand useEffect runs every time when my component is rendered. I am currently integrating a VideoConference application which creates after initialization an object called publisher. That publisher can be updated. For example, I can deactivate video etc.
I created two buttons that change the state of audio and video to either 0 or 1. I then pass these values to the component OTPublisher. The part I am confused about is if I actually should/need to use useEffect? It works both ways, also if I just insert it in the component directly without useEffect.
<OTPublisher
video={video}
audio={audio}
completionHandler={completionHandler}
>
OTPublisher.js
useEffect(() => {
if (publisher) {
audio ? publisher.publishAudio(true) : publisher.publishAudio(false);
video ? publisher.publishVideo(true) : publisher.publishVideo(false);
}
});
///
if (publisher) {
audio ? publisher.publishAudio(true) : publisher.publishAudio(false);
video ? publisher.publishVideo(true) : publisher.publishVideo(false);
}

The reason to useEffect is because it has extra features that directly having side effects in your component don't have like cleanup and only performing the effect when it's dependencies change.
The other big reason to keep the main part of the component render pure is the upcoming concurrent mode: https://reactjs.org/docs/concurrent-mode-intro.html
Concurrent mode drastically changes how components render enabling potentially multiple re-renders at once. React.StrictMode is a component that sets your components up and essentially renders them twice for every render to simulate Concurrent mode to ensure that you don't have issues.

Related

How to control a non-React component (BokehJS) in React?

Backstory
I want to include a BokehJS plot in my React component. The process for this is to render <div id="my_plot_id" className="bk-root"/> and call window.Bokeh.embed.embed_item(plotData, 'my_plot_id') which injects needed HTML into the DOM.
Because I want to control the BokehJS plot using the React component's state (i.e replace the plot with new generated plot data), I don't want to just call embed_item() in componentDidMount(). I've instead placed embed_item() in render() and added some code to remove child nodes of the container div prior to this call.
Problem
My React component renders 3 times on page load and although by the final render I have only one plot displayed, there is a brief moment (I think between the 2nd and 3rd/final render) where I see two plots.
Code
render()
{
let plotNode = document.getElementById('my_plot_id');
console.log(plotNode && plotNode.childElementCount);
while (plotNode && plotNode.firstChild) {
//remove any children
plotNode.removeChild(plotNode.firstChild);
}
const { plotData } = this.state;
window.Bokeh.embed.embed_item(plotData, 'my_plot_id');
return(
<div id="my_plot_id" className="bk-root"/>
)
}
In console I see:
null
0
2
Question
So it seems embed_item executes twice before the my_plot_id children are correctly detected.
Why is this happening and how can I resolve it? While the triple render may not be performance optimized I believe my component should be able to re-render as often as it needs to (within reason) without visual glitching like this, so I haven't focused my thought on ways to prevent re-rendering.
Interaction with DOM elements should never happen inside the render method. You should initiate the library on the element using the lifecycle method componentDidMount, update it based on props using componentDidUpdate and destroy it using componentWillUnmount.
The official React documentation has an example using jQuery which shows you the gist of how to handle other dom libraries.
At start plotNode is unable to reach 'my_plot_id'.
You can render null at start, when plotData is unavailable.
You can use componentDidUpdate().
In this case I would try shouldComponentUpdate() - update DOM node and return false to avoid rerendering.

Trigger react component render from rxjs observable subscription (skip the initial render)

I am trying to use redux state store in a redux app using a rxjs obserable wrapper. Source tutorial
I first tried this approach when I switched from ngrx to redux in angular. Now I’m using this pattern in a react app. However, I have a bit of an issue. When I subscribe to some state store stream I use setState(foo) to store the value in the component. This in turn triggers a new render cycle. I’d like to have one render cycle per component instead of 2 or more. 
I’ve been trying to inhibit the initial rendering of the component in order to have it triggered first and only once by the state store subscription. When you have multiple nested components and multiple subscriptions they tend to create wasteful renderings just to do the app init. I know that React does a great job of optimising for multiple renderings but still I find that keeping an eye on the rendering cycles is healthy for avoiding subtle bugs.
Any recommendation on how to trigger the first rendering from the state store subscription?
app.module.tsx
private subscribeToAppSettings() {
DEBUG.cmp && debug('Subscribe appSettings$');
appSettings$().pipe(
skip(1), // For REST api calls I skip the initial state
takeUntil(this.destroyed$),
)
.subscribe(settings => {
DEBUG.subscribe && debug('==> Observe appSettings$', [settings]);
this.setState({ settings });
});
}
As you can see AppModule and everything else is rendered twice because of this subscription. This is a filtered set of logs, showcasing when the app is running the render() methods. Just the init stage, no user interactions.
After reviewing the entire architecture again I figured that I need to manually set the initial state in the components. Now, the initial rendering is doing the useful work, and the second rendering will be ignored by the react change detection.
I still have the extra rendering cycles. However, I see that this is the state of affairs with change detection. A lot of things trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on performance. As they say: I'm barking at the wrong tree.
state.service.tsx
/** Access state changes as an observable stream */
export const store$ = new Observable<AppState>(observer => {
// All state store observable use `distinctUntilChanged()` operator.
// Without this initial state, `distinctUntilChanged()` will be unable to compare previous and current state.
// As a result, the webapi observable will miss the first response fron the server.
observer.next(appInitialState);
let appState: AppState;
store.subscribe( () => {
appState = store.getState();
observer.next(appState);
});
})
app.module.tsx
constructor(props: any) {
super(props);
DEBUG.construct && debug('Construct AppModule');
this.state = {
navigatorIsVisible: appInitialState.navigator.isVisible,
searchOverlayIsVisible: appInitialState.search.isVisible
} as State;
getAppSettings();
}
search.overlay.smart.tsx
searchOverlayIsVisible$().pipe(
takeUntil(this.destroyed$),
skip(1), // Ignore init state
)
.subscribe(searchOverlayIsVisible => {
DEBUG.subscribe && debug('Observe searchOverlayVisiblity$', searchOverlayIsVisible);
this.setState({ searchOverlayIsVisible });
this.state.searchOverlayIsVisible
});
search.overlay.service.tsx
export function toggleSearchOverlay(isVisible?: boolean) {
if (DEBUG.service && DEBUG.verbose) debug('Toggle search overlay', isVisible);
store.dispatch(
searchActions.toggleSearch(isVisible)
);
return searchOverlayIsVisible$();
}
export const searchOverlayIsVisible$ = () => store$.pipe(
map( state => SEARCH_VISIBILITY(state) ),
distinctUntilChanged()
);
Conclusions
Pushing the initial state in the store$ observable is necessary because we need all the state store observables to recieve their first state. Without this initial state distinctUntilChanged() will not be able to run the comparison between previous and current state. If distictUntilChanged is blocking the obsevables then we end up blocking responses from the webapi. This means we see empty pages even if the state store received the first set of data.
Notice that we are using the component constructor to setup the initial state. Thus, we use the first rendering cycle for useful work. The second rendering will be inhibited by using skip(1) in all state store observables.
Even if we setup init state in constructor we still keep the initial state in reducers as well. All the TOGGLE actions need an initial state to start from.
Be aware that, a lot of processes trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on DOM rendering performance.
This means it is close to impossible to have just one componentDidUpdate call per route change in LessonsPage. This means we still need to filter out duplicate calls to handlRouteParams().

react meteor data container doesn't update child when props change

I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).

Download SVGs rendered by React

I would like to implement an export function on my SPA, consisting on generating a bunch of SVGs (generated using React JSX) and downloading them one at once.
The number of files being huge, I can't display them at the same time on the browser. I have found React Download SVG which permits to download a SVG which is already inthe DOM.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
How could I download all my SVGs (zipping them in a file would be an advantage) without displaying them ?
Thanks in advance,
I worked with similiar problem - generate parametrized SVG paths for CNC purpose.
Problem wasn't with download as invoked manually (after DOM update) - batch download (zipped) planned, also.
Problem was: how to display SVG source/xml in another node/component for debugging - updated on every parameter change.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
This is true ... is nome sense, even harder while React Fiber can delay some DOM updates - but we have some possibilities to be notified.
componentDidUpdate() - but 'not called for the initial render'
ref callback - but '... defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element'
I prefer 2nd method for a few reasons, f.e. neutral to SVG (functional) component internals - however it's not guaranteed to be called in every use case - it won't be if not needed - OTOH CDM and CDU are guaranteed to have updated/proper refs when called.
Another hint found somewhere (SO?): use setTimeout to be safe/sure callback called after DOM update.
downloadableReference = el => {
console.log("DWNLD_REF ",el);
this.svgElement = el // save in wrapper, prevent old ref usage
if( !!el ) { // not null - second pass, fresh, updated
console.log("DWNLD_REF ready ", el.outerHTML )
setTimeout( ()=>{
this.notifyDownloadableDependants() // safest way - will be called after CDU
}, 0 )
}
}
This can be combined to batch the downloading. For UX I would render them sequentially - with progress visualisation, cancellable processing etc.
Another possibility: - maybe (not tested) use of react-jsx-parser be helpfull?

React / Flux, setting multiple states

I'm new to react and I am using states for my data models.
I have a menu that shows a user's profile picture through state. It's state because a user can change his profile picture.
I'd like the Menu to slide in from the left, initially hidden. Hence I'd like to add the Menu's open/close status as a state as well.
I'm using the standard Flux pattern.
Here is the relevant code:
Menu.js
_onChange:function(){
this.setState({
opened: MenuStore.getMenuState(),
profilePicUrl: MenuStore.getUserPic()
})
},
componentDidMount:function(){
MenuStore.addChangeListener(this._onChange)
}
MenuStore.js
MenuStore = assign({},EventEmitter.prototype,{
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
...(rest of class not shown)
})
MenuStore.dispatchToken = Dispatcher.register(function(action) {
switch(action.type) {
case ActionTypes.RECEIVE_USER_DATA:
_userData = action.details;
MenuStore.emitChange();
break;
case ActionTypes.TOGGLE_MENU:
_opened = !_opened;
MenuStore.emitChange();
break;
default:
// do nothing
}
});
Nav.js
toggleMenu:function(){
Actions.toggleMenu(); //in my actions.js it dispatches TOGGLE_MENU
}
render:function(){
return <div onClick={this.toggleMenu}>My Nav button</div>
}
I guess what I find wierd, is that I am setting the state of the User's profile picture without it having changed. Is this the correct way of doing things? Or should I emit separate change events and hence use separate callbacks, so that I set the states separately?
A related question is whether React will care if I set the state of something that hasn't changed. I.e does the diffing algo ignore the user's profile pic since it hasn't changed and therefore has no effect on React? OR does the fact that I've set the state in react, implicitly tell React that 'something has changed'?
Which is the correct React / Flux pattern? Set all the states in one callback? or set all the states separately?
There are a few things I learnt working with React and Flux that, I hope, can improve your approach:
Ship the whole state with the emitted event
In your example, you are emitting an event, and then the component is asking for data:
_onChange:function(){
this.setState({
opened: MenuStore.getMenuState(),
profilePicUrl: MenuStore.getUserPic()
})
}
I would suggest you move to a different pattern, where the store sends the whole state, no matter the event, every time it emits an event:
case ActionTypes.TOGGLE_MENU:
_opened = !_opened;
MenuStore.emitChange({opened: _opened, /* [...] */});
break;
Then in your component:
_onChange:function(snapshot){
this.setState({
opened: snapshot.opened,
profilePicUrl: snapshot.profilePicUrl
})
}
In this way your store scales up, no matter the amount of data you want to keep in the store.
Consider using Immutable.js or shipped immutability helpers
A related question is whether React will care if I set the state of something that hasn't changed.
React will trigger a virtual re-render in the virtual DOM. Then, it will execute the diff algorithm. As nothing has changed, nothing will be re-rendered.
You can avoid this by overriding shouldComponentUpdate:
Invoked before rendering when new props or state are being received. This method is not called for the initial render or when forceUpdate is used.
Use this as an opportunity to return false when you're certain that the transition to the new props and state will not require a component update.
I would strongly suggest that you start using either the immutability helpers, or Immutable.js. In this way it becomes easier to manage the whole re-rendering process, as it becomes trivial to understand when something has really changed.
Also, bear in mind that React is extremely fast. Unless you have > 100 components listening for changes, sometimes it is better to have some wasted re-render cycle, instead of writing a convoluted shouldComponentUpdate.
Code readability vs performance
I guess what I find weird, is that I am setting the state of the User's profile picture without it having changed.
This is really a trade off. If you have ~100 components listening to the same store, some of them interested in just one event, others in another one, then it would be the case to have different events (you would continue to send anyway the whole snapshot). Otherwise, I would suggest to keep your code simple: just publish one event.
Flux comes in different flavors
There are a few libraries now that implement ideas taken from Flux. I would suggest you have a look at Reflux and, especially, Redux.

Resources