Learn
Testing

SSR and tests via Fork API

Since effector-react 22.5.0 (opens in a new tab) it is no longer necessary to use @effector/reflect/ssr due to isomorphic nature of effector-react hooks after this release, you can just use @effector/reflect main imports.

Just add Provider from effector-react to your app root, and you are good to go.

For SSR (opens in a new tab) and effector-react before 2.5.0 release you will need to replace imports @effector/reflect -> @effector/reflect/ssr.

Also for this case you need to use event.prepend(params => params.something) instead (params) => event(params.something) in bind - this way reflect can detect effector's events and properly bind them to the current scope (opens in a new tab)

// ./ui.tsx
import React, { ChangeEvent, FC, MouseEvent, useCallback } from 'react';
 
// Input
type InputProps = {
  value: string;
  onChange: ChangeEvent<HTMLInputElement>;
};
 
const Input: FC<InputProps> = ({ value, onChange }) => {
  return <input value={value} onChange={onChange} />;
};
// ./app.tsx
import { reflect } from '@effector/reflect';
import { createEvent, restore, sample, Scope } from 'effector';
import { Provider } from 'effector-react';
import React, { FC } from 'react';
 
import { Input } from './ui';
 
// Model
export const appStarted = createEvent<{ name: string }>();
 
const changeName = createEvent<string>();
const $name = restore(changeName, '');
 
sample({
  clock: appStarted,
  fn: (ctx) => ctx.name,
  target: changeName,
});
 
// Component
const Name = reflect({
  view: Input,
  bind: {
    value: $name,
    onChange: changeName.prepend((event) => event.target.value),
  },
});
 
export const App: FC<{ scope: Scope }> = ({ scope }) => {
  return (
    <Provider value={scope}>
      <Name />
    </Provider>
  );
};
// ./server.tsx
import { allSettled, fork, serialize } from 'effector';
 
import { App, appStarted } from './app';
 
const render = async (reqCtx) => {
  const serverScope = fork();
 
  await allSettled(appStarted, {
    scope: serverScope,
    params: {
      name: reqCtx.cookies.name,
    },
  });
 
  const content = renderToString(<App scope={serverScope} />);
  const data = serialize(scope);
 
  return `
    <body>
      ${content}
      <script>
        window.__initialState__ = ${JSON.stringify(data)};
      </script>
    </body>
  `;
};
// client.tsx
import { fork } from 'effector';
import { hydrateRoot } from 'react-dom/client';
 
import { App, appStarted } from './app';
 
const clientScope = fork({
  values: window.__initialState__,
});
 
hydrateRoot(document.body, <App scope={clientScope} />);