Higher order component always rerenders ignoring shouldComponentUpdate - reactjs

I have a higher order component like this
// higherOrderComponent.js
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false
}
render () {
return <Component {...this.props} />
}
}
export default HigherOrderComponent
// myComponent.js
import HigherOrderComponent from './higherOrderComponent'
class MyComponent extends React.Component {
render () {
return <div>my component</div>
}
}
export default HigherOrderComponent(MyComponent)
// parentComponent.js
import MyComponent from './myComponent'
class ParentComponent extends React.Component {
render () {
return <MyComponent />
}
}
I explicitly return false but the component always get re-rendered. Any idea why? I ultimately want to share the "shouldComponentUpdate" across components. How can I achieve that if higher order component does not work?

since you have not specified how you are invoking your Higher Order component, based on the issue I have made a guess how you might be using it.
My Answer is based on the assumption that you are invoking your higher order function like
var MyHigherOrderFn = (HigherOrderComponent(Baar));
If Some you how you can invoke your higher order function like below into return in render, you can avoid the issue.
<HigherOrderComponent prop1="Hello" child="Child" />
Since I don;t know how invoke your function in above way(I am not sure its even possible), I have created HigherOrderComponent2 with different syntax style which can be invoked like, which in turn comply with shouldComponentUpdate
<Parent prop1="val1">
<Child>
</Parent>
import React, {PropTypes} from 'react';
/*This is simeple child component*/
class Baar extends React.Component {
render() {
return (<div>{this.props.name}</div>);
}
}
/*This is your higher order component*/
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render () {
return <Component {...this.props} />
}
}
/*This is another way to write higher order component*/
class HigherOrderComponent2 extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render(){
let child = this.props.children && React.cloneElement(this.props.children,
{...this.props}
);
return <div>{child}</div>
}
}
/*Problem that you are facing how you invoke your Higher Order Compoent*/
export default class Foo extends React.Component {
constructor(props) {
super(props);
this.onHandleClick = this.onHandleClick.bind(this);
this.state={
name: 'Praveen Prasad'
}
}
onHandleClick(){
this.setState({
name:Math.random()
});
}
render() {
{'This is how you might be invoking you higher order component, at this time react render doesnt know it already exists in DOM or not'}
{'this component will always re-render, irrespective of values in shouldComponentUpdate'}
var Baaz = (HigherOrderComponent(Baar));
return (<div>
<button onClick={this.onHandleClick}>Update Name</button>
<Baaz name={this.state.name} />
{'This is another way to invoke higher order Component , and this will respect shouldComponentUpdate'}
<HigherOrderComponent2 name={this.state.name}>
<Baar />
</HigherOrderComponent2>
</div>);
}
}

