SkillAgentSearch skills...

Focal

Program user interfaces the FRP way.

Install / Use

/learn @grammarly/Focal
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<a href="http://github.com/grammarly/focal"><img src="https://raw.githubusercontent.com/grammarly/focal/master/logo/logo.png" alt="Focal 🔍" height="100"></a>

Build Status npm version Apache 2.0 license

Type safe, expressive and composable state management for React applications.

  • Represent the whole application state as an immutable and observable single source of truth.
  • Seamlessly embed observables into React components' layout.
  • Leverage the power of Rx.JS (and observables in general) to enrich and combine parts of application state, explicitly controlling the data flow.
  • Use lenses to decompose the application state into smaller parts, so you can isolate UI components in a clean way and manipulate application state effortlessly.
  • Write less code that is easier to understand.

Packages

@grammarly/focal - Type safe, expressive and composable state management for React applications. @grammarly/focal-atom - Type safe, expressive and composable state management for any application.

Example

Here's a typical example of a 'counter' UI component and how it fits within the whole application:

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import {
  Atom,
  // this is the special namespace with React components that accept
  // observable values in their props
  F
} from '@grammarly/focal'

// our counter UI component
const Counter = (props: { count: Atom<number> }) =>
  <F.div>
    {/* use observable state directly in JSX */}
    You have clicked this button {props.count} time(s).

    <button
      onClick={() =>
        // update the counter state on click
        props.count.modify(x => x + 1)
      }
    >
      Click again?
    </button>
  </F.div>

// the main 'app' UI component
const App = (props: { state: Atom<{ count: number }> }) =>
  <div>
    Hello, world!
    <Counter
      count={
        // take the app state and lens into its part where the
        // counter's state lies.
        //
        // this creates an atom which you can write to, in a type safe way.
        props.state.lens('count')
      }
    />
  </div>

// create the app state atom
const state = Atom.create({ count: 0 })

// track any changes to the app's state and log them to console
state.subscribe(x => {
  console.log(`New app state: ${JSON.stringify(x)}`)
})

// render the app
const root = createRoot(document.getElementById('app'))
root.render(<App state={state} />)

You can play with this example online on CodeSandbox.

There's also a more elaborate version of this example, as well as some other examples, in the examples directory.

Installation

yarn add @grammarly/focal-atom @grammarly/focal
# or
yarn add @grammarly/focal-atom

or

npm install --save @grammarly/focal-atom @grammarly/focal
# or
npm install --save @grammarly/focal-atom

It is important to satisfy the RxJS peer dependency (required for instanceof Observable tests to work correctly).

Also note, that for npm-based packages you will need npm 3.x. For Focal to work properly, you need to:

  • have the same version of RxJS installed in your package (listed as a peer dependency in Focal)
  • have RxJS installed in an npm 3.x way so that it is not duplicated in your app's node_modules and Focal's node_modules

Tutorial

The example above might be a bit too overwhelming. Let's go over the main concepts bit by bit.

Reactive variables

In Focal, state is stored in Atom<T>s. Atom<T> is a data cell that holds a single immutable value, which you can read and write to:

import { Atom } from '@grammarly/focal-atom'

// create an Atom<number> with initial value of 0
const count = Atom.create(0)

// output the current value
console.log(count.get())
// => 0

// set 5 as the new value
count.set(5)

console.log(count.get())
// => 5

// modify the value: set a new value which is based on current value
count.modify(x => x + 1)

console.log(count.get())
// => 6

You can also track (get notified of) changes that happen to an Atom<T>'s value. In this sense, an Atom<T> can be thought of as a reactive variable:

import { Atom } from '@grammarly/focal-atom'

const count = Atom.create(0)

// subscribe to changes of count's value, outputting a new value to the
// console each time
// NOTE how this will immediately output the current value
count.subscribe(x => {
  console.log(x)
})
// => 0

console.log(count.get())
// => 0

