Unable to prevent rerender with shouldComponentUpdate() or with PureComponent - reactjs

In /home/:id I have a <LogButtons/> when its clicked logOn() get called so logsignPopUp variable become a <logForm/> component.
In the same page I have a <IframeYoutubeComponent/>, I want to prevent it to rerender when the <logForm/> pop on the screen so the video isn't reloaded.
home.js :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = { logsign: "" };
this.logOn = this.logOn.bind(this);
this.signOn = this.signOn.bind(this);
}
logOn() {
this.setState({ logsign: "log" });
}
render() {
let logsignPopUp = this.props.logsign === "log" ? <LogForm/> : this.state.logsign;
let homePage =
<div>
{logsignPopUp}
<div>
<LogButtons logOn={this.logOn}/>
</div>
<div>
<IframeYoutubeComponent paramsId={this.props.match.params.paramsId}/>
</div>
</div>;
return (
<div>
<Route exact path="/home/:id" component={() => <div>{homePage}</div> } />
</div>
);
}
}
iframeYoutubeComponent.js :
export class IframYoutubeComponent extends Component {
render() {
//this.props.youtube come from Redux state
let src = this.props.youtube.find(el => el.youtubeId === this.props.paramsId);
return (
<iframe src={"https://www.youtube.com/embed/" + src}></iframe>
);
}
}
I tried to return false in shouldComponentUpdate() but its not even called :
shouldComponentUpdate() {
console.log("test");
return false;
}
I tried to use a PureComponent for <IframeYoutubeComponent/> but the video still reload when the <logForm/> pop.
I tried to add key to my components and also tried to put this.state.logsign in Redux but nothing worked out.
I started react since 2 months so I might miss something obvious but I can't find out what... Any idea ?

That's because you are passing an arrow function in a component prop to the Route. This way everytime you generate a new function.
You should pass a react component in this prop or at least a function that returns JSX but this function should be defined once. For example as a class method.
Something like:
<div>
<Route exact path="/home/:id" component={this.renderHomePage} />
</div>
But then of course you have to refactor your logic regarding this logsign prop.

Related

How can I import element to another React component?

I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo

Migration from componentWillReceiveProps to getDerivedStateFromProps

