React rendering delay - reactjs

I'm coming from Angluar world, where change detection is way more aggressive than in React. I'm heaving issue with rendering component that asynchronously receives image URLs. Below is sinppet:
componentDidUpdate(){
this.els = this.props.imagesUrls.map(url => {
return (
<img src={url}></img>
)
})
console.log(this.els)
}
render(){
return(
<div className="image-list">
IMAGE LIST
{this.props.imagesUrls.map(el => <img src={el}></img>)} // Method 1
{this.els} //Method 2
</div>
)
}
}
Method 1 works like charm, method 2 is delayed (i.e. component must recieve second time props to render provious props urls.
I assume this is simple issue related with asynchronosity, but I will appriciate any help and/or leadning material to help me avoid sucj pitfals.

In the second method, two things cause the delay,
First: componentDidUpdate is called after render(not called on the first time render) and hence the call is executed after the component is rendered
Second: You are setting the returned data to a class variable which doesn't cause a re-render, any other change which causes a re-render will only result in the data being displayed. You must use setState instead.
However, the first method is the right way to handle such a render

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.

Passing data based on another api response from redux

I have an api which will list what all access do the current user have, so once the app.js loads I am calling the api in the componentWillMount, so basically I have three routes , home, userslist, eachuser. So the home is a static text page.
userslist is a component where I list all the users, once you click on the edit icon of user it will take you to the details of the user in the eachuser component.
The problem is since the calls are async once the useraccessApi resolves and gets only the data I should call the usersListApi , by passing the useraccessApi response.
What I mean by happy flow is first user loads the localhost:3000/home so the useraccessApi will call and the redux have data, so while switching to userslist tab on componenWillMount it will work. But if the user directly selects localhost:3000/userlist it will throw error on componenWillMount so moved the code to componentWillRecieveProps().
So how can I resolve this issue. Or should I use mergeProps to resolve it.
App.js
componenWillMount(){
this.props.userAccessApi()
}
UsersList
componentWillMount(){
const {currentUserAccess} = this.props
// if its a happy flow else it will be currentUserAccess is undefined
if(currentUserAccess){
this.props.usersListApi(currentUserAccess)
}
}
// inorder to resolve it
componentWillRecieveProps(nextProps){
const {currentUserAccess} = nextProps
if(currentUserAccess){
this.props.usersListApi(currentUserAccess)
}
}
const mapStateToProps = (state) => {
return {
currentUserAccess: state.access
}
}
This is the expected behavior of React Lifecycle events.
For componentWillMount
This function is called right before the component’s first render, so at first glance it appears to be a perfect place to put data fetching logic.
But, there’s a “gotcha,” though: An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once. Which is why your dispatch function fails and you get undefined.
Instead you should use ComponentDidMountas the event in which you will fetch data.
By the time componentDidMount is called, the component has been rendered once.
In practice, componentDidMount is the best place to put calls to fetch data.
Using it makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.

I am trying to understand why componentDidUpdate is not firing for specific component

I am having some issues with a particular component not properly updating. This component is effectively modeled after each of the other components, the only difference that I can determine is that it receives props that are array elements; although I have switched the variable being passed so that it renders store elements that are working elsewhere, though I am still getting the same update issue.
The weird thing about this is that the component update does fire, but only when one of the other elements that is properly updating is triggered, so it logs the console object on this instance for two different checks within the componentDidUpdate function.
The overall design is a basic React/Redux app, with a component that is designed to hold/render the audio events. I have a MainLayout component that renders the AudioEngine component, followed by multiple "Panel" components that are only specified for the UI. It is this component that is passed the redux store. It appears that the redusx store is handling state properly, and is passing the data back as expected; however, it is only this element of the UI that is failing to properly trigger an update.
Within the AudioEngine component:
if(this.props.lfoRate != prevProps.lfoRate){
console.log(this.state.lfoRate)
this.setState({
lfoRate: this.props.lfoRate
}, () => {
this.lfo['osc'].frequency.value = this.state.lfoRate
});
}
Here is the return from the MainLayout component, which receives the stor/props (sorry this is still a bit of a work in progress):
return(
<div >
<Header />
<div style={mainLayoutStyle} className={"main-layout"}>
{
this.props.keyOn ? (<AudioEngine lfoRate={this.props.LFObj[2]} gain={this.props.masterGain} freq={this.props.masterFreq} oscArray={this.props.oscArray}
lfoType={this.props.LFObj[1]} lfoFreq={this.props.LFObj[3]} count={count}/>) : (count % 2 == 0 ? (<AudioEngine count={0} gain={0} freq={this.props.masterFreq} oscArray={this.props.oscArray}
lfoType={this.props.LFObj[1]} lfoRate={this.props.LFObj[2]} lfoFreq={this.props.LFObj[3]}/>) : (''))
}
<MainPanel keyToggle={this.props.keyToggle} changeMasterGain={this.props.changeMasterGain}
masterGain={this.props.masterGain} keyOn={this.props.keyOn} baseFrequency={this.props.masterFreq}
changeBaseFrequency={this.props.changeBaseFrequency} />
<div style={{display: 'flex', flexFlow:'column'}} >
<Oscillator addOsc={this.props.addOsc} subOsc={this.props.subOsc} oscArray={this.props.oscArray} />
<LfoPanel lfoRate={this.props.LFObj[2]} lfoFreq={this.props.LFObj[3]} onChange={this.props.changeLFO}
lfoType={this.props.LFObj[1]}/>
</div>
</div>
</div>
)
The LfoPanel component is designed much of the same way as the others...
Any pointers would be quite helpful, perhaps it is passing array elements as properties? If that is the case, that seems like a strange gotcha. Thank-you in advance...
Ok, so after some further research, I realized that it was in fact a mutability issue from Redux. Even though it was technically updating the state, it was not properly immutable. I ended up using the primary solution from this question.
Thanks to those who looked / took time to respond. Hopefully this reference will save another React-Redux a headache later on :)

Extremely slow React list render

We are experiencing some frustrating issues with React.
We have a form, consisting from a search form and a search result list.
As seen below in the code. filter and content.
Whenever the user types in the search field, there is a debounce and a call to a rest service.
The result then populates the search result (content)
Even with as little as 15 items in the list, this is insanely slow.
it takes about 300 ms per update.
In production mode, there is no issue. only in dev mode.
Also, removing propTypes makes it much faster but still slow.
We can see that the ContactSearchResultLayout is being rendered 3 times per keystroke, while it really just should care about the result of the rest call.
What are our best bets here?
is the container component kind of pattern here wrong for our usecase, does it mean that if something in the SearchPageLayout props changes, the entire list will also be re-rendered?
We have a version that pretty much bypass React and just render item by item as they come in from the service call.
Which is super fast, but on the other hand, much less maintainable.
Is there some idiomatic way to make this work with React?
<SearchPageLayout
filter={
<ContactSearchForm
allTeams={allTeams}
allAreasOfExp={allAreasOfExp}
allResponsiblePeople={allResponsiblePeople}
allTags={allTags}
detailed={props.searchFormExpanded}
onSearchFieldBlur={() => props.setSearchFormExpanded(false)}
onSearchFieldFocus={() => props.setSearchFormExpanded(true)}
/>
}
content={
<ContactSearchResultLayout //<-- this rerenders too often
items={items.map(item => (
<ContactCard
key={item.PersonId}
areasOfExpertise={item.AreasOfExperise}
cellPhone={item.CellPhone}
city={item.City}
One reason for this as I see it, is that items is the result of a map operation and thus, causes a new array of components to be generated.
But how do we bypass this?
Thoughts?
Anonymous function will get rendered each time.
I'll create another method for creating the items:
getItems() {
return (
items.map(item => (
<ContactCard
key={item.PersonId}
areasOfExpertise={item.AreasOfExperise}
cellPhone={item.CellPhone}
city={item.City}
/>
)
)
}
<ContactSearchResultLayout
items={this.getItems()}
/>
How to check if props change and if you should re-render the code:
you can use react "shouldComponentUpdate"
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
componentWillUpdate(nextProps, nextState) {
//here you can compare your current state and props
// with the next state and props
// be sure to return boolean to decide to render or not
}

Meteor handle.ready() in render() not triggering rerender of component

I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.

Resources