I have modified your code to create a snippet and it works as intended, MyComponent.render is called only once when shouldComponentUpdate returns false.
My guess is that somehow you are using the unwrapped version of MyComponent instead of the wrapped one. Maybe a problem with your build environment?
// higherOrderComponent.js
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render () {
return <Component {...this.props} />
}
}
class MyComponent extends React.Component {
render () {
console.log('render');
return <div>my component</div>
}
}
const MyComponentHOC = HigherOrderComponent(MyComponent);
class ParentComponent extends React.Component {
render () {
return <MyComponentHOC />
}
}
ReactDOM.render(<ParentComponent/>, document.getElementById('container'));
ReactDOM.render(<ParentComponent/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

It is about the life cycle for a react component. When a component get initialized, it will not check shouldComponentUpdate. ShouldComponentUpdate will only be called when state/props CHANGED.
FYI the lifecycle methods call in order:
When a component is initialized:
getDefaultProps
getInitialStage
componentWillMount
render
componentDidMount
When a component has state changed:
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
When a component has props changed:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
When a component is unmounting:
componentWillUnmount

You would need to use a different type of HOC pattern called inheritance inversion to access the lifecycle methods. Since you are overriding shouldComponentUpdate you don't call super however it is required to call super.render() inside the subclassed components render method.
Try this...
const HigherOrderComponent = () => WrappedComponent =>
class ShouldNotUpdate extends WrappedComponent {
shouldComponentUpdate(nextProps) {
return false
}
render() {
return super.render()
}
}
it's good practice to use currying so as you could annotate your classes in the future like...
#HigherOrderComponent
class MyClass extends React.Component {
render() {
return <div>something</div>
}
}
// or without annotations..
const MyNewClass = HigherOrderComponent()(MyClass)

Related

Call child function in parent with i18next react

I used React.createRef() to call child method, like that
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
Child class like that
export default class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
return <h1>Hello</h1>;
}
}
It works well. But when I want to use i18next to translate child component, I have to add withTranslation() to use HOC.
import { useTranslation } from 'react-i18next';
class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);
Then return error: Function components cannot be given refs.
Means cannot use ref in <Child /> tag. Is there any way to call child function after add i18next?
This is a problem since the withTranslation HOC is using a function component. By wrapping your Child component with a HOC you essentially are placing the ref on the withTranslation component (by default).
There are multiple ways to fix this problem, here are the two easiest:
Using withRef: true >= v10.6.0
React-i18n has a built in option to forward the ref to your own component. You can enable this by using the withRef: true option in the HOC definition:
export default withTranslation({ withRef: true })(Child);
Proxy the ref using a named prop
Instead of using <Child ref={this.child} />, choose a different prop to "forward" the ref to the correct component. One problem though, you want the ref to hold the component instance, so you will need to assign the ref manually in the lifecycle methods.
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child innerRef={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
import { useTranslation } from 'react-i18next';
class Child extends Component {
componentDidMount() {
this.props.innerRef.current = this;
}
componentWillUnmount() {
this.props.innerRef.current = null;
}
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);

React componentDidUpdate() does not fire

I have an react app with primereact installed and I am using primereact/captcha.
Maybe I have misunderstood something, but isn't the following code supposed to work (console.log('Child component did update'))?
import React from 'react';
import { Captcha } from 'primereact/captcha';
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
class ParentComponent extends React.Component {
constructor() {
super();
this.state = {
captchaSovled: false,
key : Math.random()
}
}
render() {
let output;
if (this.state.captchaSolved) {
output = <Child key={this.state.key} />;
} else {
output =<Captcha siteKey="xxxxxxx" onResponse={() => this.setState({ key : Math.random(), captchaSolved: true })} />
}
return (
<div>
<h1>Parent component</h1>
{output}
</div>
);
}
}
From React doc
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
In your code, the Child component is mounted after captchaSolved state is set, therefore only componentDidMount is fired on Child component.
componentDidUpdate is fired, if there is any change in the state or props. As of your component child:
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
There is no state or props which are changing, that's why componentDidUpdate never get's invoked.

Exporting in React higher order components

function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
I have taken these pieces of code from the react documentation, however i am confused about what actually happens during
export default logProps(FancyButton);
My thoughts are that it probably calls the function logProps which in React is considered a higher order component which in that case it should being with capital letter to avoid ambiguity.
The logProp function defines a class component LogProps, the class component LogProps renders an argument component FancyButton. The class component LogProps is then returned from the function.
import FancyButton from './FancyButton';
const ref = React.createRef();
// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
The returnedcomponent(LogProps, FancyButton) from the function logProps is then imported and instantiated at
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>
Is this correct?
Is this correct?
Almost.
As stated in docs, Higher Order Components:
is a function that takes a component and returns a new component.
HOCs name don't start with capital letter.
There's no need to name the returned component.
To simplify, the HOC function basically just returns a new and enhanced component. That's it.
This will also work:
// camel case
function logProps(WrappedComponent) {
// no name
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} {...this.someEnhancements} />;
}
}
}

Pass dynamic value to HOC in react

