purescript-react / purescript-react-basic-hooks

An implementation of React hooks on top of purescript-react-basic
https://pursuit.purescript.org/packages/purescript-react-basic-hooks/
Apache License 2.0
198 stars 31 forks source link

useMemo documentation #52

Closed jamesdbrock closed 2 years ago

jamesdbrock commented 2 years ago

Is what I wrote true? If so then I think this is the best technique for preventing Hook components from re-rendering and we should explain that.

megamaddu commented 2 years ago

Use this to prevent a Hook component from re-rendering needlessly by memoizing the props for the Hook component.

If a component is re-rendered with new props, it's going to re-render. useMemo won't stop that. You can use it to prevent other kinds of re-renders, though. For example, if you know a child component re-renders every time you pass it a new reference, even when the value is equal, you can use useMemo to preserve the initial, equivalent reference for passing down to the child.

React will skip re-rendering the component only if the new props is equal-by-reference to the old props (or primitive and equal-by-value).

This is how React works in JavaScript, but it's not how this PureScript wrapper works. Notice the Eq deps constraint, which is used to determine whether the new dependency is not equal, causing a recompute the cached value. If you need JS reference equality (think ===), wrap your dependency in an UnsafeReference (this is what it's for).

jamesdbrock commented 2 years ago

if you know a child component re-renders every time you pass it a new reference, even when the value is equal, you can use useMemo to preserve the initial, equivalent reference for passing down to the child.

I think this is what I was trying to say, but you put it better.

What do you mean by “if you know”? We know that it is always true that “a child component re-renders every time you pass it a new reference, even when the value is equal, you can use useMemo to preserve the initial, equivalent reference for passing down to the child.", right?

jamesdbrock commented 2 years ago

Oh, maybe we don't know that. Because React probably assumes that our props object is mutable, so even when we pass the same-by-reference props object again, it might re-render?

Is there a way prevent a child component from re-rendering if the child component’s props are equal-by-value (but possibly not equal-by-reference) to the props from the last render?

megamaddu commented 2 years ago

If the child component uses one of its props to control side effects. Ideally, if it's written in PS and you don't have tons of re-renders happening this should just work as expected. This is because both parent and child will be worrying about PS value equality (Eq). There may be some duplicate equality checks which can lead to performance issues in large apps, but that's what UnsafeReference + useMemo + memo (the one which uses React's shallow reference equality on props to avoid re-renders) solve. You probably don't want to reach for them unless you're experiencing a performance issue or you're building a library and feel enabling this kind of performance optimization is necessary.

React probably assumes that our props object is mutable

React props are immutable by convention, same as state.

Is there a way prevent a child component from re-rendering if the child component’s props are equal-by-value (but possibly not equal-by-reference) to the props from the last render?

memo enables this for a components props using shallow ref equality, and you can use useMemo to preserve those references in the parent using Eq/value equality. For a single parent/child pair this makes very little difference, but for large lists or app-level values which might cause entire app re-renders (or worse, re-mounts) this can sometimes be useful. Generally, though, you want a library to solve this for you.

megamaddu commented 2 years ago

This is memo. It's a little awkward to use in PS since these are very JS/non-functional concepts, and it is side effecting for the same reason component creation is.. but sometimes it's what you need.

megamaddu commented 2 years ago

If this library did have docs, this would all be in an "edge-case performance optimization" section 😅

jamesdbrock commented 2 years ago

Okay thank you!

megamaddu commented 2 years ago

You could also do this if using memo sounds sketch:

    child <- useMemo props \_ -> renderChild props
    pure child
    -- ^ only rebuilds children when props value equality changes