// set a new value – it will get written to the console output
count.set(5)
// => 5

count.modify(x => x + 1)
// => 6

Atom properties

Every atom is expected to satisfy these properties:

  1. When .subscribed to, emit the current value immediately.
  2. Don't emit a value if it is equal to the current value.

Single source of truth

Atom<T>s are used as a source of application state in Focal. There are more than one way in which you can create an Atom<T>, with Atom.create being the one that you use to create the application state root. Ideally, we want for the application state to come from a single source of truth. (We will talk about managing application state in this fashion below).

Although you can create as many Atom<T>s through Atom.create as you need, it generally should be avoided. The problem with having several (or many) sources of application state is that you may end up with different sorts of dependencies between these state sources, and there is no way to update several Atom<T>s at the same time. This can lead to inconsistency between the parts of your application state.

Data binding

We have learned how to create, change and track application state. Now, for it to be useful in a React user interface, we need a way to display this data.

Focal allows you to directly embed Atom<T>s in JSX code. In practice, this works similar to data binding in frameworks like Angular. There are differences, though:

  • In Focal, you use regular code (TypeScript or JavaScript), and not a template engine syntax (like in Vue.js), to describe your data. There's no magic happening at the syntax level, so you can use all the standard language tools that you already have.
  • Since Focal data bindings are ordinary TypeScript (or JavaScript) expressions, you can continue using the same IDE features like autocompletion, go to definition, rename refactoring, find usages, etc. This makes UI layout code maintenance easier compared to a template engine.
  • You can also take advantage of your existing static analysis tools (e.g. the type checking of TypeScript compiler). This way, your UI code can be just as reliable as any other code.
  • The change of data (an Atom<T>) triggers component render, and not the other way around (e.g. when component render triggers data evaluation). You also usually don't think about when a component should get rendered – this is handled automatically.

Let's see what it looks like in code:

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { F, Atom } from '@grammarly/focal'

// create our state
const count = Atom.create(0)

// define a stateless React component that will take
// an Atom<number> in its props
const Counter = (props: { count: Atom<number> }) =>
  <F.div>
    {/* embed the state atom directly in JSX */}
    Count: {count}
  </F.div>

// mount the component onto DOM
const root = createRoot(document.getElementById('test'))
root.render(<Counter count={count} />)

// => <div>Count: 0</div>

How is this different from regular React?

Instead of using a <div />, we used an <F.div />. In React, you can already embed code in JSX, but it's mostly restricted to things that can be converted to a string and other React elements.

F-components are different. F is a namespace of the so-called lifted components that mirror React's intrinsic components, but which can also accept Atom<T>s (additionally to what React already allows) in their props.

Recall that a React JSX element child content gets interpreted as the children prop, so this means that Focal also supports embedding Atom<T>s in component child content – that's what we did.

Now let's get the application state to update:

// This line below will modify the current value of the `count` atom,
// which we used in the `Counter` component. Modifying the state which was
// used in a component will make the component update:
count.set(5)

// => <div>Count: 5</div>

You may have noticed that we didn't update any React component state, yet the <Counter /> component somehow has new content now. In fact, as far as React is concerned, neither props nor state of the <Counter /> component have changed, so this component is not rendered when the counter state is changed.

This content update is handled in the <F.div /> component, which is also true for all lifted (a.k.a. F) components. An F-component will .subscribe to all of its Atom<T> props and render every time a prop value has changed.

So technically, while the <Counter /> is not rendered when the count's value is changed, its child <F.div /> is rendered.

Now let's make our counter component a bit more complex:

// a spiced up version of the counter component
const Counter = (props: { count: Atom<number> }) =>
  <F.div>
    Count: {count}.
    {/* say whether the count number is odd

Related Skills

View on GitHub
GitHub Stars729
CategoryDevelopment
Updated20d ago
Forks38

Languages

TypeScript

Security Score

100/100

Audited on Mar 6, 2026

No findings