Examples

Basic Toggle

Edit this snippet
import { RxBuilder } from '@reactables/core';

export const RxToggle = (initialState = false) =>
  RxBuilder({
    initialState,
    reducers: {
      toggleOn: () => true,
      toggleOff: () => false,
      toggle: (state) => !state,
    },
  });
Bind RxToggle to View

Fetching Data with an Effect

Edit this snippet
import { RxBuilder, Action } from '@reactables/core';
import DataService from './data-service';
import { from, Observable, of } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';

export const RxFetchData = ({ dataService }: { dataService: DataService }) =>
  RxBuilder({
    initialState: {
      loading: false,
      success: false,
      data: null as string | null,
      error: null as any,
    },
    reducers: {
      fetch: {
        reducer: (state) => ({ ...state, loading: true }),
        effects: [
          (action$: Observable<Action>) =>
            action$.pipe(switchMap(() => from(dataService.fetchData()))).pipe(
              map((response) => ({ type: 'fetchSuccess', payload: response })),
              catchError((err: unknown) =>
                of({ type: 'fetchFailure' })
              )
            ),
        ],
      },
      fetchSuccess: (state, action: Action<string>) => ({
        ...state,
        success: true,
        loading: false,
        data: action.payload,
        error: null,
      }),
      fetchFailure: (state, action) => ({
        ...state,
        loading: false,
        error: true,
        success: false,
      }),
    },
  });


Bind RxFetchData to View

Composition with Reactables

Beyond creating primitives with RxBuilder, Reactables can be combined into new ones.

Two main use cases:

  • Reuse existing functionality.
  • Reactable(s) reacting to changes from another reactables.

Reactables emit:

Other reactables can subscribe to these events via its sources option (see RxConfig).

Example:

  • RxAuth – handles user login.
  • RxProfile – loads the user profile after login.

This forms a directed acyclic graph (DAG): profile management depends on authentication but not vice versa. Both remain reusable, independent units of state logic.

DAG flow: RxAuth (login) ──▶ (loginSuccess) ──▶ RxProfile (fetch profile)

Edit this snippet

RxAuth

import { Action, RxBuilder } from '@reactables/core';
import { Observable, switchMap, of, delay } from 'rxjs';

export const RxAuth = () =>
  RxBuilder({
    initialState: {
      loggingIn: false,
      loggedIn: false,
      userId: null as number | null,
    },
    reducers: {
      login: {
        reducer: (state) => ({
          ...state,
          loggingIn: true,
        }),
        effects: [
          (login$: Observable<Action>) =>
            login$.pipe(
              switchMap(() =>
                // Simulate API call for logging in
                of({ type: 'loginSuccess', payload: { userId: 123 } }).pipe(
                  delay(500)
                )
              )
            ),
        ],
      },
      loginSuccess: (state, action: Action<{ userId: number }>) => ({
        ...state,
        loggedIn: true,
        userId: action.payload.userId,
        loggingIn: false,
      }),
    },
  });

RxProfile

import { Action, RxBuilder } from '@reactables/core';
import { Observable, switchMap, of, delay } from 'rxjs';
type UserProfile = {
  userId: number;
  userName: string;
  email: string;
};

export const RxProfile = ({
  sources,
}: {
  sources: Observable<Action<any>>[];
}) =>
  RxBuilder({
    initialState: {
      fetchingProfile: false,
      userProfile: null as null | UserProfile,
    },
    sources,
    reducers: {
      fetchProfile: {
        reducer: (state, _: Action<number>) => ({
          ...state,
          fetchingProfile: true,
        }),
        effects: [
          (login$: Observable<Action<number>>) =>
            login$.pipe(
              switchMap(({ payload: userId }) =>
                // Simulate API call for fetching user profile
                of({
                  type: 'fetchProfileSuccess',
                  payload: {
                    userId,
                    userName: 'Homer Simpson',
                    email: 'homer@simpson.com',
                  } as UserProfile,
                }).pipe(delay(500))
              )
            ),
        ],
      },
      fetchProfileSuccess: (_, action: Action<UserProfile>) => ({
        fetchingProfile: false,
        userProfile: action.payload,
      }),
    },
  });

We can now combine the two and have RxProfile listen to RxAuth’s actions to detect a loginSuccess.

RxApp (combining RxAuth & RxProfile)

import { combine } from '@reactables/core';
import { map } from 'rxjs/operators';
import { RxAuth } from './RxAuth';
import { RxProfile } from './RxProfile';

export const RxApp = () => {
  const rxAuth = RxAuth();
  // Access rxAuth's actions observable
  const [, , authActions$] = rxAuth;

  const fetchProfileOnLoginSuccess$ = authActions$
    // Filter for login success actions
    .ofTypes([authActions$.types.loginSuccess])
    .pipe(
      // Map action to a fetch profile action
      map(({ payload: userId }) => ({
        type: 'fetchProfile',
        payload: userId,
      }))
    );

  const rxProfile = RxProfile({
    // Have RxProfile listen for loginSuccess and fetch profile
    sources: [fetchProfileOnLoginSuccess$],
  });

  return combine({
    auth: rxAuth,
    profile: rxProfile,
  });
};

Bind RxApp to View

Global State with Reactables

A Reactable can be used for global or shared state. It doesn’t dictate how it’s stored or accessed: in React, you can use Context; in Angular, services; or any approach your framework provides.

Debugging

When creating a reactable primitive with RxBuilder, a debug option is available to console.log all the actions and state updates occuring within that primitive.

Debug Example: