Skip to content

Function: joinAdapters()

joinAdapters<ParentState, ExcludedProps>(): <AE>(adapterEntries) => NewBlockAdder<ParentState, FlattendAdapters<AE, ParentState> extends object ? S extends Selectors<ParentState> ? WithStateSelector<ParentState, S<S>> : WithStateSelector<ParentState, Record<string, (state) => any>> : object, ReactionsWithoutSelectors<ParentState, FlattendAdapters<AE, ParentState> & BasicAdapterMethods<ParentState> & WithUpdateReaction<ParentState>>>

Defined in: src/lib/adapters/join-adapters.function.ts:221

joinAdapters creates a complex adapter from simpler adapters by taking each of their state change functions and selectors and adding them to the new adapter with more specific names to distinguish them from each other. All state reaction names have their adapter's namespaces inserted after the first word, and all selector names get prepended with their adapter's namespace.

The initial joinAdapters call returns the same thing as the buildAdapter call, so it can be called again and again with more objects inheriting from previous objects, until a final empty call () to get the final built adapter.

Example: Basic joinAdapters

tsx
import { joinAdapters, createAdapter } from '@state-adapt/core';

interface State {
  a: number;
  b: number;
}

const adapter = joinAdapters<NumbersState>()({
  a: createAdapter<number>()({}),
  b: createAdapter<number>()({
    setTo0: state => 0,
    selectors: {
      negative: state => state * -1,
    },
  }),
})();

This is the same as:

tsx
import { createAdapter } from '@state-adapt/core';

interface State {
  a: number;
  b: number;
}

const adapter = createAdapter<State>()({
  setA: (state, newA: number) => ({...state, a: newA}),
  resetA: (state, payload: void, initialState) => ({...state, a: initialState.a}),
  setBTo0: (state, payload: void) => ({...state, b: 0}),
  setB: (state, newB: number) => ({...state, b: newB}),
  resetB: (state, payload: void, initialState) => ({...state, b: initialState.b}),
  set: (state, newState: State) => newState,
  reset: (state, payload: void, initialState) => initialState,
  update: (state, newState: Partial<State>) => ({...state, ...newState}),
  selectors: {
    a: state => state.a,
    b: state => state.b,
    bNegative: state => state.b * -1,
    state: state => state,
  },
});

Example: joinAdapters with buildAdapters-like syntax

tsx
import { joinAdapters, createAdapter } from '@state-adapt/core';
import { numberAdapter } from './number.adapter';

interface NumbersState {
  a: number;
  b: number;
}

const adapter = joinAdapters<NumbersState>()({
  a: numberAdapter,
  b: numberAdapter,
})({
  // Selectors
  total: s => s.a + s.b,
})({
  // Group reactions
  incrementAll: {
    a: numberAdapter.increment,
    b: numberAdapter.increment,
  },
})(([selectors, reactions]) => ({
  // More reactions
  addBToA: state => ({ ...state, a: state.a + selectors.b(state) }),
  addAToB: state => ({ ...state, b: state.b + selectors.a(state) }),
}))();

For more details, see buildAdapter.

Example: Auth

tsx
import { joinAdapters, createAdapter } from '@state-adapt/core';

interface AuthState {
  username: string | null;
  password: string | null;
  isLoggedIn: boolean;
}

const authAdapter = joinAdapters<AuthState>()({
  username: createAdapter<string | null>()({}),
  password: createAdapter<string | null>()({}),
  isLoggedIn: createAdapter<boolean>()({
    login: () => true,
    logout: () => false,
  }),
})();

// Usage
const initialState = { username: null, password: null, isLoggedIn: false };
const newState = authAdapter.update(initialState, {
  username: 'bob',
  password: '1234',
});
// { username: 'bob', password '1234', isLoggedIn: false }

Example: Cookies

tsx
import { joinAdapters, createAdapter } from '@state-adapt/core';

interface CookieState {
  price: number;
  flavor: 'Chocolate Chip' | 'Oatmeal Raisin';
}

const cookieAdapter = joinAdapters<CookieState>()({
  price: createAdapter<number>()({
    selectors: {
      discounted: state => state * 0.9,
    },
  }),
  flavor: createAdapter<Flavor>()({
    setToChocolateChip: () => 'Chocolate Chip',
    setToOatmealRaisin: () => 'Oatmeal Raisin',
  }),
})();

interface CookiesState {
  favorite: CookieState;
  leastFavorite: CookieState;
}
const initialCookiesState: CookiesState = {
  favorite: {
    price: 200,
    flavor: 'Chocolate Chip',
  },
  leastFavorite: {
    price: 190,
    flavor: 'Oatmeal Raisin',
  },
};

const cookiesAdapter = joinAdapters<CookiesState>()({
  favorite: cookieAdapter,
  leastFavorite: cookieAdapter,
})({
  totalPrice: s => s.favorite.price + s.leastFavorite.price,
})({
  totalPriceDiscounted: s => s.totalPrice * 0.9,
})();

// Usage
cookiesAdapter.setFavoriteToOatmealRaisin(initialCookiesState);
cookiesAdapter.setLeastFavoriteToOatmealRaisin(initialCookiesState);
const favoritePriceDiscounted =
  cookiesAdapter.selectors.favoritePriceDiscounted(initialCookiesState);
const totalPrice = cookiesAdapter.selectors.totalPrice(initialCookiesState);
const totalPriceDiscounted =
  cookiesAdapter.selectors.totalPriceDiscounted(initialCookiesState);

Example: Olympic Sports

tsx
import { joinAdapters, createAdapter } from '@state-adapt/core';

interface SportState {
  name: string;
  isOlympic: boolean;
}

const sportAdapter = joinAdapters<SportState>()({
  name: createAdapter<string>()({
    setToSoccer: () => 'soccer',
    setToBasketball: () => 'basketball',
  }),
  isOlympic: createAdapter<boolean>()({
    setToTrue: () => true,
    setToFalse: () => false,
  }),
})({
  isOlympicAndSoccer: s => s.isOlympic && s.name === 'soccer',
  isOlympicAndBasketball: s => s.isOlympic && s.name === 'basketball',
})({
  setToOlympicSoccer: {
    name: () => 'soccer',
    isOlympic: () => true,
  },
})();

Type Parameters

ParentState

ParentState extends Record<string, any>

ExcludedProps

ExcludedProps extends string = ""

Returns

<AE>(adapterEntries): NewBlockAdder<ParentState, FlattendAdapters<AE, ParentState> extends object ? S extends Selectors<ParentState> ? WithStateSelector<ParentState, S<S>> : WithStateSelector<ParentState, Record<string, (state) => any>> : object, ReactionsWithoutSelectors<ParentState, FlattendAdapters<AE, ParentState> & BasicAdapterMethods<ParentState> & WithUpdateReaction<ParentState>>>

Type Parameters

AE

AE extends AdapterEntries<Omit<ParentState, ExcludedProps>>

Parameters

adapterEntries

AE

Returns

NewBlockAdder<ParentState, FlattendAdapters<AE, ParentState> extends object ? S extends Selectors<ParentState> ? WithStateSelector<ParentState, S<S>> : WithStateSelector<ParentState, Record<string, (state) => any>> : object, ReactionsWithoutSelectors<ParentState, FlattendAdapters<AE, ParentState> & BasicAdapterMethods<ParentState> & WithUpdateReaction<ParentState>>>