Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Reactivity utilities
  2. afterRecomputation

afterRecomputation#

Runs a callback after a recomputation has settled, rather than in the middle of one.

Use it when you need to act on a value once the reactive graph is stable — scrolling to a freshly rendered row, measuring after a list changed. Not for ordinary derivation, which belongs in a computed.

Overview#

This function binds queries, mutations, and async methods to sources for automatic execution by:

  • Listening to source emissions and computing new values

  • Providing a readonly source suitable for method binding

  • Maintaining reactivity through the effect system

  • Enabling source-based triggering patterns

Signature#

typescript
function afterRecomputation<State, SourceType>(
  _source: Source<SourceType>,
  callback: (source: SourceType) => State,
): ReadonlySource<State>;

Parameters#

  • _source - The source to listen to. When this source emits, the callback is invoked.

  • callback - Function that transforms source values. Receives the emitted value and returns the transformed result.

Returns#

A readonly source that emits transformed values. Can be used as the method parameter in queries, mutations, and async methods.

Primary Use Case#

Bind queries/mutations/async methods to sources for automatic execution:

typescript
method: afterRecomputation(mySource, (data) => data);

This pattern makes queries/mutations execute automatically when the source emits.

Execution Flow#

  1. Source emits a value via source.set(value)

  2. afterRecomputation callback transforms the value

  3. Resulting readonly source emits the transformed value

  4. Bound query/mutation/async method executes with the new value

Difference from computedSource#

  • afterRecomputation: Designed for binding to method parameters

  • computedSource: General-purpose source transformation

  • Both transform source values, but afterRecomputation is optimized for method binding

Common Patterns#

  • Identity transformation: afterRecomputation(source, (x) => x) - pass value through

  • Field extraction: afterRecomputation(source, (data) => data.id) - extract specific field

  • Validation: afterRecomputation(source, (data) => validate(data)) - transform and validate

  • Mapping: afterRecomputation(source, (data) => mapToDto(data)) - convert to different type

Examples#

Binding a query to a source for automatic execution#

typescript
import { afterRecomputation, query, source$ } from '@craft-ts/core';

const userIdChange = source$<string>('userIdChange');
const { user } = query('user', {
  method: afterRecomputation(userIdChange, (userId) => userId),
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params}`);
    return response.json();
  },
});

// Query executes automatically when source emits
userIdChange.emit('user-123');
// -> query loader executes with params 'user-123'

userIdChange.emit('user-456');
// -> query loader executes again with params 'user-456'

Binding a mutation to a source#

typescript
import { afterRecomputation, mutation, source$ } from '@craft-ts/core';

const submitForm = source$<{ name: string; email: string }>();
const { submit } = mutation('submit', {
  method: afterRecomputation(submitForm, (formData) => formData),
  loader: async ({ params }) => {
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(params),
    });
    return response.json();
  },
});

// Mutation executes automatically when source emits
submitForm.emit({ name: 'John', email: 'john@example.com' });
// -> mutation loader executes with form data
// Note: No submit.mutate(...) call is needed here

Binding async method to a source#

typescript
import { afterRecomputation, asyncProcess, source$ } from '@craft-ts/core';

const searchInput = source$<string>('searchInput');
const { search } = asyncProcess('search', {
  method: afterRecomputation(searchInput, (term) => term),
  loader: async ({ params }) => {
    // Debounce at source level before setting
    const response = await fetch(`/api/search?q=${params}`);
    return response.json();
  },
});

// Async method executes automatically
searchInput.emit('query');
// -> search loader executes

Extracting specific field from complex data#

typescript
type FormData = {
  user: { id: string; name: string };
  address: { city: string };
};

import { afterRecomputation, mutation, source$ } from '@craft-ts/core';

const formSubmit = source$<FormData>('formSubmit');
const { updateUser } = mutation('updateUser', {
  // Extract only user data
  method: afterRecomputation(formSubmit, (data) => data.user),
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.id}`, {
      method: 'PATCH',
      body: JSON.stringify(params),
    });
    return response.json();
  },
});

