Why ref object existed before assign it to target in Reactjs - javascript

I tried Ref and forwarding-refs in Reactjs to keep a reference to a DOM or React Component. I did not understand why the ref object in which created kept correct target reference before target component was rendered or mounted.
I have the codesandbox right here to present my question more details. Here is the screenshot
As what you see, Ref object keep a correct reference to the target (in this case is FancyButton Component) even the render and ComponentDidMount method of target have not yet fired.
Could someone possibly help me to understand about this more. Thanks.

Because console keep reference to current value to object (so you get value of object after rendering). If you will change your code to
console.log(JSON.stringif(ref))
Then you will get this:
]1

You are console logging in quite a few wrong places, namely the console.log("render of Fancybutton"); in the render method of FancyButton and in the function body of forwardRef of the FancyHOC. The render method should be a pure function without side-effect. Console logging is considered a side-effect.
Study this react component lifecycle diagram:
Notice where the render function resides. It resides in the "Render Phase" of the render cycle. Notice also that it "May be paused, aborted or restarted by React." This means they are called before anything in the "Commit Phase".
Notice now as well that the other component lifecycle methods, specifically componentDidMount and its functional component coutnerpart useEffect with empty dependency array are all called after the component has rendered (committed to DOM) at least once.
Fix the logging in the incorrect places:
FancyButtonHOC
Move the logs into componentDidUpdate and useEffect hook.
function createFancyButtonHOC(WrappedComponent) {
class FancyHOC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent ref={forwardRef} {...rest} />;
}
componentDidMount() {
console.log("FancyHOC mounted");
}
componentDidUpdate() {
console.log("render of HOC: ", this.props.forwardRef);
}
}
return React.forwardRef((props, ref) => {
React.useEffect(() => {
console.log("forwardRef callback: ", ref);
});
return <FancyHOC forwardRef={ref} {...props} />;
});
}
In FancyButton if you leave the console log in the render method it will simply log any time react is invoking the render method for DOM diffing purposes, not actually when it is rendered to the DOM during the commit phase. Move it to componentDidUpdate.
export default class FancyButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
console.log("fancy button mounted");
}
componentDidUpdate() {
console.log("render of Fancybutton");
}
handleClick() {
console.log("button click");
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
New console log output
render of HOC: {current: FancyButton}
fancy button mounted
FancyHOC mounted
forwardRef callback: {current: FancyButton}

Related

React - setState of child from parent component

I have two components. A main and a child component.
Let's assume a function is triggered in the main component which cause its state to be mutated.
The state of the main component is passed down to the child component as a prop. The newly updated data in the props of the child component should now be used to to set the state of the child component.
I can't do this on ``componentDidUpdate since it would cause an infinite loop.
On the other hand I wouldn't want to lift the child's state to the main component since most code of it would be useless in the main component.
I hope you can help
You can use getDerivedStateFromProps as mentioned in the React docs:
export default class Child extends Component {
static getDerivedStateFromProps(newProps, currentState) {
return {
value : newProps.value
}
}
render() {
return (
<div>
{/* Your layout */}
</div>
);
}
}
componentDidUpdate takes prevProps as argument componentDidUpdate(prevProps, prevState, snapshot). So to not getting the code in infinite loop, you can compare this.props with prevProps and update the state accordingly.
componentDidUpdate(prevProps) {
if(this.props.data !== prevProps.data) {
// update the new state here this will not cause infinite loop
}
}
For a functional component using hooks.
function Child(props) {
const [whatever, setWhatever] = React.useState(props.whatever);
React.useEffect(() => {
setWhatever(props.whatever);
}. [whatever]);
}
export default Child;
Hope it helps.

componentDidUpdate not firing after each render

I'm tracking when componentDidUpdate and render are firing with log statements.
The log statements in componentDidUpdate do not fire after render. I have used breakpoints to confirm this isn't a timing issue.
I'm using "render props" to wrap the component in question. My code (stripped down) is below. This is the output of the logging. Sometimes I'll get componentDidUpdate to fire, but inconsistently and it's never the final thing, a RENDER always shows up in my logs last, never UPDATE.
As I understand it componentDidUpdate should fire even if the update does not modify the DOM (though the renders here do update the DOM.) I've tried React#16.11.x and React#16.12.x with identical results.
class MyWrapper extends React.PureComponent {
render() {
const { buttonDefinitions } = this.props;
return (
<InfoProvider
render={infoProps => {
return (
<MyMenu
{...{ buttonDefinitions, infoProps }}
/>
);
}}
/>
);
}
}
class MyMenu extends React.Component {
componentDidUpdate() {
log.warn('UPDATE');
}
render() {
log.warn('RENDER');
const { buttonDefinitions } = this.props;
return (
<MenuWrapper>
{buttonDefinitions.map(buttonDef => (
<MyButton {...buttonDef} />
))}
</MenuWrapper>
);
}
}
As per react docs, if you are using render props with React pure component, then shallow prop comparison will always return false for new props. In this case, for each render it will generate a new value for the render prop. As new props getting created & not updating previous one it won't call componentDidUpdate.

how children's props get updated?

I'm new to React, still struggling to learn how props get updated and in which lifecycle methods the update takes place. Below is some code I wrote:
export default Parent extends Component {
constructor(props) {
super(props);
this.state = {
name: "Tom"
}
}
changeName = () => {
this.setState({ name: "Jerry" });
}
render() {
return <div>
<Child name={this.state.name}/>
<button onClick={this.changeName }>Change Name</button>
</div>
}
}
export default Child extends Component {
...
shouldComponentUpdate(newProps, newState) {
console.log("old name is" + this.props.name)
console.log("new name is" + newProps.name)
return true;
}
render() {
return <p>{this.props.name}</p>
}
}
When I click the button, the content of paragraph changes from "Tom" to "Jerry", which is expected, but if I dig a little bit, the console showed that:
old name is Tom
new name is Jerry
My question is, after I click the button, inside the Child component, this.props.name is still Tom, which means this.props is not the latest props, while inside the render() function, this.props.name becomes Jerry, which means this.props is the latest prop.
When did the old prop change to the latest prop? Is there another lifecycle method after shouldComponentUpdate() that actually changes this.props to the latest props, for example:
afterShouldComponentUpdate(newProps, newState){
...
this.props = newProps;
}
Does such a lifecycle method exist? I know the name won't match, but is there a lifecycle method that accomplishes the above functionality?
No, there is no such lifecycle method.
Assignment of props to this is happening BETWEEN lifecycle methods (after shouldComponentUpdate, before render) by the library itself and there is no public interface how you can interfere with the process.

How to unit test React functions passed in as prop to child component with Enzyme shallow wrapper

I am very new to front-end dev & I am having some trouble getting my Enzyme unit tests using Shallow. Basically, I have something like
class MyComponent extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
const {
handleClick,
...other
} = this.props;
return (
<div className="someClass">
// a bunch of stuff
<div className="buttonArea">
<MyComponentChild onClick={handleClick} />
</div>
</div>
);
}
MyComponent.propTypes = {
handleClick: PropTypes.func.isRequired,
...other
};
export default MyComponent;
}
handleClick is a callback function defined in the container (i.e ComponentContainer) that MyComponent belongs to. I am passing it as a prop into MyComponent, and subsequently MyComponentChild (which is a button component). I want to test whether handleClick fires when MyComponentChild is clicked.
My current Enzyme test
it('handleClick should fire when clicked', () => {
const mockCallbackFn = jest.fn();
const wrapper = shallow(<MyComponent {handleClick = { mockCallbackFn }} />);
wrapper.find('MyComponentChild').simulate('click');
expect(mockCallbackFn).toHaveBeenCalled();
});
However, this test is currently failing, as mockCallbackFn is apparently never called. But, this is also passing
expect(wrapper.find('MyComponentChild').prop('handleClick')).toEqual(mockCallbackFn);
What am I doing wrong? Any help much appreciated!
simulate(someEventName) does really simple thing: it calls prop with name of "on" + someEventName. So simulate('click') runs .props().onClick().
But your component uses handleClick prop, that's why it does not called by simulate()
wrapper.find('MyComponentChild').props().handleClick();
Name simulate is so confusing that team is going to remove it out(https://github.com/airbnb/enzyme/issues/2173).
Side note: you don't need extra braces when declaring props. I mean {handleClick = { mockCallbackFn }} better be handleClick={mockCallbackFn} since it's typical for React code and looks less confusing.
You need to use mount instead of shallow. shallow only mounts the first level of components, so your ComponentChild is not being mounted and your click handler isn't being passed in.
You can see this yourself by calling debug() on your wrapper and console.log-ing it.

Is ReactJS "clever" when it comes to invoking the render method?

The render method of this component does use any of the props supplied to the component.
Will the component re-render when the props change regardless?
class MyComponent extends React.Component {
constructor(props) {
super(props);
const { propValue } = props;
// do something with propValue...
}
render () {
return (
<div>foo</div>
);
}
}
Will render be called - yes. Unless you implement shouldComponentUpdate to return false.
Will the DOM be rerendered - no.
Also you might want to take a look at https://babeljs.io/docs/plugins/transform-react-constant-elements/ that hoists static elements up.
In
const Hr = () => {
return <hr className="hr" />;
};
Out
const _ref = <hr className="hr" />;
const Hr = () => {
return _ref;
};
Yes, the component will re-render unless you implement shouldComponentUpdate. You can inherit from PureComponent which uses shallow comparison of prop and state with previous values to determine if component should update or not.
As far as i know react will call the render method in the following scenarios
when your component get mounted initially
when state got changed using this.setState()
when your component receives new props
when this.forceUpdate() get called.
since you didn't implement shouldcomponentUpdate() the render method is going to get called

Categories