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.
Related
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);
A parent component - App is wrapped with a Context.Provider - that passes down the App's State.
A child Counter component is rendered via a HOC, its argument is a State variable passed from the App component.
A click on the child component - changes that state variable, via the Context API;
The child component does in-fact rerenders -
but, componentDidUpdate is not triggered
Why?
I suspect it's related to React's shallow comparison...
How can I make componentDidUpdate trigger - while still using this pattern?
Code example (also on Codepen: https://codepen.io/yuval_a/pen/YzPgBQV)
const StateContext = React.createContext();
const StateProvider = StateContext.Provider;
const renderChild = num=> {
class Counter extends React.Component {
static contextType = StateContext;
constructor(props, context) {
super(props, context);
}
componentDidUpdate(prevProps) {
console.log ("Component updated");
}
click = ()=> {
this.context.setAppState({ counter: this.context.appState.counter+1 });
}
render() {
return <div onClick={this.click}>{ this.props.counter }</div>
}
}
return <Counter counter={num} />
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
render() {
return (
<StateProvider value={{
appState: this.state,
setAppState: newState=>{ this.setState(newState) }
}}>
{ renderChild(this.state.counter) }
</StateProvider>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
The problem is that renderChild is invoked on every render.
Therefore, Counter is actually unmounted on every render instead of getting updated as you expect, so the DidUpdate life cycle never kicks in.
const renderChild = num => {
class Counter extends React.Component {
// Called on every render
componentDidMount() {
console.log('mounted on every render');
}
...
render() {
return (...);
}
}
return <Counter counter={num} />;
};
To fix it, you need to mount Counter once in his parent component.
I have a React component that I exported it like this:
class Child extends Component {
getStatus() {
return 'I am child!';
}
render() {
return (<div>Child</div>);
}
}
export default withRouter(Child)
And I have another class that needs the ref of "Child" component like this:
class Parent extends Component {
constructor(props) {
super(props);
this.childRef = createRef();
}
handleLogChildStatus = () => {
if (typeof this.childRef.current.getStatus === 'function') {
console.log(this.childRef.current.getStatus());
} else {
console.log(' Can not access to child component via ref! ');
}
}
render() {
return (
<div>
<Child ref={this.childRef} />
<div onClick={this.handleLogChildStatus}>Log child status</div>
</div>
);
}
}
This sample shows me I can not access to child component ref because it wrapped by withRouter HOC.
My question is How I can access to child ref component when it wrapped by nextjs withRouter>
I think one of below should work
export default withRouter(Child, { withRef: true })
2nd option
this.childComponent = React.createRef();
....
<Child wrappedComponentRef={c => (this.childComponent = c)} />
I have this piece of code (which I've simplified for posting here) that creates a component and renders it
const getComponentToRender = (user, path) => {
switch(path ){
case 'ChangePassword':
return <ChangePassword user={ user } />;
case 'NewPassword':
return <NewPassword user={ user } />;
case 'PasswordExpire':
return <PasswordExpire user={ user } />;
default:
return null;
}
}
class UserAdmin extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired
};
render() {
const component = getComponentToRender(this.props.user, 'ChangePassword' );
return(
<div id='user-admin-wrapper'>
{component}
</div>
)
}
componentWillUnmount(){
}
}
When I navigate away from UserAdmin the componentWillUnmount gets called.
Q: What is the simplest way to actually remove the component ChangePassword or any other component (by its name) from the DOM when componentWillUnmount executes.
OR, removing the component at any point, without waiting for componentWillUnmount
Using react-dom 15.6.1 . btw
Un-mounting a component will un-mount(remove) all the child components it contains. So after componentWillUnmount the component you rendered inside it will be removed.
If you need to control over components that rendered without un-mounting you use conditional render logic.
Example
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
shouldIRender: true
};
}
componentDidMount() {
setTimeout(() => {
this.setState({shouldIRender: false});
}, 5000);
}
render() {
return(
<div>
<ComponentThatAlwaysHere />
{ this.state.shouldIRender === true ? <ComponentThatRemovesAfterStateChange /> : null }
{ this.state.shouldIRender === true && <AnotherComponentThatRemovesAfterStateChange /> }
</div>
)
}
}
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)