I am learning reactjs and I wrote component with the method componentWillReceiveProps (cWRP) but I read that it is deprecated and it must replace with getDerivedStateFromProps (gDSFP) - https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
Please note that the following code has the sole purpose of illustrating my problem and questions. It is not a full code.
App.js file :
import React from 'react';
import './App.css';
import Display from './component.js'
class App extends React.Component {
state={resetCounter:false}
resetCounter= () => this.setState( {resetCounter: true} );
render() {
return (
<div className="App">
<header className="App-header">
<Display resetCounter={this.state.resetCounter}></Display>
<div>
<p></p><p></p>
<button onClick={this.resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
componentDidUpdate () {
if (this.state.resetCounter!==false)
this.setState( {resetCounter: false} );
}
}
export default App;
component.js file
import React from 'react'
class Display extends React.Component {
constructor() {
super();
this.state = this.resetState();
this.state.generalCounter=0;
}
/* method to avoid code duplication in constructor and cWRP
could not be used with getDerivedStateFromProps */
resetState = () => ({resettableCounter: 0,});
componentWillReceiveProps(nextProps) {
if (nextProps.resetCounter===true)
this.setState(this.resetState())
}
render() {
return (
<>
<div>
<div>general counter : {this.state.generalCounter}</div>
<div>resettable counter : {this.state.resettableCounter}</div>
</div>
<div>
<button onClick={this.incCounters}>+</button>
<button onClick={this.decCounters}>-</button>
</div>
</>
)
}
incCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter+1,
generalCounter: this.state.generalCounter+1
}
)
decCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter-1,
generalCounter: this.state.generalCounter-1
}
)
}
export default Display
In the state of the component, there is a resettable part and a non resettable one. A method resetState is used to avoid code duplication in the constructor and in cWRP.
To replace cWRP by gDSFP, I wrote a class method because instance method could NOT be called in gDSFP (this is not usable)
...
constructor() {
super();
this.state = Display.resetState();
this.state.generalCounter=0;
}
static resetState () {
return ({resettableCounter: 0,});
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.resetCounter === true) {
return Display.resetState();
} else {
return null;
}
}
...
With this solution, it is very easy to modify all my components but I am not sure that it is a good mean.
I wonder if I have a misconception and if I should rewrite my components to separate them into Fully controlled components and Fully uncontrolled components with a key ( https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions).
For example, in this case, do I have to write :
One Fully uncontrolled components for the resettable counter
One Fully controlled one for the non resettable counter
A parent component with the +/- buttons to render them.
I ask this question because in some cases, it will be much work, so I want to be sure before continuing.
You would want to keep the gdsfp version in your post if your component depends on some outside props, which you don't have controll over (such as JSON returned or 3rd party render props component, etc).
It looks like you have a full control over what's passed down to the Display. You can pass down an initial resettableCounter value down to Display.
The advantage is two-folds.
Your Display props shows what the Display does - Making it more descriptivie/readable.
It's easier to maintain, as you don't have to massage the data.
For your particular case, Fully uncontrolled component with a key seems to make more sense, as Display should accept the initial value to show, but is responsible for managing the reseetableCounter.
Unless it's absolutely unavoidable, don't create components which control their siblings (or parents). Instead, lift state to a common ancestor:
const Display = ({
generalCounter,
resettableCounter,
incrementCounters,
decrementCounters,
}) => (
<div>
<div>General Counter: {generalCounter}</div>
<div>Resettable Counter: {resettableCounter}</div>
<button onClick={incrementCounters}>Increment</button>
<button onClick={decrementCounters}>Decrement</button>
</div>
);
class DisplayContainer extends React.Component {
state = {
generalCounter: 0,
resettableCounter: 0,
};
incrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter + 1,
resettableCounter: prevState.resettableCounter + 1,
}));
decrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter - 1,
resettableCounter: prevState.resettableCounter - 1,
}));
resetResettableCounter = () => this.setState({
resettableCounter: 0,
});
render() {
return (
<React.Fragment>
<Display
{...this.state}
incrementCounters={this.incrementCounters}
decrementCounters={this.decrementCounters}
/>
<button onClick={this.resetResettableCounter}>
Reset Resettable Counter
</button>
</React.Fragment>
);
}
}
const App = () => (
<div>
<DisplayContainer />
</div>
);
An alternative approach would be something like Redux (which effectively lifts state out of React).

Component mounts several times, but only renders once

