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
Related
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.
I have multiple component with similar piece code in lifecycle methods and some similarity in state variables. Is there a way to unify them, by inheriting from one parent or something like that?
constructor(props) {
super(props);
this.state = {
//state properties similar in all components, getting from redux
//state properties specific for this component
}
// same code in many components
}
componentWillMount() {
// same code in many components
// code specific for this component
}
Can I use children methods and props in parent "wrapper" ? Can I change component state from parent ?
You can create Higher Order Component (HOC) for that, basically, you just write component with your same lifecycle method which is repeating, and then in render() function, call this.props.children function with any HOC internal state arguments you want, you can pass the whole state and a setState function as well, so you can change the HOC's state inside the underlying component.
For example:
class HOC extends React.Component {
constructor(props) {
super(props);
state = {
someState: 'foo',
};
}
componentWillMount() {
console.log('i mounted!')
}
render() {
return (
<div>
{this.props.children({ state: this.state, setState: this.setState })}
</div>
)
}
}
const SomeComponent = () =>
<HOC>
{({ state, setState }) => (
<div>
<span>someState value: </span>
<input
value={state.someState}
onChange={e => setState({ someState: e.target.value})}
/>
</div>
)}
</HOC>
You can also do really cool and interesting things with it, like connecting a slice of your redux state whenever you need it:
import { connect } from 'react-redux';
const ProfileState = connect(
state => ({ profile: state.profile }),
null,
)(({
profile,
children
}) => (
<div>
{children({ profile })}
</div>
));
const ProfilePage = () => (
<div>
Your name is:
<ProfileState>
{({ profile }) => (
<span>{profile.name}</span>
)}
</ProfileState>
</div>
);
Here is the full documentation on this technique.
You could create HOCs (Higher Order Components) in that case. It can look like this:
/*
A Higher Order Component is a function,
that takes a Component as Input and returns another Component.
Every Component that gets wrapped by this HOC
will receive `exampleProp`,`handleEvent`,
plus all other props that get passed in.
*/
function WithCommonLogic(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
example: ''
}
}
componentWillMount() {
...
// Same code in many components.
}
callback = () => {
/* Enhanced components can access this callback
via a prop called `handleEvent`
and thereby alter the state of their wrapper. */
this.setState({example: 'some val'})
}
render() {
return <WrappedComponent
exampleProp={this.state.example}
handleEvent={this.callback}
{...this.props}
/>
}
}
// You use it like this:
const EnhancedComponent1 = WithCommonLogic(SomeComponent);
const EnhancedComponent2 = WithCommonLogic(SomeOtherComponent);
Now all the shared logic goes into that HOC, which then wrap all your different components you want to share it with.
See the React Docs for further reading.
In my project, I'm looping through cards in my Child Component, and updating the Parent's state with ones where I activate an event of 'swiping right' on the card to favorite. The parent's state keeps track of all favorites by adding the favorited card to the array.
I'm passing down a function (updatestate)from the Parent Component, App, to the Child, Main, that allows the Child to call .setState() and append to the array in the Parent state.
But, when I activate the eventhandler onSwipedRight inside the Child Component, the parent's state gets updated as planned with the new Card, but nothing below the <Card> in Main gets rendered automatically for the next card, as it should. If I tap the screen, then the next card/picture renders only then.
Any ideas? Am I missing some binding or need to do some componentDidMount or anything so the code in child component, Main, renders even after I activate the event handler that updates the parent state?
Basically, is there a tool in React to make sure something renders or at least waits for it to render? (post event handling which sets the parents state in my case)
collection = imagedata;
//collection is the data (local JSON) i'm looping thru via .map in Main Component
const RootStack = StackNavigator(
{
Main: {
screen: Main}
}
);
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
}
updateArr=(itemname, url)=>{this.setState({ favoritesList: [...this.state.favoritesList, {"item_name":itemname, "url":url}]})};
render() {
return <RootStack screenProps={{appstate: this.state,
updatestate: this.updateArr}}
/>;
}
}
class Main extends React.Component {
render() {
var updatestate = this.props.screenProps.updatestate;
const contents = collection.map((item, index) => {
return (
<Card key={index}
onSwipedRight={() => {updatestate(item.item_name,item.url)}}
>
<View> //THIS and anything after <Card> doesn't render for the next card automatically if I do 'onSwipedRight'
<Image
source={{uri: item.url}} />
</View>
</Card>
)
},this);
return (
<View>
<CardStack>
{contents}
</CardStack>
</View>
);
}
}
(abbreviated) Project structure:
App
|__ Rootstack
|
|__Main
UPDATE (more info):
Just to test the event handler, I added in a function that doesn't set the state of the parent, and had <Card> call that on the event handler -- it works perfectly and the child component <Card> renders perfectly. It seems that it's the updatestate function passed down from the parent to the child that acts to call .setState() upstream that for some reason is causing the Child to not render/not finish rendering after the event handler.
class Main extends React.Component {
render() {
var updatestate = this.props.screenProps.updatestate;
var newfunc = (a, b) => {console.log('a:', a, 'b:', b)};
const contents = collection.map((item, index) => {
return (
<Card key={index}
newfunc(item.item_name,item.item_name);}}
// onSwipedRight={() => {updatestate(item.item_name,item.url); }}
>
If you need to set the state of a component based on the previous state, you should use
this.setState((prevState, props) => {})
Which should, in your case look like
this.setState((prevState, props) => {
return {
favoritesList: [
...prevState.favoritesList,
{"item_name":itemname, "url":url}
]
};
})
For more on setState
We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created
each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
MyComponent
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Sample snippet
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<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="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in
multiple functions being created but is still an improvement over the
initial case.
Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"
How to avoid this way of binding inside render method or what are the
alternatives of this?
If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ... only <App /> is re-rendered.
render()
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
Documentation encourages to use data-attributes and access them from within evt.target.dataset:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
Also note that this makes sense only when you have performance issues:
Is it OK to use arrow functions in render methods?
Generally speaking, yes, it is OK, and it is often the easiest way to
pass parameters to callback functions.
If you do have performance issues, by all means, optimize!
This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.
Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
Proof: https://jsfiddle.net/69z2wepo/92281/.
So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}
I've hit a brick wall here, meaning I can't fully figure out why the next two versions of code behave so differently.
In the first version, when I'm initialising a this.childComponent = (<ChildComp />), its props do not seem to update when I change the Parent's state (via setState()). This happens even though the setState() is actually called, and the Parent's state is updated.
In the second version, when I'm actually initialising a function that returns the component (this.childComponent = () => {return (<ChildComp />)}), everything works like a charm, props are updated.
I am using the second version (since it works), but I'd like to understand why this works and the first one doesn't.
Here's the child component:
class Child extends React.Component {
render() {
return (
<button onClick=(this.props.setValue())>
{this.props.value}
</button>
)
}
}
I have the next two versions of the parent component:
1.
class Parent extends React.Component {
constructor() {
this.state = {
value: 1
}
this.childComponent = (
<Child value={this.state.value}
setValue={() => this.setValue()}/>
)
}
setValue() {
this.setState({value: 2})
}
render () {
return ( {this.childComponent} )
}
}
2. (the this.childComponent is now a function that returns the react Element)
class Parent extends React.Component {
constructor() {
this.state = {
value: 1
}
this.childComponent = () => {
return (
<Child value={this.state.value}
setValue={() => this.setValue()}/>
)
}
}
setValue() {
this.setState({value: 2})
}
render () {
return ( {this.childComponent()} )
}
}
I've tried to simplify the code so my issue is easier to understand.
Thank you in advance
React uses a strategy called reconciliation to efficiently update the DOM every time there's a change in its internal state. Typically, this happens after a setState call.
In your first example, the render method inside the Parent component always returns the same Child component, as it's created only once in the constructor. Because of this, the reconciliation algorithm doesn't find any changes since there aren't any.
I'd like to point out that <Child value={this.state.value} setValue={() => this.setValue()}/> is just syntactic sugar for React.createElement(Child, {value: this.state.value, setValue: () => this.setValue()}, null). createElement simply returns an object.
In your second example, with every render call, you're calling childComponent which in turns create a new Child component.
You don't have a return in the first case since
this.childComponent = (
<Child value={this.state.value}
setValue={() => this.setValue()}/>
)
was defined in the constructor which is executed only once and is a static value now.
whereas it will work as it is a function that is executed everytime it is called.
If you want to go by the first method, define the childcomponent in the render rather than the constructor since render is called on every change. Your code also had a lot of mistakes. See the working snippet below
class Parent extends React.Component {
constructor() {
super();
this.state = {
value: 1
}
}
setValue() {
this.setState({value: 2})
}
render () {
const childComponent = (
<Child value={this.state.value}
setValue={() => this.setValue()}/>
)
return ( <div>{childComponent}</div> )
}
}
class Child extends React.Component {
render() {
return (
<button onClick={this.props.setValue}>
{this.props.value}
</button>
)
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'))
<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="app"></div>