Refract
  • Read Me
  • Introduction
    • Why Refract?
    • Core Concepts
    • Thinking In Refract
    • Alternatives
  • Usage
    • Getting Started
    • Installation
    • Connecting To React
    • Observing React, Preact and Inferno
    • Injecting Dependencies
    • Observing Redux
    • Observing Anything
    • Handling Effects
    • Pushing to Props
    • Rendering Components
    • Handling Errors
    • Testing
  • Recipes
    • Dependency Injection
    • Creating An API Dependency
    • Handling state
    • Replacing Redux Connect
  • Examples
    • Debounced fetch
    • Counter
    • Field validation
    • Redux fetch
    • Routing
    • Typeahead
    • Visit time
  • API
    • compose
    • withEffects
    • toProps, asProps
    • useRefract
    • refractEnhancer (Redux)
  • Glossary
  • Feedback
Powered by GitBook
On this page
  • Source of truth
  • Stateful apertures
  • Using context
  1. Recipes

Handling state

PreviousCreating An API DependencyNextReplacing Redux Connect

Last updated 6 years ago

State in React is a never-ending story: you can use Redux, vanilla local state, higher-order components built on top of local state, use the new context API...

We believe state strongly coupled to components is best colocated: it is easier to reason about, easier to maintain and enables you to cleanly isolate sections of an application. Refract makes it possible to handle local state reactively, and it might even challenge some of your assumptions.

Source of truth

Redux popularised state as a source of truth: in a Redux app, state is indeed the source of truth of your view, because a hard separation is enforced. However, actions (which are like events) are the source of truth of your reducers and therefore state.

In any application, events are the source of truth! Any application projects them (reduces them if you prefer!) to state. And state can be represented in different ways: an object, or a React element.

A counter for instance will have 'click' events resulting in a value being incremented ({count: 1}) and rendered <span>{count}</span>: from event, to representation.

Stateful apertures

Refract can observe "events" (see ) and has built-in effects: to and to ; it allows you to handle the different transformation steps, from events to presentation, in one place.

The example below is a very basic example of a toggle. We have a Toggle component which can reveal content (children).

import React from 'react'

export default function Toggle({ isExpanded, toggle, children }) {
    return (
        <div>
            <button onClick={toggle}>{isExpanded ? 'Hide' : 'Reveal'}</button>

            {isExpanded ? children : null}
        </div>
    )
}

We wrap this component with Refract, so we can provide the toggle callback, and map state to props.

import React from 'react'
import { withEffects, toProps } from 'refract-rxjs'
import { scan, startWith, map } from 'rxjs/operators'
import Toggle from './Toggle'

const aperture = component => {
    const [toggleEvents$, toggle] = component.useEvent('toggle')

    return toggleEvents$.pipe(
        startWith({
            toggle,
            isExpanded: false
        }),
        scan(props => ({
            ...props,
            isExpanded: !props.isExpanded
        })),
        map(toProps)
    )
}

const ToggleWithState = withEffects(aperture)(Toggle)

export default ToggleWithState

Using context

When handling local state, it can be cumbersome to have to pass props through several layers of components (this is known as "prop drilling"). React's new context API can help with it, and you can leverage it with Refract by using rendering effects.

The example below puts a counter state in context. In the aperture, we create a context object from 'count' events and combine them with the values of children received to set the value of a context provider.

import React from 'react'
import { withEffects } from 'refract-rxjs'
import { combineLatest } from 'rxjs'
import { scan, startWith, map } from 'rxjs/operators'

const { Provider, Consumer: CounterStateConsumer } = React.createContext({})

const aperture = (component, initialProps) => {
    const children$ = component.observe('children')

    const [countEvents$, countUp] = component.useEvent('count')
    const context$ = countEvents$.pipe(
        startWith({
            countUp,
            count: initialProps.initialCount
        }),
        scan(context => ({
            ...context,
            count: context.count + 1
        }))
    )

    return combineLatest(context$, children$).pipe(
        map(([context, children]) => (
            <Provider value={context}>{children}</Provider>
        ))
    )
}

const CounterStateProvider = withEffects(aperture)()

export { CounterStateProvider, CounterStateConsumer }
Observing React
map and replace props
render components