React - Does changing an unused prop rerender a component? - javascript

I'm new to React and I have a doubt about rerendering and I haven't found answer regarding this specific case.
Let's assume I have a <Parent /> component and a <Child myProp='10' /> component and that the Child component does not use that myProp prop in the return statement, nor anything related to that prop, (in other words, the child will always return the same html elements independent of whatever that number prop is), I just want to use that number prop to identify that specific component to pass it back to the parent in an event handler. If that number prop changes, will the Child component rerender? Here is a simplified code example of what I mean:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
number1: 5,
number10000: 20
};
this.eventHandler=this.eventHandler.bind(this);
};
eventHandler(argGivenByChild){
//setState depending on which Child was clicked, argGivenByChild will be the identifier passed to the Child.
}
render() {
return (
<div>
<Child identifier={this.state.number1} clickHandler={this.eventHandler}/>
<Child identifier={this.state.number10000} clickHandler={this.eventHandler}/>
</div>
)
}
}
function Child(props) {
return (
<div onClick={()=>props.clickHandler(props.identifier)}>
<h1>Some heading</h1>
<p>Some paragraph</p>
</div>
)
}
Whenever I click any of the Child, the identifier prop will change, but the Child never uses it to visually render anything anyways, it's only passed to the event handler when it's clicked on. So, in that case, does React rerender the Child to account for the prop change, even though nothing changed on screen?
On one hand, I think there's nothing to rerender, as both the html and css remain the same (the Virtual and real DOM remain the same, don't they?), but on the other hand, internally the component is being modified, so maybe it has to rerender to reflect that change to the component, particularly in the event handler? What exactly happens in this case?
Thanks in advance

Related

How to use Props to jQuery in React component

I hope you understand this simple example.
I tried to change the background color of my HTML element on first render by handling it in React Component with a bit help of jQuery.
This is the code inside my React Component (the props is passed from state of Parent Component):
class QuoteBox extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
$('#root').css('background-color', this.props.color);
$('#text').css('color', this.props.color);
$('#author').css('color', this.props.color);
}
render() {
return (
<div>
<div id="quote-box" className="quote-box">
<div className="quote">
<span id="text" class="quote-text">{this.props.quote}</span>
</div>
<div id="author" className="quote-author">
<span>- {this.props.author}</span>
</div>
</div>
)
}
The code inside componentDidMount() seem doesn't recognize the this.props.color. But if change to $('#root').css('background-color', 'green'); it's immediately change the background to green on first render.
But in render() it recognize another props.
So what did I do wrong? Is there a way to target HTML element using jQuery inside React?
This most likely happens, because on the first render the props.color is not set. It is most likely, set on the parent element causing a re-render of the QuoteBox, which will not run the componentDidMount which is only run on the first render.
Besides the fact that you should not mix jQuery DOM manipulation with React, just to test your scenario, try using componentDidUpdate instead and it will most likely fix your issue.

Can React re-render only a portion of component?

Thinking efficiency-wise, is it possible to re-render only a portion of a component's render method?
I'm not talking about shouldComponentUpdate, which asks if the whole component should re-render.
Let's say I have:
class Cmponent extends React.Component{
render(){
return(
<div className="div1"></div>
<div className="div2"></div>
<div className="div3"></div>
);
}
}
Assume I want to dynamically display .div3. For example using a state flag showDiv3 with a toggle, e.g:
class Cmponent extends React.Component{
//assume a code that initializes showDiv3 and provides a toggle function
render(){
return(
<div className="div1"></div>
<div className="div2"></div>
{this.state.showDiv3 ? (<div className="div3"></div>) : null}
);
}
}
Is it possible in React to tell the component it should only re-render div3 when I toggle it? Or it'll always re-render the whole component via a state change?
I assume that on small components (such as this) it won't matter much, but on bigger ones it might make a big impact on performance (right?).
class Component extends React.Component {
render() {
return (
<>
<div className="div1"></div>
<div className="div2"></div>
{this.state.showDiv3 ? <div className="div3"></div> : null}
</>
);
}
}
The Component itself will re-render (the render function will be called) because of setState call.
For the components it renders, by default (props shallow comparison) only div3 will re-render (remount), see Reconciliation.
If you switch the divs with custom components, you can memoize them by having a custom props comparison function (check React.memo/React.PureComponent/shouldComponentUpdate).

prevent component from unmounting when displayed in different parts of the DOM

