saasquatch / bunshi

Molecule pattern for jotai, valtio, zustand, nanostores, xstate, react and vue
https://www.bunshi.org/
MIT License
218 stars 16 forks source link

Scoped value update issue with React #50

Closed OhSeungWan closed 7 months ago

OhSeungWan commented 7 months ago

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch bunshi@2.1.2 for the project I'm working on.

problem is:

When the value of the Scope Provider is first changed, the value of the scope inside the molecule function does not change. However, from then on, it will change, but it will change to the value one time before.

code for test

import { ScopeProvider, createScope, molecule, use, useMolecule } from 'bunshi/react'
import { atom, useAtom } from 'jotai'
import React, { useState } from 'react'

const testListAtom = atom([1, 2, 3, 4, 5])

const TestCope = createScope<{ number?: number }>({})
const TestMol = molecule(() => {
  const scope = use(TestCope)
  console.log(scope.number) //In the first case, there is no change, and in the second case, the value from one timing ago is output.
  return { scope }
})

const useTestMol = () => {
  const mol = useMolecule(TestMol)
  const number = mol.scope.number

  return { number }
}

const Test = () => {
  const { number } = useTestMol()
  return <div>{number}</div>
}

export const TestList = () => {
  const [testList, setTestList] = useState([1, 2, 3, 4, 5])
  //   const [testList, setTestList] = useAtom(testListAtom)

  const handleClick = () => {
    setTestList(list => list.map(l => l + 1))
  }

  return testList.map((number,index) => (
    <ScopeProvider key={index} scope={TestCope} value={{ number }}>
      <Test />
      <button onClick={handleClick}>test</button>
    </ScopeProvider>
  ))
}

export default Test

Here is the diff that solved my problem:

diff --git a/node_modules/bunshi/dist/react.mjs b/node_modules/bunshi/dist/react.mjs
index 0d7d66c..4314802 100644
--- a/node_modules/bunshi/dist/react.mjs
+++ b/node_modules/bunshi/dist/react.mjs
@@ -52,7 +52,7 @@ function useScopes(options) {
     const innerSub = injector.createSubscription();
     innerSub.expand(inputTuples);
     return innerSub;
-  }, [injector, ...flattened]);
+  }, [injector,options, ...flattened]);
   const [tuples, setTuples] = useState(sub.tuples);
   useEffect(() => {
     const subbedTuples = sub.start();
@@ -61,7 +61,7 @@ function useScopes(options) {
       sub.stop();
     };
   }, [sub, setTuples]);
-  return useMemo(() => tuples, flattened);
+  return useMemo(() => tuples, [flattened,options]);
 }
 function useScopeTuplesRaw(options) {
   const parentScopes = useContext2(ScopeContext);
@@ -96,24 +96,32 @@ function useScopeTuplesRaw(options) {
   return inputTuples;
 }

+// src/react/ScopeProvider.tsx
 // src/react/ScopeProvider.tsx
 function ScopeProvider(props) {
   const { value, scope, uniqueValue } = props;
-  let options;
-  if (uniqueValue) {
-    options = {
-      withUniqueScope: scope
-    };
-  } else {
-    options = {
-      withScope: [scope, value]
-    };
-  }
+
+  // options를 useMemo로 감싸 props 변경 시 재계산되도록 함
+  const options = useMemo(() => {
+    if (uniqueValue) {
+      return {
+        withUniqueScope: scope,
+      };
+    } else {
+      return {
+        withScope: [scope, value],
+      };
+    }
+  }, [scope, value, uniqueValue]); // 의존성 배열에 scope, value, uniqueValue 추가
+
   const simpleDownstreamScopes = useScopes(options);
+
+  // downstreamScopes 계산 시 options 변경을 감지
   const downstreamScopes = useMemo2(
     () => simpleDownstreamScopes.filter(([scope2]) => scope2 !== ComponentScope),
-    [simpleDownstreamScopes]
+    [simpleDownstreamScopes] // simpleDownstreamScopes가 변경될 때 재계산
   );
+
   return React3.createElement(
     ScopeContext.Provider,
     { value: downstreamScopes },
@@ -121,6 +129,7 @@ function ScopeProvider(props) {
   );
 }

+
 // src/react/useMolecule.tsx
 import { useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
 function useMolecule(mol, options) {
diff --git a/node_modules/bunshi/src/react/ScopeProvider.tsx b/node_modules/bunshi/src/react/ScopeProvider.tsx
index 51a2213..e3a4011 100644
--- a/node_modules/bunshi/src/react/ScopeProvider.tsx
+++ b/node_modules/bunshi/src/react/ScopeProvider.tsx
@@ -28,6 +28,8 @@ export function ScopeProvider<T>(
 ): ReturnType<React.FC> {
   const { value, scope, uniqueValue } = props;

+  console.log("ScopeProvider", props);
+
   let options: MoleculeScopeOptions;
   if (uniqueValue) {
     options = {

If I misunderstood or missed anything, please let me know!

This issue body was partially generated by patch-package.

loganvolkers commented 7 months ago

Thanks for the report @OhSeungWan!

I reproduced the bug with some tests, and found that the culprit was actually the useMemo in the return of useScopes. Take a look at https://github.com/saasquatch/bunshi/commit/6e78a9b07ada22ab5a24ed3220c44d7547df21ac for the fix.

Will work on releasing this in version 2.1.3

loganvolkers commented 7 months ago

@OhSeungWan this has been patched and released in v2.1.3