Skip to content

Instantly share code, notes, and snippets.

@Kingdutch
Created April 18, 2020 22:29
Show Gist options
  • Select an option

  • Save Kingdutch/db2cf90050aa13271fc968d8e87ad259 to your computer and use it in GitHub Desktop.

Select an option

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
// 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