API
reflect

reflect

import { reflect } from '@effector/reflect';
const Component = reflect({
  view: SourceComponent,
  bind: Props,
  hooks: Hooks,
});

Static method to create a component bound to effector stores and events as stores.

Arguments

  1. view — A react component that should be used to bind to
  2. bind — Object of effector stores, events or any value
  3. mapProps — Optional object of derived props. Each entry computes a prop for the view from a store value combined with the component's own props.
  4. hooks — Optional object { mounted, unmounted } to handle when component is mounted or unmounted.
  5. useUnitConfig - Optional configuration object, which is passed directly to the second argument of useUnit from effector-react.

Returns

  • A react component with bound values from stores and events.

Example

// ./user.tsx
import { reflect } from '@effector/reflect';
import { createEvent, restore } from 'effector';
import React, { ChangeEvent, FC } from 'react';
 
// Base components
type InputProps = {
  value: string;
  onChange: ChangeEvent<HTMLInputElement>;
  placeholder?: string;
};
 
const Input: FC<InputProps> = ({ value, onChange, placeholder }) => {
  return <input value={value} onChange={onChange} placeholder={placeholder} />;
};
 
// Model
const changeName = createEvent<string>();
const $name = restore(changeName, '');
 
const changeAge = createEvent<number>();
const $age = restore(changeAge, 0);
 
 
// Components
const Name = reflect({
  view: Input,
  bind: {
    value: $name,
    placeholder: 'Name',
    onChange: (event) => changeName(event.currentTarget.value),
  },
});
 
const Age = reflect({
  view: Input,
  bind: {
    value: $age,
    placeholder: 'Age',
    onChange: (event) => changeAge(parseInt(event.currentTarget.value)),
  },
});
 
export const User: FC = () => {
  return (
    <div>
      <Name />
      <Age />
    </div>
  );
};

Deriving props with mapProps

Sometimes a prop is not just a store value or a plain value, but a combination of a store's state and the props passed to the component. For these cases use mapProps.

Each entry is { source, fn }:

  • source — what the derived value is read from reactively. Like combine / useUnit, it can be:
    • a single store — value is its state;
    • an object of stores — value is the resolved object ({ a: Store<A> }{ a: A });
    • an array of stores — value is the resolved tuple ([Store<A>, Store<B>][A, B]).
  • fn(value, props) => derivedProp, where value is the resolved source value (its type is inferred — no annotation needed) and props are the component's own props.

Each key in mapProps must be a prop of the view — a typo'd or unknown key is a type error at the key itself. A key must not appear in both bind and mapProps — this is a type error. For best type inference, pass source as an inline literal; a variable typed as Store<any> will widen fn's value argument to any.

import { reflect } from '@effector/reflect';
import { createStore } from 'effector';
import React, { FC } from 'react';
 
type GreetingProps = {
  title: string;
  label: string;
};
 
const Greeting: FC<GreetingProps> = ({ label }) => <span>{label}</span>;
 
const $user = createStore({ name: 'Bob' });
 
const Hello = reflect({
  view: Greeting,
  bind: {},
  mapProps: {
    // `label` is derived from the `$user` store and the incoming `title` prop
    label: {
      source: $user,
      fn: (user, props) => `${props.title} ${user.name}`,
    },
  },
});
 
// usage: `label` becomes optional, since it is derived
export const App: FC = () => <Hello title="Hello," />;

To derive from several stores at once, pass an object (or array) of stores as source:

const Hello = reflect({
  view: Greeting,
  bind: {},
  mapProps: {
    label: {
      source: { user: $user, count: $count },
      // `s` is inferred as { user: { name: string }; count: number }
      fn: (s, props) => `${props.title} ${s.user.name} (${s.count})`,
    },
  },
});

The component re-renders only when the source changes. A prop computed via mapProps is made optional in the resulting component's type and can still be overridden explicitly at the usage site (an explicitly passed prop wins over the derived value — in that case fn is not invoked for the overridden key).

Note: like bind, the mapProps field is supported by all Reflect operators — reflect, createReflect, variant and list. In list, a key must not appear in both mapItem and mapPropsmapItem automatically omits keys that are derived via mapProps.

Fork API auto-compatibility

This feature is available since 9.0.0 release of Reflect.

The Fork API (opens in a new tab) - is a feature of Effector, which allows to seamlessly create virtualized instances of the application's state and logic called Scope (opens in a new tab)'s. In React to render an App in specific Scope a Provider (opens in a new tab) component should be used.

When an external system (like React) calls an Effector event and Fork API is used - target Scope should be provided before call via allSettled or scopeBind API.

The reflect operator does it for you under the hood, so you can provide arbitary callbacks into bind:

const Age = reflect({
  view: Input,
  bind: {
    value: $age,
    placeholder: 'Age',
    onChange: (event) => {
      changeAge(parseInt(event.currentTarget.value));
    },
  },
});

☝️ This feature works for bind field in all of Reflect operators.

In most cases your callbacks will be synchronous and you will not need to do anything besides using Provider (opens in a new tab) to set the Scope for the whole React tree.

Special case: Asynchronous callbacks

Asynchronous callbacks are also allowed, but those should follow the rules of Imperative Effect calls with Scope (opens in a new tab) to be compatible.

TypeScript and Polymorphic Types Caveat

Generally, reflect handles polymorphic props well. However, in some implementations, such as Mantine UI (opens in a new tab), it may not work as expected.

In such cases, it is recommended to explicitly narrow the component type. For example:

  const ReflectedMantineButton = reflect({
    view: MantineButton<'button'>, // <- notice explicit component type in the "<...>" brackets
    bind: {
      children: 'foo',
      onClick: (e) => {
        clicked(e.clientX);
      },
    },
  });