Reactions

Reactions allow you to respond to state changes outside the normal rendering flow. This guide covers common usage patterns for reactions in different scenarios.

When to Use Reactions∞ 

Reactions are useful for:

  • Running side effects when state changes
  • Triggering sequences in response to state updates
  • Managing focus, scroll position, or other DOM operations
  • Syncing state changes with external systems

Reactions in Modules∞ 

When creating a module, you can attach reactions that respond to state changes:

// modules/admin.js
import { Reaction } from 'cerebral'
import { state, sequences } from 'cerebral'

// Define reactions in a separate file
export const watchPermissions = Reaction(
  { permissions: state`user.permissions` },
  ({ permissions, get }) => {
    if (!permissions.includes('admin')) {
      get(sequences`redirectToHome`)()
    }
  }
)

This pattern allows you to define side effects that run automatically when specific state changes.

Reactions in Components∞ 

Components often need to react to state changes for side effects like focus management:

import * as React from 'react'
import { connect } from '@cerebral/react'
import { state, sequences } from 'cerebral'

export default connect(
  {
    inputValue: state`form.inputValue`,
    changeInputValue: sequences`changeInputValue`
  },
  function FormInput({ inputValue, changeInputValue, reaction }) {
    const inputRef = React.useRef(null)

    // The reaction prop is provided by the connect HOC
    reaction(
      'focusOnError', // Name for debugging
      { error: state`form.error` },
      ({ error }) => {
        if (error && inputRef.current) {
          inputRef.current.focus()
        }
      }
    )

    return (
      <input
        ref={inputRef}
        value={inputValue}
        onChange={(event) => changeInputValue({ value: event.target.value })}
      />
    )
  }
)

Advanced Usage Patterns∞ 

Computed-Based Reactions∞ 

Reactions can depend on computed values:

import { Reaction } from 'cerebral'
import { state } from 'cerebral'

// Define a computed value
export const filteredItems = (get) => {
  const items = get(state`items`)
  const filter = get(state`filter`)
  return items.filter((item) => item.type === filter)
}

// Create app state with the computed
const appState = {
  items: [],
  filter: 'all',
  filteredItems
}

// Create a reaction to the computed
export const onFilteredItemsChange = Reaction(
  { filteredItems: state`filteredItems` },
  ({ filteredItems }) => {
    console.log('Filtered items changed:', filteredItems.length)
  }
)

Accessing Additional State∞ 

Reactions provide a get function to access state not declared in dependencies:

Reaction({ isLoggedIn: state`user.isLoggedIn` }, ({ isLoggedIn, get }) => {
  if (isLoggedIn) {
    // Only access these values when needed
    const username = get(state`user.name`)
    const permissions = get(state`user.permissions`)

    // Use the dynamically accessed values
    analytics.identify(username, { permissions })
  }
})

Reacting to Multiple State Changes∞ 

A single reaction can depend on multiple state paths:

Reaction(
  {
    filter: state`items.filter`,
    sort: state`items.sort`,
    items: state`items.list`
  },
  ({ filter, sort, items }) => {
    // This runs when filter, sort, or items change
    const processed = processItems(items, filter, sort)
    localStorage.setItem('processedItems', JSON.stringify(processed))
  }
)