Сделать независимые копии стора для инстансов компонента

Как правильно сделать, чтобы эти Counter'ы работали независимо, а не увеличивались вместе, как сейчас?

https://share.effector.dev/YuZvKpqn

import React from 'react'
import ReactDOM from 'react-dom'
import { createEvent, createStore } from 'effector'
import { useUnit } from 'effector-react'

const inc = createEvent()

const $counter = createStore(0).on(inc, n => n + 1)

function Counter() {
  const [counter, incFx] = useUnit([$counter, inc])
 
  return <button onClick={incFx}>{counter}</button>
}

function App() {
  return (
    <div>
      <Counter />
      {" "}
      <Counter />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Насколько я понимаю, надо копать в сторону fork и Provider, и мне даже удалось сделать так:

https://share.effector.dev/08HXMbOo

import React from 'react'
import ReactDOM from 'react-dom'
import { createEvent, createStore, fork } from 'effector'
import { useUnit, Provider } from 'effector-react'

const inc = createEvent()

const $counter = createStore(0).on(inc, n => n + 1)

function Counter() {
  const [counter, incFx] = useUnit([$counter, inc])
 
  return <button onClick={incFx}>{counter}</button>
}

const scope1 = fork()
const scope2 = fork()

function App() {
  return (
    <div>
      <Provider value={scope1}>
        <Counter />
      </Provider>
      {" "}
      <Provider value={scope2}>
        <Counter />
      </Provider>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Но у меня ощущение, что это решение неправильное и на более сложном коде вызовет кучу проблем, например:

  • я не могу так создать произвольное количество Counter'ов - например, на основе массива
  • fork форкает все сторы, а не только $counter - получается, если я захочу внутри провайдера изменить какой-то общий стор, то у меня это не выйдет, поскольку он изменится только в рамках скоупа

Как сделать так, чтобы всё правильно работало в чём-то таком?

https://share.effector.dev/VVz0vtnu

import React from 'react'
import ReactDOM from 'react-dom'
import { createEvent, createStore } from 'effector'
import { useUnit } from 'effector-react'

const inc = createEvent()
const $counter = createStore(0).on(inc, n => n + 1)

const incCommon = createEvent()
const $common = createStore(0).on(incCommon, n => n + 1)

function Counter({ name }) {
  const [counter, incFx, common, incCommonFx] = useUnit([$counter, inc, $common, incCommon])
 
  return (
    <p>
      <button onClick={incFx}>{name}: {counter}</button>
      {" "}
      <button onClick={incCommonFx}>Common: {common}</button>
    </p>
  )
}

function App({ names }) {
  return (
    <div>
      {names.map(name => <Counter name={name} />)}
    </div>
  )
}

ReactDOM.render(<App names={["Jack", "Peter"]} />, document.getElementById('root'))

Ответы (1 шт):

Автор решения: Andrey Tabakov

Проблема в том, что в вашем примере createStore и createEvent создают глобальные синглтоны вне компоненты. Когда вы импортируете $counter и inc, вы всегда получаете одни и те же экземпляры.

Через fork в теории можно, вы создаёте общий объект, но затем просите сделать копию (которую можно было бы форкнуть внутри компоненты кстати, но не суть) и потом её используете.

На мой взгляд правильнее было бы создавать счётчик через подобие фабрики. https://share.effector.dev/cXfKMHC2

import React from 'react'
import ReactDOM from 'react-dom'
import { createEvent, createStore } from 'effector'
import { useUnit } from 'effector-react'

// Фабрика для создания изолированных сторов и событий
const createCounter = () => {
  const inc = createEvent()
  const $counter = createStore(0).on(inc, n => n + 1)
  return { inc, $counter }
}

// Общий стор и событие, ?если нужно?
const incCommon = createEvent()
const $common = createStore(0).on(incCommon, n => n + 1)

function Counter({ name }) {
  // Создаём изолированные стор и события внутри конкретной компоненты
  const { $counter, inc } = React.useMemo(createCounter, [])
  const [counter, incFx] = useUnit([$counter, inc])
  const [common, incCommonFx] = useUnit([$common, incCommon])

  return (
    <p>
      <button onClick={incFx}>{name}: {counter}</button>
      {" "}
      <button onClick={incCommonFx}>Common: {common}</button>
    </p>
  )
}

function App({ names }) {
  return (
    <div>
      {names.map(name => <Counter key={name} name={name} />)}
    </div>
  )
}

ReactDOM.render(<App names={["Jack", "Peter"]} />, document.getElementById('root'))
→ Ссылка