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.)
Related
Need a little guidance with conditional rendering in react
class Car extends React.Component {
id = '123456'
render() {
if("id".includes("1") === true){
return <h2>$$${id}</h2>;
}
else{
return <h2>{id}</h2>;
}
}
For rendering $$$ for certain condition , apart from using if else and rendering
is there any way to conditionally render ???
a better way and the return can be quite long in big applications
is there a way to write conditional and return the value according to it?
Your question is not very clear so do well to add some clarity if my answer is not satisfactory.
When you want to conditionally render in react you can use the short-circuiting property of the && operator.
For example:
(false && "Logged In")
//Logged In
So using this idea, you can do the same for components
function App(props) {
let element
if(props.loggedIn) {
element = <div>Logged In</div>
} else {
element = <div>Not Logged In</div>
}
return element
}
simply becomes
function App(props) {
return (
<div>
{
props.loggedIn &&
<h3>You're logged in as {props.data.username}</h3>
}
</div>
)
}
There's a variety of ways to do conditional rendering. Here is one example with a ternary operator for simple cases:
render() {
return <h2>{id.includes("1") ? "$$$" : ""}{id}</h2>
}
You can also look into using a switch statement, where you move the conditional logic outside of the render function.
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
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!
I've got this React parent component here. The children components at this point are just returning dropdown menus. I expected that componentWillReceiveProps would update the state here, which in turn should be passed to StopList as props. However, when state.selectedSub is changed through handleSubSelect, nothing happens and StopList doesn't receive any props.
Is my mistake with the asynchronous nature of componentWillReceiveProps? Is it in the wrong place in my code? Am I using the wrong lifecycle method?
// We're controlling all of our state here and using children
// components only to return lists and handle AJAX calls.
import React, { Component } from 'react';
import SubList from './SubList';
import StopList from './StopList';
class SubCheck extends Component {
constructor (props) {
super(props);
this.state = {
selectedSub: '--',
selectedStop: null,
stops: ['--'],
};
this.handleSubSelect.bind(this);
this.handleStopSelect.bind(this);
}
// We want the user to be able to select their specific subway
// stop, so obviously a different array of stops needs to be
// loaded for each subway. We're getting those from utils/stops.json.
componentWillReceiveProps(nextProps) {
var stopData = require('../utils/stops');
var stopsArray = [];
var newSub = nextProps.selectedSub
for(var i = 0; i < stopData.length; i++) {
var stop = stopData[i];
if (stop.stop_id.charAt(0) === this.state.selectedSub) {
stopsArray.push(stop.stop_name);
}
}
if (stopsArray.length !== 0 && newSub !== this.state.selectedSub) {
this.setState({stops: stopsArray});
}
}
handleSubSelect(event) {
this.setState({selectedSub:event.target.selectedSub});
}
handleStopSelect(event) {
this.setState({selectedStop:event.target.selectedStop})
}
render() {
return (
<div>
<SubList onSubSelect={this.handleSubSelect.bind(this)}/>
<StopList stops={this.state.stops} onStopSelect={this.handleStopSelect.bind(this)}/>
</div>
);
}
}
export default SubCheck;
You are duplicating data, and causing yourself headaches that aren't necessary.
Both selectedSub and selectedStop are being stored as props and as state attributes. You need to decide where this data lives and put it in a singular location.
The problem you are encountering entirely revolves round the fact that you are changing the state attribute and expecting this to trigger a change to your props. Just because they share a name does not mean they are the same value.
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.