Created
April 18, 2020 22:29
-
-
Save Kingdutch/db2cf90050aa13271fc968d8e87ad259 to your computer and use it in GitHub Desktop.
A dive into react-sizeme and what may be the cause of the render issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Relevant render code from the SizeMe component in react-sizeme | |
| render() { | |
| const { SizeAware } = this | |
| const render = this.props.children || this.props.render | |
| return ( | |
| <SizeAware onSize={this.onSize}> | |
| // ! Here it calls the function that you provide. React doesn't know anything about this, | |
| // it only know about the output. | |
| {render({ size: this.state.size })} | |
| </SizeAware> | |
| ) | |
| } | |
| // This is the program that's working. | |
| import * as ReactSizeme from "react-sizeme"; | |
| function MyComponent() { | |
| return ( | |
| <ReactSizeme.SizeMe> | |
| {({ size }) => { | |
| return React.createElement("div", null, "Width ", size.width); | |
| }} | |
| </ReactSizeme.SizeMe> | |
| ); | |
| } | |
| // Lets look at what happens on initial render. | |
| // If we step through this then we can render the ReactSizeme.SizeMe | |
| // component as follows: (this is illustrative, not actual working code) | |
| ReactSizeMe.SizeMe.render({ children }) { | |
| // children now holds our ({ size }) => { return ... } function. | |
| // Cleverly try to assign this.SizeAware to SizeAware. | |
| const { SizeAware } = this | |
| // Allow callling our code with either a prop named 'render' or | |
| // with a function as a child (this is the method used in the example) | |
| // We can now always call it as render() | |
| const render = this.props.children || this.props.render | |
| // The SizeAware function would now be rendered by React roughly as follows: | |
| SizeAware.render({ | |
| onSize: this.onSize, | |
| children: render({size: this.state.size }), | |
| }) | |
| // As you can see here, the argument passed to `children` is the result of the | |
| // call to `render()`. React only knows about the result, not its existence. | |
| // In the working example this translates to: | |
| SizeAware.render({ | |
| onSize: this.onSize, | |
| children: React.createElement("div", null, "Width ", size.width), | |
| }) | |
| // This is the program that's broken. | |
| import * as ReactSizeme from "react-sizeme"; | |
| function MyComponent() { | |
| return ( | |
| <ReactSizeme.SizeMe> | |
| {({ size }) => { | |
| return React.createElement("div", null, "Width " + size.width); | |
| }} | |
| </ReactSizeme.SizeMe> | |
| ); | |
| } | |
| // Taking a bit of a shortcut | |
| // Its render basically translates to | |
| SizeAware.render({ | |
| onSize: this.onSize, | |
| children: React.createElement("div", null, "Width " + size.width), | |
| }) | |
| // However, this still means the `size` is in the tree and changing it should | |
| // change the children. So I can't imagine that this gets optimised away. | |
| // The only reason I can imagine that React is optimizing away a re-render is | |
| // because the props to a tree are the same. This doesn't appear the to be the | |
| // case for `size.width`. | |
| // However, react-sizeme does seem to be some questionable | |
| // things in how it's passing around state and updating state. It's possible that | |
| // it accidentally passes a 'reference' somewhere and causes both the reference for | |
| // this.size to be updated as well as the value that React has stored to use in determining | |
| // whether the props to a part of a component have changed. | |
| // In combination with the above, the difference could be possibly be that in the working | |
| // variant of the code the separate `size.width` causes an extra value stored in the tree | |
| // that will be found to be different causing a re-render. The broken code only stores a | |
| // string, that requires a function call to change, which doesn't happen because the props | |
| // to the component that call that function aren't changed, so the function isn't called. | |
| // A demonstration of how this assignment and comparison can go wrong unexpectedly and why | |
| // all React tutorials focus on immutable state using the spread operator: | |
| // `{ ...preState, newValue: 2 }` | |
| const mutable = { | |
| width: 2 | |
| }; | |
| const other = mutable; | |
| const actualValue = mutable.width; | |
| // Lets change the original object and see what | |
| // has and hasn't changed. | |
| mutable.width = 5; | |
| console.log("other.width === 2", other.width === 2); | |
| console.log("other.width === mutable.width", other.width === mutable.width); | |
| // As you can see above, the value in `other` changed | |
| // with the change in mutable. | |
| console.log("actualValue === 2", actualValue === 2); | |
| console.log("actualValue ==== mutable.width", actualValue === mutable.width); | |
| // In the above two lines you see that the scalar value we assigned | |
| // has actually not changed as we expected. | |
| // Some background on how JavaScript values are passed around that details the above: | |
| // https://stackoverflow.com/questions/9437981/why-isnt-this-object-being-passed-by-reference-when-assigning-something-else-to/9438044#9438044 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment