createUseModel
createUseModel is a factory function that creates a single hook combining both selectors and actions for a given model. Unlike createUseSelectors and createUseActions which return all selectors or actions, createUseModel lets you pick only the keys you need per component. It also automatically provides loading state for effect actions.
Importing
To use createUseModel, import it from @blue-functor/remodel:
import { createUseModel } from '@blue-functor/remodel';Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
options.name | string | Yes | A name for the model (used for identification). |
options.selectors | Record<string, (state: any) => any> | Yes | An object containing selector functions to retrieve state slices. |
options.actions | Record<string, ActionCreator> | Yes | An object containing action creators (regular or effect actions). |
Returns
A custom React hook with two call signatures:
Selectors only:
const { mode } = useModel(['mode']);Selectors + Actions:
const { mode, setMode } = useModel(['mode'], ['setMode']);The returned object contains:
- Selected state values (inferred from your selector return types)
- Bound action creators (automatically dispatched when called)
{actionName}Loadingboolean keys for any effect actions in the action array
Example Usage
Defining the Hook
import { createUseModel, createUseSelectors, createUseActions } from '@blue-functor/remodel';
import * as actions from './actions';
import * as selectors from './selectors';
export const useUiModel = createUseModel({ name: 'ui', selectors, actions });
// These still work alongside createUseModel
export const useUiSelectors = createUseSelectors(selectors);
export const useUiActions = createUseActions(actions);Selectors Only
import React from 'react';
import { useUiModel } from '@/models/ui';
const ModeDisplay: React.FC = () => {
const { mode } = useUiModel(['mode']);
return <p>Current Mode: {mode ? 'Enabled' : 'Disabled'}</p>;
};
export default ModeDisplay;Selectors + Actions
import React from 'react';
import { useUiModel } from '@/models/ui';
const ModeToggle: React.FC = () => {
const { mode, setMode, setModeEffect, setModeEffectLoading } = useUiModel(
['mode'],
['setMode', 'setModeEffect']
);
return (
<div>
<p>Current Mode: {mode ? 'Enabled' : 'Disabled'}</p>
<button onClick={() => setMode(!mode)}>
Toggle (sync)
</button>
<button
onClick={() => setModeEffect({ mode: !mode })}
disabled={setModeEffectLoading}
>
{setModeEffectLoading ? 'Loading...' : 'Toggle (async)'}
</button>
</div>
);
};
export default ModeToggle;In this example:
modeis a selected state value.setModeis a regular action — calling it dispatches immediately.setModeEffectis an effect action — it triggers an async operation.setModeEffectLoadingis automatically generated becausesetModeEffectwas created withcreateEffectAction. It istruewhile the effect is in flight andfalseonce.succeededor.failedis dispatched.
Effect Action Loading
When you include an effect action in the actions array, the hook automatically adds a {name}Loading boolean key to the returned object:
// Given these actions:
const setMode = createAction<boolean>('SET_MODE'); // regular action
const fetchUser = createEffectAction<string, User, Error>('FETCH'); // effect action
// The hook return includes:
const { mode, setMode, fetchUser, fetchUserLoading } = useModel(
['mode'],
['setMode', 'fetchUser']
);
// setMode → no loading key (regular action)
// fetchUser → fetchUserLoading: boolean (effect action)Loading state is read from state.effect.loading — the same system reducer used by useLoading. No additional setup is required.
Type Safety
The hook provides full TypeScript inference:
- Selector keys are autocompleted from the keys of your
selectorsobject - Action keys are autocompleted from the keys of your
actionsobject - Return types are inferred from selector return types and action creator signatures
- Loading keys are only generated for effect actions at the type level
- Duplicate keys are rejected at compile time
// Full autocomplete and type inference
const { mode } = useUiModel(['mode']);
// ^? boolean (inferred from selector return type)
// Type errors for invalid keys
useUiModel(['nonExistent']); // Error: not a valid selector key
useUiModel(['mode'], ['oops']); // Error: not a valid action key
// Duplicate keys are caught at compile time
useUiModel(['mode', 'mode']); // Error: duplicate keyHow It Works
-
Factory Time (runs once, outside React):
- Pre-computes which actions are effect actions by checking for
.succeededand.failedproperties. - Builds a map of action keys to their type strings for efficient loading state lookups.
- Pre-computes which actions are effect actions by checking for
-
Hook Render Time:
- Uses a single
useSelectorcall withshallowEqualto select both state values and loading states in one pass — one subscription, one equality check. - Bound action creators are memoized and only recreated if
dispatchchanges. - Inline array literals (e.g.,
['mode']) are stabilized withuseRefto prevent unnecessary recomputations across renders.
- Uses a single
Comparison with Other Hooks
| Feature | createUseModel | createUseSelectors + createUseActions | withProps |
|---|---|---|---|
| Pick specific keys | Yes | No (returns all) | Yes |
| Auto loading state | Yes | No (use useLoading) | No |
| Single hook call | Yes | Two separate hooks | N/A (HOC) |
| Type inference | Full | Full | Requires manual Props interface |
| Duplicate key prevention | Yes | N/A | N/A |
| Works with hooks | Yes | Yes | No (class + function components) |
Best Practices
Do
// Pick only what you need per component
const { mode } = useUiModel(['mode']);
// Combine selectors and actions in one call
const { mode, setMode } = useUiModel(['mode'], ['setMode']);
// Use loading state for UI feedback
const { fetchUser, fetchUserLoading } = useModel([], ['fetchUser']);Don’t
// Don't select everything if you only need one value
const { mode, error, userName } = useUiModel(['mode', 'error', 'userName']);
// If you only use `mode`, the component re-renders when error/userName change too
// Don't mix createUseModel with useLoading for the same action
// createUseModel already provides loading state
const { fetchUser, fetchUserLoading } = useModel([], ['fetchUser']);
const isLoading = useLoading(fetchUser); // RedundantNotes
-
Coexists with existing hooks:
createUseModelworks alongsidecreateUseSelectorsandcreateUseActions. You can adopt it incrementally without rewriting existing code. -
Performance: Uses a single
useSelectorsubscription withshallowEqualcomparison, so the component only re-renders when selected values actually change. -
React Native Compatible: Works in both React web and React Native applications.
By using createUseModel, you get a single hook that combines state selection, action dispatching, and loading state tracking with full type safety and minimal boilerplate.