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.
Related
In the following sample code, unfinishedTodoCount (under LoadContent component) is not updated when todo items checked.
I dereference unfinishedTodoCount in the render method of TodoListView so i think it must be tracked by mobx.
(I use "trigger rendering" button to force render() to update unfinishedTodoCount value.)
Question: So, why does not mobx trigger render() when unfinishedTodoCount changes?
Consideration: I am wondering if props.children() is running asynchronously so mobx cannot catch dereferencing.
(Solutions: Various solutions could be applied by uncommenting lines in the code.)
// Uncomment following so render() will be called when unfinishedTodoCount changes.
//#observer
class LoadContent extends React.Component {
render() {
console.log("rendering LoadContent");
return (
<div>
{this.props.children({
// ...this.props,
})}
</div>
);
}
}
#observer
class TodoListView extends React.Component {
constructor(props) {
super(props);
this.state = {
triggerRender: false
};
}
render() {
console.log("rendering TodoListView");
// Uncomment following so render() will be called when unfinishedTodoCount changes.
//let todoCount = this.props.todoList.unfinishedTodoCount;
//console.log(todoCount);
return (
<div>
<input
type="Button"
onClick={() =>
this.setState({ triggerRender: !this.state.triggerRender })
}
value="Trigger rendering"
/>
<ul>
{this.props.todoList.todos.map((todo) => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
<div>
{/* Uncomment following so render() will be called when unfinishedTodoCount changes. */
/* {(() => (
<div>Tasks left: {this.props.todoList.unfinishedTodoCount}</div>
))()} */}
<LoadContent>
{() => (
<div>Tasks left: {this.props.todoList.unfinishedTodoCount}</div>
)}
</LoadContent>
</div>
</div>
);
}
}
Complete source code here;
https://codesandbox.io/s/simple-mobx-todolist-forked-hep3t?file=/index.js
I think components render() is being called asynchronously (yet components are rendered in sync. as expected) per my proof of concept below so mobx can not track dereferencing in component.
Proof of Concept: Components are called async.
I added a console.log() call after LoadContent and its called before the console.log() in LoadContent.
<LoadContent>
{() => (
<div>Tasks left: {this.props.todoList.unfinishedTodoCount}</div>
)}
</LoadContent>
{(()=>{console.log("after load content")})()}
rendering TodoListView
*after load content*
rendering LoadContent
(Complete source code here; https://codesandbox.io/s/simple-mobx-todolist-forked-hep3t?file=/index.js)
SOLUTION: We could use #observer decorator for child components or access those observable variables earlier in render() of parent components.
Actually, caveat of passing renderable callbacks to components is also stated in following documentation;
The notable caveat here is passing renderable callbacks to React
components, take for example the following example:
const MyComponent = observer(({ message }) =>
<SomeContainer
title = {() => {message.title}}
/> )
message.title = "Bar" At first glance everything might seem ok here,
except that the is actually not rendered by MyComponent (which
has a tracked rendering), but by SomeContainer. So to make sure that
the title of SomeContainer correctly reacts to a new message.title,
SomeContainer should be an observer as well. If SomeContainer comes
from an external lib, you can also fix this by wrapping the div in its
own stateless observer based component, and instantiating that one in
the callback:
const MyComponent = observer(({ message }) =>
<SomeContainer
title = {() => }
/> )
const TitleRenderer = observer(({ message }) =>
{message.title}} )
message.title = "Bar"
https://doc.ebichu.cc/mobx/best/react.html
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}
I am writing a higher-order component that takes children and then re-renders them based on the state of a context provider.
Consider the following simplified example:
index.js
const ChildElem = () => {
return(
<div/>
)
}
class Example extends React.Component{
render(){
return(
<FocusProvider>
<ChildElem/>
<ChildElem/>
<ChildElem/>
</FocusProvider>
)
}
}
FocusProvider.js
class FocusProvider extends React.Component{
renderChildren = (providerState) => {
//Does nothing with state and simply returns children yet they still re render
return this.props.children
}
render(){
return(
<Provider>
<Subscribe to={[ContextProvider]}>
{provider => this.renderChildren(provider.state)}
</Subscribe>
</Provider>
)
}
}
As you can see from the example the children of FocusProvider are being returned from a function that subscribes to a context.
The problem I am running into is that the children are being re-rendered even though nothing is being changed on them. The only thing that is being changed is the state of the context provider they are subscribed to.
Any advice would be greatly appreciated
You can control whether the component should update or not there is a function of react component class
shouldComponentUpdate ( nextProps, nextState, nextContext ) {
/* compare nextState with your current states properties if you want to update on any basis return true if you want to render the component again */
return true; // will re-render component ,
return false; // do not re-render component even if you change component states properites
}
nextProps contains that prop the new props , and nextState contain new State properties
So I've got two react components, and for some reason one of them is running again (and causing a nasty bug) when I click another to go to another component.
My guess is that this is because I am running some asynchronous code for geolocation in my component constructor, but I don't know enough about React to be 100% certain of this.
The showPosition method makes an API call based on a user's location and other variables.
class Cards extends Component {
constructor() {
super();
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
this.showPosition = this.showPosition.bind(this);
}
...
render() {
if (this.state.isLoading) {
return <Loading />;
}
// The lines below this ALWAYS get run, even several seconds after I
// have been on my new component!
console.log("state value new");
console.log(this.state.data);
return (
<Page className="main-page">
<div className="cards">
{this.state.data.merchants.map( (merchant, index) =>
<CardRow
merchant={{merchant}}
count={index}
className={'card-color-' + index}
/>
)}
</div>
</Page>
);
}
}
This cards component creates a child component called CardRow, and then that component creates several Card and PromoCard component children.
I won't link the full card, but the way I am accessing the component that breaks is this way - a user clicks the link, and is directed to the chat component:
<Link to={{
pathname: "/chat/" + this.state.merchant.id,
state: {merchant: this.state.merchant}
}}>
I made a toy component for chat, and everything loads fine, but then the render function in <Cards /> runs again, which messes up my entire chat interface.
Why is this happening? Is it related to my geolocation code in my constructor? Something else potentially?
You have a memory leak, it is not a good practice to set listeners in your constructor.
You must use lifecycle methods (componentDidMount, componentWillUnmount, etc...)
the componentDidMount lifecycle method is the right place to set a listener
constructor() {
this.showPosition = this.showPosition.bind(this);
this.unmounted = false;
}
componentDidMount() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
}
componentWillUnmount() {
// indicate that the component has been unmounted
this.unmounted = true;
}
showPosition() {
// exiting early if the component was unmounted
// prevent any update using setState
if (this.unmounted) { return; }
// more code...
}
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