I write some HOC and I need to pass to this HOC a dynamic object that I create on some life cycle level and I did not get him as a prop.
If I try to pass some static value ( for example initialize myObj from start) it works as expected and I get the correct value.
Let's say this is my component class :
let myObj = {};
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'};
return ( ... )
}
}
export default withHOC(Test, myObj);
And this is my HOC:
const withHOC = (Component, test) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = test; // ---->test is empty object always !!
...
}
}
return Hoc;
}
My 'Dynamic' object that I create on my 'test' class is always empty on my HOC class.
It's happend also when I try to pass some value from my props directly, in this case the page is stuck(without errors in console).
Does someone have any idea how to resolve that? Thanks!
When you compose a component that way, composition only happens at compile time (static composition). This means that withHOC runs only once and is receiving an empty myObj argument, as it is using the one defined on declaration.
export default withHOC(Test, myObj); //myObj = {}
If you want that value to be dynamic, the withHOC composition should be runned when that value changes.
You can't send data up from the WrappedComponent (Test) to the HOC (withHOC), so even if you change myObj value in Test.render, the HOC would never know.
What you could do, if you really need it, is do the composition on the Test.render
render(){
const Hoc = withHOC(this.state.myObj, WrappedComponent);//WrappedComponent can be anything
return(
<Hoc/>
)
}
This way, every time the component renders, Hoc is composed using as myObj a value from the component state, wich is not the preferable way to do it, because this.state.myObj might have the same value as it did at the previous render, and you would be re-composing with the same data as before.
A better way to do it is checking for changes in myObj at Test.componentDidUpdate, and if it did change, then compose Hoc again.
You are passing an empty object to the withHOC function
let myObj = {}; // <- your myObj is empty
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'}; // <- You're doing this in the render method of your Test component, so until the component is rendered myObj is empty
return ( ... )
}
}
export default withHOC(Test, myObj);
Some explanation about what's happening here, by order:
import Comp from '.../Test.js'
the withHOC function is triggered, with the params of Test (which is defined above the call) and myObj (which is defined above the call but is empty)
Test component is returned, and nobody used the logic of myObj = {test:'test'}
Suggested solution:
Make the HOC get the logic from the props with another hoc:
const withProps = newProps => BaseComponent => props => {
const propsToAdd = typeof newProps === 'function' ? newProps(props) : newProps
return <BaseComponent {...props} {...propsToAdd} />
}
Usage:
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
return ( ... )
}
}
export default withProps({test:'test'})(withHOC(Test));
// or: export default withProps(props => {test:'test'})(withHOC(Test));
const withHOC = (Component) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = this.props.test;
...
}
}
return Hoc;
}
you can use recompose, a library which has many hocs and utils, and for better readability:
import { compose, withProps } from "recompose"
class Test extends React.Component {...}
const enhance = compose(
withProps({test:'test'}),
withHOC
)
export default enhance(Test);
I can't say with confidence this is optimal but I solved a similar problem by having a function within the HOC that updates state that you can then invoke with any data in the wrapped component.
HOC:
func = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
return ({ <WrappedComponent func={this.func} /> })
Wrapped Component:
this.props.func(anythingA, anythingB);
You can then access the data through state in the HOC.
To elaborate:
const withHOC = (WrappedComponent) => {
class withHOC extends React.Component {
constructor(props) {
super(props)
this.state = {
stateA: 1,
stateB: 2
}
*use state however you want in this HOC, including pass it through to another component*
*the following is just a function*
*when it's invoked in the wrapped component state will update here in the
HOC*
changeState = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
render() {
return (
<div>
<p>this.state.stateA</p>
<p>this.state.stateB</p>
<WrappedComponent changeState={this.changeState} />
</div>
)
}
}
}
}
In wrappedComponent, after importing:
class aComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
*you can now invoke the function from the HOC in this wrapped component*
}
}
You can use react-redux and store your object in redux state. Change the object wherever you need (in your case it's in Test) and access it in component inside your HOC from redux state, it'll be always up to date.

Is it possible to get ref of props.children?

I want to get ref of Child component. What is the best way to do this?
class Child extends React.Component {
render() {
return <div>Child</div>;
}
}
class GetRef extends React.Component {
componentDidMount() {
console.log(this.props.children.ref)
}
render() {
return this.props.children
}
}
Edit:
So I can use it like this
<GetRef><Child/></GetRef>
I assumed that GetRef children has only one child, then you can retrieve the ref of the only child component with this code
class Child extends React.Component {
render() {
return <div>Child</div>;
}
}
class GetRef extends React.Component {
componentDidMount() {
console.log(this.ref);
}
render() {
const childElement = React.Children.only(this.props.children);
return React.cloneElement(
childElement,
{ ref: el => this.ref = el }
);
}
}
class App extends Component {
render() {
return <GetRef><Child/></GetRef>;
}
}
Here is the complete example on stackblitz
If this.props.children has more than one child, you will need to iterate over the children and store all the refs into an array
This is where forwardRef can be used:
class GetRef extends React.Component {
render() {
console.log(this.props.forwardRef)
}
}
const ref = React.createRef();
<Child forwardRef={ref} />
Alternatively, you may also use:
<Child ref="childRef" .../>
// In the parent component
React.findDOMNode(this.refs.childRef)
But take a look at Exposing DOM Refs to Parent Components to know whether to use ref or not:
In rare cases, you might want to have access to a child’s DOM node from a parent component. This is generally not recommended because it breaks component encapsulation, but it can occasionally be useful for triggering focus or measuring the size or position of a child DOM node.

Resources