Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize usePrevious #115

Closed
FleetAdmiralJakob opened this issue Dec 6, 2024 · 15 comments
Closed

Optimize usePrevious #115

FleetAdmiralJakob opened this issue Dec 6, 2024 · 15 comments

Comments

@FleetAdmiralJakob
Copy link
Collaborator

FleetAdmiralJakob commented Dec 6, 2024

Following this YT video: https://www.youtube.com/watch?v=B-Xb_8n5wRg the usePrevious hook probably should not use useRef but instead something closer to that implementation: https://github.com/uidotdev/usehooks/blob/90fbbb4cc085e74e50c36a62a5759a40c62bb98e/index.js#L1017-L1027

Other libraries discuss this too as you can see here: streamich/react-use#2605

@childrentime
Copy link
Owner

Thank you very much for pointing this out. This is an interesting question, and I quickly watched the video and it seems to be related to the concurrent mode of react18. I know about concurrent mode, but I am not very familiar with this mode.

@childrentime
Copy link
Owner

It's late at night in my timezone right now, you can submit a PR or I'll change it tomorrow.

@childrentime
Copy link
Owner

By the way, I also have an interesting example of concurrent mode here: https://reactarium.vercel.app/#eNrFVltv2zYU/iusitYKZsmqW2eBZgfrsgAtsO1h7Z6iPtASJbGhSIGkfKnh/75DUpSlZBv2ViEX8dz4ne8cHuoUNJjyWKtDkJ4CjhsSpBfRPGCYVx2ujFQfW6JySVsN8h1mnRHSphVSoxPKJcGa/CmERmdUStGgLABRrqNCNIucUcJ1FvyU8d7jfdt6s3gBC6vLeC640kiaMJtRzLAQeddAiLgi+p4R8/rL8WMRwiagzoIrhBX68Pn333rlFUQzmlgSXhAZrs1+i1sjDs7zAFZPcvaS/53yzMKOc6VmQ1In1ClyX5Yk13Pz+kkD/jlSGkv9WWKuqKaCe4Jmlp+ZzZsRjchBE8kxs16QPpAqOg6yFCXoDGbGiFGlCSdSpQjz48MXsHv4YkOUHc9t+IKqFuu8DiE6rFMIZBKxDuh8hU4ZR4iWqNfHRok2mw2aUQ6UG/pmvRX6L1ATVdyL0Q/ojQWLIE3CFPGBdC3FHt1LKWQ4+4s/crHnyCGYmbKAvfkz5BeXQt5jyCIs+RXa3KKSh1fGEMxGyapuayq0JWCWotBa7gQt+gSGcIanOI6H9RziGd4QkkR3kveuPdix2wgRZZAhAHJ40AvgDMA58M+QmVboGfoVawzxbXDX4Q/QE7Y1iLb0mTr6hgknxLrwQ1+FE5wuWI15wcgd/KtMiZyFj/xP0bxjxwf6wO9C5Tig9+hpGrm4vOfQf86mt7CZeTYAxUfTFnB4xsiHFvWtOWo9aFHjPUerxLxknBzs4SpIiTum0UAwHL+npNZi7ziFlwmlJYZWnMAMXVrrgu5QzrBSf8Ag2GRmEGTBrVOCettpLQCEWyIk+B2j+ePmNKmDe56c82mpBiOHLnxhwHpy3WMTHxb2OJhnAAOHSFQVIyZdaEk9gFw4lIPhycRGr1/7JK3RKAysPjGxvxMwtjgEgsn4fZXry+Kqz3u9gMpY6fNDPwnke8BMRxgqUPWWSBgeDeY5iUESOlr3NQXqwmdKFFm3NVomyTD2FgtUCJDrmvJqGE6uzWyDu+aaHvBxe63rt7d3flKerIsfkef1ApQmKX8VwSXy5CoykslV5AT+DkrtDWmxltALUYkbyo4psmcNTsARRlYTdXSO3u8IpyD6QNiOaJpjEEmKGRhBn0aKSFpa3NGebB8phDLxVAPxTerm0tBgTrEihbNrxLdIqMMzw0rio8oxc3PBaveEVrVO0bsksUJGOYnqXvgmXllhLpgAjmS1DZerFfK/C3Tz4ytHqYYRFrmL3O4kWk0b+o0wUtEtZVQfL2YK5BEuvnbKbJEkry5o1JHrmigKVyeHzvFd9fLCZYMP0Z4WugbX5U3SHqxzi4vC7ruECWUlDZYVhesmQbjT4rI58FSBOIeuJNLH34rieJl6DEOZSkZc6IZyv+Hbpd/PCAeSkmRXT/d0mBjOSUShypDNsN9A5svEMm73jwssH/2YdNqyNDW3WsBnZ4czGFJN4mvSQImWfcaTLqO8hkroEbNAOmAd2/rSr/rS551UZu9W0AHsVkgoKTi2B6QEowXSZni2GGrtojuLSOKCdpDoTc+QNbMzNvUmNjWAvVypgXibWFqLHXyW2PTGtkDD9bvr/MJEb17Cl6aaT5fRjiq6helho4hOm0aGtgbcpgFGZ8cYmyZ1e/gz7j4Powa38Vcl+OisP9VMznwv8Yfebp71LioLYK64YdV/aRtJFtRatypdLIhqYlUvrObnNzfxMk6yAPIaOUw+zf/N1xgN/v0khJ/gfP4byaoOZw==

@childrentime
Copy link
Owner

