react-bindings
Data Bindings for React
A binding is a wrapper type that can be used to read, write, and observe changes in a source of truth.
This package provides an implementation of bindings that works well with React and React Native. It also provides a few convenient tools for working with these bindings.
Basic Example
In the following example, we demonstrate creating, reading, and updating a binding as well as observing it for changes in two ways:
- using
useBindingEffect
, which triggers a callback whenever the associated bindings change - using
BindingsConsumer
, which runs a render function whenever the associated bindings change
Try it Out – CodeSandbox
import React from 'react';
import { BindingsConsumer, useBinding, useBindingEffect } from 'react-bindings';
export const MyComponent = () => {
const myBinding = useBinding(() => 0, { id: 'myBinding' });
console.log('myBinding', myBinding.get());
myBinding.set(1);
console.log('myBinding', myBinding.get());
useBindingEffect({ myBinding }, ({ myBinding }) => {
console.log('myBinding', myBinding);
});
const onIncClick = () => myBinding.set(myBinding.get() + 1);
return (
<div>
myBinding:
<BindingsConsumer bindings={{ myBinding }}>{({ myBinding }) => myBinding}</BindingsConsumer>
<button onClick={onIncClick}>Increment</button>
</div>
);
};
Things become a lot more interesting when you start passing bindings around, including them in React contexts, and dealing with asynchronous updates.
Bindings provide some additional functionality by default, such as locking, and they're also extensible, as we'll see more later. But, be sure to check out the API docs for more complete details.
For cases where you want to observe but not modify, you can use the ReadonlyBinding
interface. This package also provides three specialized forms of readonly binding:
- derived bindings - bindings derived from zero or more other bindings
- const bindings - good for forcing non-binding wrapped data into a binding, for interfaces that expect such
- flattened bindings - bindings derived from bindings that point to other bindings
Another Example
In the following example, we make a component that is updated with a new random number every second. It displays the number as well as whether or not the number is even.
Try it Out – CodeSandbox
import React, { useEffect } from 'react';
import { Binding, BindingsConsumer, useBinding, useDerivedBinding } from 'react-bindings';
const streamRandomNumbers = (binding: Binding<number>) => {
const next = () => {
binding.set(Math.floor(Math.random() * 100));
timeout = setTimeout(next, 1000);
};
let timeout = setTimeout(next, 1000);
next();
return () => {
clearTimeout(timeout);
};
};
export const MyComponent = () => {
const myBinding = useBinding(() => 0, { id: 'myBinding', detectChanges: true });
const isEven = useDerivedBinding({ myBinding }, ({ myBinding }) => myBinding % 2 === 0, { id: 'isEven' });
useEffect(() => {
const stop = streamRandomNumbers(myBinding);
return stop;
});
return (
<div>
<BindingsConsumer bindings={{ myBinding }}>{({ myBinding }) => myBinding}</BindingsConsumer>
is even:
<BindingsConsumer bindings={{ isEven }}>{({ isEven }) => (isEven ? 'true' : 'false')}</BindingsConsumer>
</div>
);
};
In the above example, we could have also chosen to set limitType: 'none'
on the isEven
derived binding, which would change the limiter from the default, which is 'debounce'
, so that propagation of changes from myBinding
to isEven
would happen immediately. However, it's generally better to only do that when it's critical that data be in sync because too many chained updates could lead to reduced frame rates and laggy user interactions.
Extensibility Example
To demonstrate binding extensibility, let's revise the above example so that the isEven
function is a property of myBinding
directly, instead of being a derived binding.
Try it Out – CodeSandbox
export const MyComponent = () => {
const myBinding = useBinding(() => 0, {
id: 'myBinding',
detectChanges: true,
addFields: (b) => ({
isEven: () => b.get() % 2 === 0
})
});
useEffect(() => streamRandomNumbers(myBinding));
return (
<div>
<BindingsConsumer bindings={myBinding}>
{() => (
<>
{myBinding.get()}
is even:
{myBinding.isEven() ? 'true' : 'false'}
</>
)}
</BindingsConsumer>
</div>
);
};
Thanks
Thanks for checking it out. Feel free to create issues or otherwise provide feedback.
API Docs
Be sure to check out our other TypeScript OSS projects as well.