I've got a component:
class Swatch1 extends Component {
componentWillMount() {
console.log("MERCHANT");
console.log(this.props.merchant);
}
lightestColor(colors) {
// Color calculations that set highestColor
console.log(highestColor);
}
render() {
return (
<ColorExtractor
src={this.props.merchant.merchant.coverPhoto}
getColors={colors => this.lightestColor(colors)}
/>
);
}
}
// mapStateToProps and mapDispatchToProps here
// Redux connection
const Swatch = connect(mapStateToProps, mapDispatchToProps)(Swatch1);
Now this component is being called every time another component is rendered, and for my current use-case, it should be being rendered three times...
Other component:
class Card extends Component {
render() {
return (
<div className="card titlecard" style={{backgroundImage: `url(${this.props.merchant.merchant.coverPhoto})`}}>
<Swatch merchant={this.props.merchant.merchant} />
....
CardRow component in it's entirely, including how the '' objects are being added.
class CardRow extends Component {
list = []
componentWillMount() {
this.list.push(<Card merchant={this.props.merchant} />)
}
render() {
return (
<div className="App">
<ScrollMenu
data={this.list}
/>
</div>
);
}
}
Right now, this is creating three <CardRow /> objects.
So theoretically, every time CardRow is rendered, Card (and thus Swatch1) should be rendered, and this seems to be consistent with componentWillMount() in Swatch1, but NOT lightestColor().
Why is componentWillMount() correctly being called multiple times, but not lightestColor()?
Shouldn't they both be called each time the Swatch1 component renders (which should be 3 times?)

How can I change what is rendred with a click event in React

I'm new to React, and I'm trying to figure out how to adjust what appears in render based on a click event. My component receives two props "front" and "back". I want the component to display this.props.front upon rendering and change to this.props.back when the div is clicked. I'm having trouble figuring out how to accomplish this in my handleClick function.
Any help would be appreciated!
import React, { Component } from 'react';
class Card extends Component {
handleClick = event => {
}
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.front}</h1>
</div>
);
}
}
export default Card;
You could add a state to this component which is a boolean that toggles itself
class Card extends Component {
constructor(props) {
this.state = {
showFront: true
}
}...
And than use your handleClick method to switch the state back and forth
handleClick = (e) => {
this.setState({showFront: !this.state.showFront})
}
And in your render function you could put a conditional to show
render() {
return (
<div className="Card" onClick={this.handleClick}>
{
this.state.showFront
? <h1>{this.props.front}</h1>
: <h1>{this.props.back}</h1>
}
</div>
);
}
A comment to this answer was made but was deleted - i think it's a subject worth touching.
the comment said you should use the setState(updater()) and not pass an object.
it's true that when the app becomes more complex, you have several state updates together and data states may not be what you believe they are at that moment, updater function is apropriate (setState is async and could batch calls this is why we have the function that flushes all and helps us maintain state integrity comparing old states with new ones.
but for this answer and the complexity of the question an updater isn't necessary and the code should work just fine (and it gets to the point of using state and toggling which is the right way of doing what was asked).
you can use the updater function any time you please - even for the most simplest state change. And like said here, maybe it is best practice to just always use it :)
for more reference
React.Compoment setState & Updater function
In react you trigger render by changing the state of component. If this component needs to recieve props "front" and "back" then parent component should have saved in state if the state is "front" or "back" and pass down to component callback function to handle change. Something like:
import React, { Component } from 'react';
class ParentCard extends Component {
state = { isFront: true };
handleClick = event => {
this.setState({isFront: !this.state.isFront})
}
render = () => {
const { front } = this.state;
return (
<Card front={front} onClick={this.handleClick} />
);
};
export default ParentCard;
Also you can make Card component "pure" just by creating it as function which returns JSX.
import React from 'react';
const Card = ( { isFront, onClick } ) => {
return (
<div className="Card" onClick={onClick}>
<h1>{isFront ? `text if is front` : `text if it is not`}</h1>
</div>
);
}
}
export default Card;
Hope it helps :)
I'd say in that case you want to use state rather than props here, particularly when the state you want to change is being dictated by the component itself.
class Card extends Component {
state = {
mode: 'front' // default state to front
}
handleClick = () => this.setState({ mode: 'back' })
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.mode}</h1>
</div>
);
}
}
export default Card;
If this is really a toggle then of course you can use a Boolean flag instead, but you get the idea.
This component itself is currently not set up as a stateless functional component so if thats what you also wanted to achieve. Youll want to make these changes as well as pass props of a boolean in your stateful component.
import React from 'react';
const Card = (props) => {
return (
<div className="Card" onClick={props.handleClick}>
{props.showFront
?
<h1>props.front</h1>
:
<h1>props.back</h1>
}
</div>
);
}
export default Card;
you'll want to utilize the previous state to toggle your state because it could cause issues later down the road with batching, so your stateful component should look something like:
import React, {Component} from "React";
class StatefulCard extends Component {
state = {
showFront: true // default state to front
}
handleClick = () => {
this.setState(prevState => {
return {
showFront: !prevState.showFront
}
}
}
render() {
return (
<div>
<Card
handleClick={this.handleClick}
showFront={this.state.showFront}
/>
</div>
);
}
}
export default Card;

react component lifecycle to execute before render

I want to have some check before react component is rendered..
Lets say I have a component which renders something like
render() {
const {videos, actions} = this.props
return (
<div>
<List />
</div>
)
}
But before rendering the list I want to have custom check on the list...
What I meant to say is before showing the <List /> component I want to have some check and only after that check is finished I want to show the component with render method ..
The check I am mentioning here might be anything like the list should be 10 in count or anything.
I have tried following
componentWillUpdate(){
console.log("Component will update")
}
shouldComponentUpdate(){
console.log("Should component update")
}
From the react documentation I knew that both of them are called before render... But when I do this nothing is printed in the console but render method is called..
I know that componentWillMount() is called before render but what I want is a function which calls render only after the completion or return from the function
Hope you understand and need help
In the render() function you can run checks before you return the JSX/elements:
render() {
// run any checks here
const {videos, actions} = this.props
return (
<div>
<List />
</div>
)
}
You can return early from render with a different result. When the props change, render will be called again with newer props.
class MyComponent {
render() {
const { items } = this.props;
if (items.length < 10) return <span>Loading...</span>;
return <List items={items} />;
}
}

Resources