// Only user data is passed to mutation
formSubmit.emit({
  user: { id: 'user-1', name: 'John' },
  address: { city: 'NYC' },
});
// -> mutation receives only { id: 'user-1', name: 'John' }

Transforming data before execution#

typescript
import { afterRecomputation, query, source$ } from '@craft-ts/core';

const searchParams = source$<{ query: string; filters: string[] }>();
const { results } = query('results', {
  method: afterRecomputation(searchParams, (params) => ({
    q: params.query.trim().toLowerCase(),
    f: params.filters.join(','),
  })),
  loader: async ({ params }) => {
    const queryString = new URLSearchParams(params);
    const response = await fetch(`/api/search?${queryString}`);
    return response.json();
  },
});

// Data is transformed before query execution
searchParams.emit({
  query: '  craft  ',
  filters: ['tutorial', 'advanced'],
});
// -> query receives { q: 'craft', f: 'tutorial,advanced' }

Validation and type narrowing#

typescript
import { afterRecomputation, asyncProcess, source$ } from '@craft-ts/core';

const inputChange = source$<string>('inputChange');
const { validate } = asyncProcess('validate', {
  method: afterRecomputation(inputChange, (input) => {
    // Only proceed if input is valid
    const trimmed = input.trim();
    if (trimmed.length < 3) {
      throw new Error('Input too short');
    }
    return trimmed;
  }),
  loader: async ({ params }) => {
    const response = await fetch('/api/validate', {
      method: 'POST',
      body: JSON.stringify({ input: params }),
    });
    return response.json();
  },
});

// Invalid input throws error in callback
inputChange.emit('ab'); // Error: Input too short

// Valid input proceeds
inputChange.emit('valid input'); // Validation executes

Multiple sources with different transformations#

typescript
import { afterRecomputation, query, source$ } from '@craft-ts/core';

const quickSearch = source$<string>('quickSearch');
const advancedSearch = source$<{ query: string; options: unknown }>();

const { quickResults } = query('quickResults', {
  method: afterRecomputation(quickSearch, (term) => ({
    query: term,
    mode: 'quick',
  })),
  loader: async ({ params }) => {
    const response = await fetch('/api/search', {
      method: 'POST',
      body: JSON.stringify(params),
    });
    return response.json();
  },
});

const { advancedResults } = query('advancedResults', {
  method: afterRecomputation(advancedSearch, ({ query, options }) => ({
    query,
    options,
    mode: 'advanced',
  })),
  loader: async ({ params }) => {
    const response = await fetch('/api/search/advanced', {
      method: 'POST',
      body: JSON.stringify(params),
    });
    return response.json();
  },
});

// Quick search with simple string
quickSearch.emit('craft');
// -> query receives { query: 'craft', mode: 'quick' }

advancedSearch.emit({
  query: 'craft',
  options: { tags: ['signals'] },
});
// -> query receives { query: 'craft', options: { ... }, mode: 'advanced' }

Identity transformation (pass-through)#

typescript
import { afterRecomputation, mutation, source$ } from '@craft-ts/core';

const dataUpdate = source$<{ id: string; payload: unknown }>();
const { update } = mutation('update', {
  // Pass data through unchanged
  method: afterRecomputation(dataUpdate, (data) => data),
  loader: async ({ params }) => {
    const response = await fetch(`/api/data/${params.id}`, {
      method: 'PUT',
      body: JSON.stringify(params.payload),
    });
    return response.json();
  },
});

// Data passed through unchanged
dataUpdate.emit({ id: 'item-1', payload: { value: 123 } });
// -> mutation receives exact same object

See Also#

  • craftEffect

  • source$

←sourceFromEventType-safe i18n→

On this page

  • Overview
  • Signature
  • Parameters
  • Returns
  • Primary Use Case
  • Execution Flow
  • Difference from computedSource
  • Common Patterns
  • Examples
  • Binding a query to a source for automatic execution
  • Binding a mutation to a source
  • Binding async method to a source
  • Extracting specific field from complex data
  • Transforming data before execution
  • Validation and type narrowing
  • Multiple sources with different transformations
  • Identity transformation (pass-through)
  • See Also

@craft-ts · MIT licensed

GitHubnpm

Search