https://reactarium.vercel.app/#eNqVVtuO2zYQ/RVCQWAZtS52481C9RpNgwAJ0PShF/ShCgpaGknMUqRAUl47rv+9Q1KS7Q1aIA+7os6cuc9QPgUtZSI2+hBkp0DQFoLsAi0CTkXd09qi5tiBLhTrDOJ7ynsLsraTypATUSBKUORMKiVbkgcKaGGiUrZ58EMuBtqbrhvlcYIvTpaLQgptiJLSkAcSlrLoWxAmrsG842CPPx0/lCHaREYezAnV5P3vH38ehHO04b2HG+sg2S4s0cLBeREg9Cy9EfmG7HoNvxlqYGFPv0Llnu+qCgozpjxzGc+mXGcuw7jQGrFcVL0oDJPC1iCck1MuCPF5/1XIXpgF0WDe2tMnLMLoL0znF2KnYM9kr70co9iIvt1h0f8houd8G9r/Nu3J8t8LUklVwB9dicauDQ+q2zBaWg2rM2UUYnwPWx+iNyU5xFzW4cyFOlsgiE8XGpnCioteYR9sD53YBXJeDPl9Qvb/eLkKM/xITRMrKnB4wrlz8pWVm6DGADCu8eholqjA9EqQ0HvZbP0TT81y+x44l+RPqXi5SfB9kpVsTwpOtf4FB+ZhVlBVziYpyne9MdhJKd5yVjw+nHwmY//C0FfHYu5EviPL+Xnrz0yTkzudN4m3c225205NtsTnpUWd7hJmgnEOb5vEHTDts80bDm4ES6hoz93aXXYBB/LZLljkZhc8MC5B5hbT9amSwkQVbRk/ZuSDMKBwbI/aQBv1bEHe7EEwhLCwezCsoAgpRjmSqNCRBsUqNxXRE+weGZqy9nSL9hsm6oxQYZDOqIbS81r5JZL68BWxVvSoC8rB0Zz0CVjdmIy8SlMHciYgagZwGa+HveBSZUTVu3C1XpPxLyH3r1/6zTFwMJG/Tpwn2RnWsi/AoWY7xpk5Xmga8YiWn3ttXaTpy0s0+ihMA5rpjAgpbJiuMS8utWzpIXpipWlQdXWfdgen3NGydH5XClqHtFTVTGQkJbQ38uIc61QjXOBYgBrt72R59OZLpjtOsU0VB2+6ZWJ0+P1q9GfBqUhpum9uffqYOC0gYthlzGbyNxXzReoq7vzHJVWP4+XmpVVle+6kGJ/fHEeYUk3jO2ixRash45spY6LBTvjLxFcWi46xXnPH1q+H1uO2aOu7k2wKdicVthQVuwPBq4OVxOAVoztq9+qKESlash4TvR8q5GjMXt3ZSHGpYdirtZ4K7xLLGrnH+9ild83FMty9uisulRjoFX7q9OL2NdozzXYcvBXZGzvIONYYtx2Aq92xZDuk3oc1bXfcf36ilnbxZy3F1a4/l9zs/ICMS++c54OKzoNsvKiHL7tF8qAxptNZkoBuY90kTvLj8nWMpckDzOtKwf8U+C8lK50U3Y3vChWcz/8CMW7oyw== No. I was mistaken, it has nothing to do with concurrent mode and behaves the same in react 17.

@childrentime
Copy link
Owner

So the traditional usePrevious hook worked because while useRef was updated, React didn't re-render - it just appeared to work correctly since the ref update wasn't triggering a new render cycle. I found this implementation quite elegant too. It demonstrates that you can achieve the same result without setState by using useRef: https://github.com/alibaba/hooks/blob/master/packages/hooks/src/usePrevious/index.ts

@FleetAdmiralJakob
Copy link
Collaborator Author

FleetAdmiralJakob commented Dec 6, 2024

Interesting

So the traditional usePrevious hook worked because while useRef was updated, React didn't re-render - it just appeared to work correctly since the ref update wasn't triggering a new render cycle. I found this implementation quite elegant too. It demonstrates that you can achieve the same result without setState by using useRef: alibaba/hooks@master/packages/hooks/src/usePrevious/index.ts

While this looks nice I think it violates the rule outlined in the pitfall section too, right?
image
https://react.dev/reference/react/useRef

@FleetAdmiralJakob
Copy link
Collaborator Author

btw. alibaba has the same discussion 😂: alibaba/hooks#2162

@childrentime
Copy link
Owner

You're right. I'll migrate to a pattern that doesn't rely on refs👍👍👍.

@prlnd
Copy link

prlnd commented Dec 7, 2024

It's allowed to write ref.current conditionally: https://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents

Edit: nvm, it's only allowed for initialization.

@childrentime
Copy link
Owner

Yes. This is typically performancfe improve method. Often used with Intersetion Observer initialization or other thing

@childrentime
Copy link
Owner

I'm thinking about the implementation of useLatest - it writes to the ref during render. However, it seems difficult to simply migrate this operation to useEffect instead?: https://stackoverflow.com/questions/68512910/concurrent-safe-version-of-uselatest-in-react

@childrentime
Copy link
Owner

facebook/react#16956 (comment) also a good case. I decide to migrate to useLayoutEffect.

@FleetAdmiralJakob
Copy link
Collaborator Author

So how would your other solution look like?

@childrentime
Copy link
Owner

just wrap with useIsomorphicLayoutEffect。

export const useLatest: UseLatest = <T>(value: T): MutableRefObject<T> => {
  const ref = useRef(value)
  useIsomorphicLayoutEffect(() => {
    ref.current = value
  }, [value])
  return ref
}

@childrentime
Copy link
Owner

facebook/react#16956 (comment) As long as you don't pass the value to child components and use it in the child component's useLayoutEffect, there won't be any issues. I believe the chances of encountering this scenario are quite rare, and it's more acceptable compared to potential concurrent mode issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants