Coder Social home page Coder Social logo

Comments (14)

bowheart avatar bowheart commented on May 22, 2024 1

Hey @Aleksion sorry I spent some time at Disney World a couple weeks ago and was busy getting back to work last week.

I've basically decided on a hybrid approach. Instead of switching off of useSyncExternalStore completely, we'll still use it for now, but won't rely on the passed onStoreChange function to rerender the component. This has a few advantages (for example, transition support) and only one disadvantage (an extra useState call is needed).

This is a pretty involved change. I'm actively working on it. Will probably take a couple days to get a new version published.

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024 1

ohhh... I think this might have done the trick.
It seems to work perfectly!

from zedux.

bowheart avatar bowheart commented on May 22, 2024

Thank you @Aleksion for reporting! It looks like this is due to StrictMode changes in React 18. You can see this in the sandbox by either removing StrictMode in index.js or switching to React v17.

Removing StrictMode is an easy fix. But obviously, Zedux is supposed to be compatible with it. This is a confirmed bug.

Initial findings: It looks like React no longer calls the subscribe function passed to useSyncExternalStore if the reference changes on a subsequent render. The useAtomSelector code relies on this happening. I remember hating that behavior in React 17, so I'm glad they made that change. Unfortunately, it broke useAtomSelector.

I'll spend some time today switching all the tests to use StrictMode. We should be doing that anyway. Looks like 3 tests fail in StrictMode that would have caught this.

As for the real fix, I can probably get something quick and dirty in pretty quickly. But I actually already have a branch where I switched useAtomSelector off of useSyncExternalStore completely, which would also fix this and may be better anyway. I'll explore it a bit and report back.

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024

Thank you for the quick response!
Fascinating - I've seen multiple complaints about React 18 and strict mode on Xwitter as of late (useEffects with no dependencies firing twice 🤯).

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024

any updates on this @bowheart?

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024

Hi,

Just wanted to check on this 🙈 Don't mean to apply pressure, but I'm moving into production and I like getting reactStrictMode back on 😂

(I also know that it's open source so it is what it is. Just wanted to understand if it's still on track).

Thank you for the amazing work you've done here!

from zedux.

bowheart avatar bowheart commented on May 22, 2024

Hey @Aleksion pressure away! It's good for me.

TL;DR this change has suffered some feature creep. But it's really a good thing, fixing a few outstanding issues.

Longer explanation:

I actually got all the tests passing about when I thought I would (a few days after my last message), but it's a big enough change that I wanted to add more tests and play with it in a test project for a bit. And it's a good thing I did because I discovered a new problem in strict mode that requires another rework to inline selectors passed to useAtomSelector.

The core of the change is that both useAtomSelector and useAtomInstance now add their graph edges during render. These operations need to be completely idempotent since they are impure. With atoms and externally-defined atom selectors, this works like a charm. But when inline selectors (selectors whose function reference changes across renders) are used and React double-renders a component unnecessarily in strict mode, the generated id for the first render doesn't match the generated id on the second render. In this case (and only in strict mode), effects aren't allowed to fire to clean up the unnecessary graph edge, leading to a memory leak. The leak isn't very aggressive - it would take a lot for it to lead to memory problems - but those leaked nodes would show up when inspecting the graph, potentially leading to wasted dev time trying to figure out why they're there.

This fault is only possible (or at least only possibly problematic) in dev strict mode as far as I can tell. But I'd still call it a must fix. So for the fix: I'm making useAtomSelector generate an id using a deterministic hash of the stringified function body. This generation shouldn't be too expensive and will only need to run once for non-inline selectors (unless Zedux has already encountered and cached the selector reference in another atom or selector before). For inline selectors, the hashing will need to run every render, but, again, that shouldn't be too expensive, especially since inline selectors are typically smaller functions. Still I'll want to run stress tests to verify.