The layout of my app changes depending on some choices made by the user so the same component gets hung under different nodes in the DOM. Unfortunately, React unmounts and re-mounts the component. As a result, my component loses the state is has accumulated. I have tried to add a key property to convey the information that this is the same instance but I got the same results. The below is obviously a SSCCE. Every time the user clicks the button, component A gets unmounted and re-mounted:
class A extends React.Component {
componentWillUnmount = () => {
console.log('map::componentWillUnmount()');
}
componentDidMount = () => {
console.log('map::componentDidMount()');
}
render() {
return (
<div>
this is component A
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state={i:0};
}
increment = () => {
this.setState({i: this.state.i+1});
}
render() {
console.log('app::render');
if (this.state.i % 2 === 0)
return (
<>
<div>
<div>
<A key={42}/>
</div>
</div>
<button onClick={this.increment}>re-render</button>
</>
);
else return (
<>
<div>
<A key={42}/>
</div>
<button onClick={this.increment}>re-render</button>
</>
)
}
}
Just to clarify, my code isn't trying to achieve anything except reproduce the issue, like I said, it's a SSCCE. Surely there are apps where they user can change the layout from a "preferences" menu so that a component ends up in a different place in the DOM depending on the user's preferences. I can't imagine its acceptable to lose the state in such a situation. What is the proper way to deal with this kind of situations?
This is because the conditional rendering is returning two different tree structures where the path to A is different.
React.Fragment > div > div > A
React.Fragment > div > A
Imagine if we're mimicking React by using plain JS to do the mounting and unmounting manually, we will have to:
Store <A/> as a variable
Remove the inner <div/> (<A/> will automatically be removed as well)
Then append the previously stored <A/> into the outer <div/> (which actually is not a good approach either because this assumes <A/> will never need to update itself once it's mounted, even if the props change)
So long as there's a remove and append, it's quite equal to a React component being unmounted and then mounted again.
The code is a bit vague and I cannot tell what it's trying to achieve by having 2 <div/>s in the first condition and only 1 <div/> in the second. But perhaps you can use ternary operators to conditionally render just what you need and leave <A/> alone (and may be a bit of CSS to make the inner <div/> appear as if it is nested inside another <div/>). This way, <A/> will not unmount when <App/> changes state.
return (
<>
<div>
<A />
{condition ? // where `condition` is a boolean
<div style={{
position: 'absolute'
// and may be other styles to achieve the visual trickery
}}>
{/* your content here */}
</div>
:
null
}
</div>
<button onClick={this.increment}>re-render</button>
</>
)
By the way, they say 42 is the answer to everything, but I think not for this case. Setting the key to 42 won't help. 😅

React: on hover over component 1, change the style of another component

I am learning react and I have some code which waits for a hover over a button and then applies an opaque white overlay over the image over which you hover (and this works):
class Product extends Component {
constructor(props) {
super(props);
// 1. bind your functions in the constructor.
this.mouseOver = this.mouseOver.bind(this);
this.mouseOut = this.mouseOut.bind(this);
this.state = {
hover: false
};
}
// 2. bind it with fat arrows.
mouseOver = () => {
this.setState({hover: true});
}
mouseOut() {
this.setState({hover: false});
}
render() {
return (
<Link to={"/products/"+this.props.value.uid}>
<button className="Product" onMouseEnter={this.mouseOver.bind(this)} onMouseLeave={this.mouseOut.bind(this)}>
<img className="ImageGrid" src={this.props.value.media}/>
{this.state.hover ? (
<div className="ImageOverlay">
<div className="TextOverlay">
<p><b>{this.props.value.name}</b></p>
<p>${this.props.value.price}</p>
</div>
</div>) : null}
</button>
</Link>
);
}
}
My question is... let's say that instead of adding an overlay over the image rendered by this component, I wanted to change an image rendered by another component and not by applying an overlay div, but by changing some CSS setting of said image, like applying a filter: filter: grayscale(100%). So there is this image:
<img className="PicBox" src={this.state.img[sid-1]} />
Rendered by another component.
Here is what I am thinking the strategy might be:
Have my original component (the one over which I hover), have a prop "state" which keeps track on whether or not i hover over it, like I did above.
In the other component which renders ImageX, I need to somehow access the prop of the Hover component, and check its state, to decide how to render the image (with grayscale or not).
How do I access state of the hover component within another component?
(Or if my strategy is off, a hint would be appreciated)
As long as you don't use some state managing library such as redux or flux and you want to access state between components, you need a common parent between those.
In the end you have something like this (pseudocode):
ParentComponent {
hoverHandler(isHover) {
this.childIsHover = isHover;
}
render() {
<hoverComponent onHover={this.hoverHandler} />
<imageComponent overlay={this.childIsHover} />
}
}
When working with React, you've got to think about who should have the
responsibility of maintaining state. So in this case, the state should not be
stored in the button component, because it needs to be accessed by another
component which is not one of its children.
Instead, you should create a parent component that is responsible for storing
the hover state, and also renders both the button and image components. You can
bind functions to the parent, so that when they are passed as props to other
children, they can still update the state of the parent.
So for example, your parent component might look like this:
class Parent extends Component {
constructor () {
super()
this.state = {
hover: false
}
this.updateHoverState = this.updateHoverState.bind(this)
}
updateHoverState (hover) {
this.setState({ hover: hover })
}
render () {
<div>
<ButtonComponent updateHoverState={this.updateHoverState} />
<ImageComponent hover={this.state.hover} />
</div>
}
}
Now, your button component can just be a function, and does not need to maintain
any state of its own. You can update the parent state by calling
this.props.updateHoverState:
function ButtonComponent (props) {
return (
<button
onMouseEnter={() => this.props.updateHoverState(true)}
onMouseLeave={() => this.props.updateHoverState(false)}
/>
)
}
In React, you generally pass properties down to children components. So rather than trying to "reach up and over" into an unrelated component to access it's state, you should be accessing a state that's been passed down to your from a shared parent component.
So if the ImageX component is a child of Product, you can pass the state directly into it <ImageX hover={this.state.hover} />. You can then access hover in the ImageX props.
If it's not a child of Product, you'll want to pass down the state from a shared parent and access it from both components.
You can use Context and make a theme-like color set for your components.
Read about the context in the docs and codeburst.

How do I test a React component that calls a function on its parent, which changes props on the component?

The problem:
component Child's props are passed down as the values of Parent's state.
Child has a method that calls a method on Parent, which updates the state of Parent.
When Parent's state updates, one of Child's prop values change. as in: <Child prop1={this.state.prop1}>
What is the right way to go about testing that this process is happening as expected?
Here's some example code to make the problem clearer:
//App.js
import React, { Component } from 'react';
import Content from './Content';
class App extends Component {
constructor(props){
super(props)
this.state = {
page: 'home',
}
}
gotoAbout(){
this.setState({
page: 'about',
})
}
render() {
return(
<Content page={this.state.page} gotoAbout={this.gotoAbout.bind(this)} />
)
}
}
As you can see, the parent component App passes a prop, and a function that can change the value of that prop to its child component, Content.
The Content component would then do something like this:
//Content.js
import React, { Component } from 'react';
class Content extends Component {
constructor(props){
super(props)
}
gotoAbout() {
this.props.gotoAbout()
}
render(){
if(this.props.page = 'home'){
return(
<div>
<p>this is the home content</p>
<button onClick={this.gotoAbout}></button>
</div>
)
} else {
return(
<p>this is the about content</p>
)
}
}
}
The above is a simplified example, but I think it gets the point across. What would be the best way to write a test for this kind of component-prop flow?
I generally first start testing the components in isolation using shallow rendering with its expected functionality and then test components with composition.
e.g To test Content component
1.test whether it behaves correctly for props or state changes
2.test if it performs event's correctly such as button click or any other events through simulation
const wrapper
= shallow(
<Content
page={"home"}
gotoAbout={()=>{ console.log("fake goAbout")}}
/>
);
Now Check if rendered structure is matched as expected for prop page={"home"}
expect(wrapper.containsMatchingElement(
<div>
<p>this is the home content</p>
<button onClick={this.gotoAbout}></button>
</div>
)).to.equal(true);
Similarly test for other prop page={"about"} whether content renders correctly or not.
wrapper.setProps({ page: 'about' });
expect(wrapper.containsMatchingElement(
<p>this is the about content</p>
)).to.equal(true);
After that you can test for button click events
clickCount = 0;
const wrapper
= shallow(
<Content
page={"home"}
gotoAbout={()=>{ clickCount = clickCount + 1;}}
/>
);
Now you can check whether clickCount is greater than zero, after simulating the click event.
wrapper.find('button').simulate('click');
After that you may start testing the App component.
const wrapper = shallow(<App />);
const childWrapper = wrapper.find('Content');
After that you may create another Content component separately via shallow rendering and match those two for equal html structure, props, states etc.
const twrapper
= shallow(
<Content
page={"home"}
gotoAbout={()=>{ console.log("fake goAbout")}}
/>
);
expect(twrapper.html()).to.equal(childWrapper.html());
You can also check whether prop is passed correctly to rendered child elements -
expect(childWrapper.prop('page')).to.equal("home");
There may other better way available as well for testing the react components and these are just simple test examples.
Enzyme provides lots of ways to test your components and there is no hard and fast rule I guess. But you should at least test the expected functionality and features of your component.
Also your test cases make sure that any new changes being made to the component don't break your test specification.

Categories