Selected element within list in React - javascript

I want to apply different style for selected element from a long list.
I'm passing to React component element as props: currentId and selectedId.
Inside render function, I compare both ids and apply proper styles.
When clicking any element from the list, I fire an action with the new selected Id and all elements in the list will re-render(because selectedId prop does change).
If the list has 1000 element and I click one of them, It would be nice to only update 2 elements (new selected and deselected ones) not all of them.
Is these a better way to handle this scenario in React?
Update: Add code example
List component:
const MyList = (props) => {
const items = props.items;
const selectedId = props.selectedId;
return (
<div>
{items.map((item) => (
<MyItem
currentId={item.id}
selectedId={selectedId}
key={item.id}
content={props.content}
/>
))}
</div>
);
};
Item component:
const MyItem = (props) => {
const isSelected = props.currentId === props.selectedId;
return (
<div className={isSelected ? 'selected-item' : ''}>
<h1>{props.currentId}</h1>
</div>
);
};

You can implement shouldComponentUpdate logic to prevent components from rerendering. Generally this is a bad idea (more on that later) but it does seem to apply to your use case. In general it is better to simply use PureComponent to prevent unneeded rerenders. This implements shouldComponentUpdate logic that compares state and props and if neither has changed, no update occurs.
Without seeing your code this is my best guess as to what shouldComponentUpdate might look like in your context:
shouldComponentUpdate(nextProps) {
if(this.state.isSelected && this.state.id !== nextProps.selectedId) {
return true;
} else if (!this.state.isSelected && this.state.id === nextProps.selectedId) {
return true;
}
return false;
}
Note that this means that a rerender will not happen unless shouldComponentUpdate returns true or you call this.forceUpdate() on that component. Your component won't even render if this.setState() is called, unless you add in more specific logic so that shouldComponentUpdate returns true on state changes. This can lead to difficult to debug problems, where your UI fails to reflect changes in application state but no obvious error occurs. This behavior doesn't apply to child components, so if these components have children they will still rerender as expected. If you decide to implement shouldComponentUpdate, you can add logic to compare state as well by declaring the nextState parameter:
shouldComponentUpdate(nextProps, nextState) {
if(this.state.isSelected && this.state.id !== nextProps.selectedId) {
return true;
} else if (!this.state.isSelected && this.state.id === nextProps.selectedId) {
return true;
} else if (/*compare this.state to nextState */) { .... }
return false;
}
Implementing your own shouldComponentUpdate is tricky, and may require you to restructure your code for best results (for example, passing an isSelected variable to your components instead of allowing those components to decide whether or not they are selected might allow you to easily implement PureComponent). Best of luck!

Related

How do i prevent my React component from rendering unnecessarily?

I have a react app. On this app I am rendering 10 tables. When a user makes a change to a table I only want that one table to re-render, not all 10.
To accomplish this task I have used React.useMemo() with a comparer function. Here it is:
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
const { categoryTotal: ctPrev, ...prev } = prevProps;
const { categoryTotal: ctNext, ...next } = nextProps;
if (
!ctPrev.totalPrice.eq(ctNext.totalPrice) &&
!ctPrev.totalWeight.eq(ctNext.totalWeight) &&
!ctPrev.totalWorn.eq(ctNext.totalWorn) &&
!ctPrev.totalConsumable.eq(ctNext.totalConsumable)
) {
console.log('totals did change')
return false;
}
for (var key in next) {
if (next[key] !== prev[key]) {
console.log('not equal', key);
return false;
}
}
console.log('props did not change')
return true;
}
export default React.memo(CategoryTable, areEqual);
I have verified that true is being returned for every table except the one that changes. So only that one table should re-render and not all 10 right? Wrong. Here is my flamegraph:
The name of my table component is CategoryTable. As you can see, the CategoryTable (memo) is grayed out but the subsequent CategoryTable is green and renders as does all of its children. I have confirmed that every category table is rendering by putting a console.log in the CategoryTable component.
How do I actually stop this component from re-rendering? Also does react.memo stop all components below in the tree from rendering or just the wrapped component?
React.memo return cach if the value didnt change its really usefull but in your case you can try pureComponent ,it prevent to render the children components if their props dont change

Why does this React/Mobx observer component not rerender when its observable props change?

I have a React component which renders a table of data from its props. The data is wrapped in MobX's observable(). From time to time, an asynchronous operation (AJAX call) adds a new row to the data source, which should cause a new table row to render. This is not the case.
A minimal example of the component in question is below:
#observer
class MyDashboard extends React.Component {
render() {
return (
<Table
columns={this.props.columns}
dataSource={this.props.model.contentTypes}
/>
);
}
#action.bound
private onThingHappened = async () => {
const thing = await asyncThing();
runInAction('onThingHappened', () => {
this.props.model.contentTypes.push(thing);
});
}
}
The prop model is an observable object. model.contentTypes is an array of objects. The table renders correctly on the first draw; it's subsequent re-renders which are problematic.
The bound action onThingHappened works as expected if the await is removed. To put it differently - model changes which occur before the first await trigger re-renders as expected.
The runInAction does not seem to noticably change anything.
However, if I draw model.contentTypes in the component itself - rather than simply passing it down to a child component (<Table> in this case), the entire thing works as expected. For example:
return (
<React.Fragment>
<div style={{display: 'none'}}>{this.props.model.contentTypes.length}</div>
<Table
columns={this.props.columns}
dataSource={this.props.model.contentTypes}
/>
</React.Fragment>
)
In this render function, dereferencing the model.contentTypes array seems to be enough to trigger the re-render in onThingHappened, even after an await.
I don't use mobx but after some research:
wrapping <Table/> in observer() should work ... if <Table/> render uses the value directly, it can fail if passed to (unwrapped) subcomponent;
'Mobx traces access' then probably it's enough to force mobx to react by 'unnecessary' access.
Try sth like:
render() {
const amount = this.props.model.contentTypes.length;
if( !amount ) return null; // to avoid 'amount unused, no unused ...'
return (
<Table
columns={this.props.columns}
dataSource={this.props.model.contentTypes}
/>
);
}