Other outstanding issues that this fixes:

  • Transition support! Zedux state updates should now work with startTransition, though they may not play perfectly with suspense since Zedux is an external store that doesn't implement state versioning (currently), so it can't be in multiple states at once for full compatibility with React's fiber system. But React doesn't have any way for external stores to integrate with that anyway yet, nor are there any plans to ever support it as far as I know. In short, Zedux should be as compatible with transitions as any state manager can be.
  • ttl: 0 atoms can be destroyed synchronously by an unmounting component tree but captured by user code during initial render of a mounting component tree (e.g. by passing an atom instance to useRef()). These references need to be manually updated to use the new, non-destroyed atom instance (e.g. ref.current = ecosystem.getInstance(ref.current)). This isn't at all straightforward and has led to a few (rare) bugs that take a while to track down. Now that graph edges are added during render of the new component tree, the unmounting component tree doesn't destroy ttl: 0 atoms that are actually still in use.

Additionally, these changes do completely get rid of useSyncExternalStore. I've ditched the hybrid approach since it created as many edge cases as the original code. This means we'll no longer need to bundle the uSES shim, reducing Zedux's bundle size.

For time frame, I'm shooting to have all of this done and tested this week. If performance looks good, I'll publish a release candidate by the end of this week.

from zedux.

bowheart avatar bowheart commented on May 22, 2024

@Aleksion a preemptive heads up this time. I'm almost there. I'll try to snag a couple hours to finish this and get a release out tomorrow

from zedux.

bowheart avatar bowheart commented on May 22, 2024

@Aleksion The fix for this has been released in version 1.2.0-rc.0, which can be installed via npm i @zedux/react@next (or similar).

I've verified that it fixes your codesandbox here: https://codesandbox.io/s/useatomselector-react-context-t5dcps

If you get a minute, could you try out the release candidate version and verify that it fixes the issue in your app? I'm going to do some heavy testing with the release candidate version and if all goes well will probably release version 1.2.0 within a few days.

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024

Hi @bowheart,

Just did a test, and it seems like useAtomSelector with dynamic selectors causes infinite loops:
Screenshot 2023-11-20 at 10 21 57 AM

from zedux.

bowheart avatar bowheart commented on May 22, 2024

@Aleksion Thank you! This is so helpful! I was getting a different error and thought I broke suspense support somehow. This clarified everything and I found the problem quickly and have put together #88 to fix. Publishing a new release candidate momentarily that I will test.

from zedux.

bowheart avatar bowheart commented on May 22, 2024

Version 1.2.0-rc.1 has been published with the fix for inline selectors that return referentially unstable results

from zedux.

Aleksion avatar Aleksion commented on May 22, 2024

@bowheart I think I might have found another bug:

useAtomSelector re-renders subscribing components twice.

Screenshot 2023-11-21 at 12 12 53 PM Screenshot 2023-11-21 at 12 12 34 PM

from zedux.

bowheart avatar bowheart commented on May 22, 2024

@Aleksion I haven't been able to reproduce this. Are you perhaps seeing the double-renders from StrictMode?

const idAtom = atom('id', () => ({ _id: '123' }))
const selector = (
  { get }: AtomGetters,
  instance: AtomInstanceType<typeof idAtom>
) => get(instance)._id

function Test() {
  const [state, setState] = useState(1)
  const instance = useAtomInstance(idAtom)
  const val = useAtomSelector(selector, instance)

  console.log('rendered component', state)

  return (
    <div>
      Selector val: {val}, state: {state}{' '}
      <button onClick={() => setState(s => s + 1)}>Increment</button>
      <button onClick={() => instance.setState(s => ({ _id: s._id + 1 }))}>
        Update Atom
      </button>
    </div>
  )
}

This (perhaps over-simplified?) example renders the component once for each click of each button outside StrictMode, and twice for each click of each button when wrapped in StrictMode, as expected. If you could modify this code snippet to make it trigger excess renders beyond that, that would be a great help

from zedux.

Related Issues (3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.