Meeting multiple conditions that aren’t set simultaneously in componentWillReceiveProps

I need to use componentWillReceiveProps() to call a method in my component once three conditions are met. Two of these conditions compare current props to next props, and those two receive their values via an Ajax request. The problem is not all conditions will be true at the same time.
For example.
export class Styles extends Component {
componentWillReceiveProps(nextProps) {
if (
!_.isEmpty(nextProps.one) && // set via ajax request
!isEqual(this.props.two, nextProps.two) &&
!isEqual(this.props.three, nextProps.three) // set via ajax request
) {
this.myMethod();
}
}
myMethod() {
… do something
}
render() {
return (
<div />
)
}
}
Because two of the props are being set with an Ajax response, I can’t be sure when those values are set and when they’ll fulfill the condition. I obviously need to achieve three true values in order to call this.myMethod(), but instead I get various combinations of true/false at any given time. It’s a bit of a lottery scenario.
Do I ultimately need to manage each of these conditions temporarily in state, then clear them out when they’re met?
You could do this without the deprecated componentWillReceiveProps with something like this:
export class Styles extends Component {
isDirtyTwo = false;
isDirtyThree = false;
..
componentDidUpdate(prevProps) {
this.isDirtyTwo = this.isDirtyTwo || !isEqual(prevProps.two, this.props.two);
this.isDirtyThree = this.isDirtyThree || !isEqual(prevProps.three, this.props.three);
if (!_.isEmpty(this.props.one) && this.isDirtyTwo && this.isDirtyThree) {
this.isDirtyTwo = false;
this.isDirtyThree = false;
this.myMethod();
}
}
..
}
This will call myMethod when one is empty and both other props have changed at some point. (I'm assuming that once one is empty it stays that way, but if not, you could add another property for it.)

How to read child elements from a React component

I want to get a list of all child elements of a react component.
For example:
Parent.js
<Parent>
<SomeComponent/>
</Parent>
SomeComponent.js
<SomeComponent>
<ChildElement1/>
<ClhidElement2/>
</SomeComponent>
So in Parent.js I want to get ChildElement1 and ChildElement2.
Is that possible?
My Use case is:
I'm passing form fields (Field component) to a generic Form component. The Form element receives an object of default values and also other things (what CRUD/resource it is related to for example). And it must inject these values in the Fields. Instead of passing all fields one by one and avoid repetion I created containers like "UserFields" and a few others and they are the ones that have the Fields components. So I need Form to read the Fields in UserFields. But since the fields are already inside UserFields, I can't figure out how to get them.
React is designed to be unidirectional data flow and following Flux architecture, and hence to keep best practices, it's always top down (from parent to child, not bidirectional).
However, you can achieve them in several options such as implementing React with redux or React Context
I am considering that your child components are mapped from array inside <SomeComponent />
Try this inside your parent
state = {
child: []
}
renderChildren = () => {
if(this.state.child.length > 0) {
return this.state.child.map(e => {
return (
<div>{e}</div>
)
})
}
}
returnChild = (data) => {
var child = [];
for(var i = 0; i < data.length; i++) {
child.push(data[i])
}
this.setState(prevState => ({child: [...prevState.child, child]}));
}
return (
<div>
<SomeComponent returnChild={(child) => this.returnChild(child)} />
{this.renderChildren()}
</div>
)
Add this method to your <SomeComponent /> component like this along with other code.
onGettingMoreChild = (child) => {
this.props.returnChild(child)
}
Don't forget to call onGettingMoreChild whenever there is a new child created.
I have not tested this code. Please playaround with it if needed. Also, remember to pass in your entire view as child to the method onGettingMoreChild
Example of child variable passed to onGettingMoreChild is
<div>I am child one!!</div>

Error when using if, when updating the state in react

I wanted to update this variable when the condition is true, but it doesn't let me, thanks
constructor(props){
super(props);
var isActive = false;
this.props.items.map(item => (
if(item.name == this.props.product.name) {
isActive = true;
}
));
this.state = {
active: isActive
};
console.log(isActive);
this.handleClick = this.handleClick.bind(this);
}
Reason is, you forgot to use {}, If you are using any condition or wants to do some calculation use {} and put all the statement inside that. One more thing since you just want to iterate the array means not returning anything, so i that case use forEach instead of map, Use this:
this.props.items.forEach(item => { //here
if(item.name == this.props.product.name) {
isActive = true;
}
});
Check this code it will produce the same error:
[1,2,3,4].forEach(el=>(
if(el % 2 == 0)
console.log(el);
))
Check the working example:
[1,2,3,4].forEach(el=>{
if(el % 2 == 0)
console.log(el);
})
You really aren't passing an 'active' property nor state. You are trying to set a computed property, much like Ember (see How to setup Ember like computed properties in Immutablejs and Redux and Flux and React). This was considered for React and dismissed.
You should just create a function, isActive() and call it when needed. I expect you only need it when rendering a set of individual item components for which isActive is a property.
That said, there are ways of cramming this into a React codebase, but your code will be unmaintainable.

Categories