0.4.1 • Published 1 year ago
@mdekrey/jotai-react-signals v0.4.1
React Signals with Jotai
This provides a simple way to bind Jotai atoms to React intrinsic elements to get reactive pages, like the signals paradigm.
Please note that the current state of this package is a proof-of-concept; while it works quite well, there is no live demo and the API may change significantly.
Included in this package:
- Hooks for more easily working with computed Jotai state
useComputedAtom
- same interface as Jotai'scomputed
, but as a React hook with no dependenciesuseAsAtom
- converts aT | Atom<T>
to anAtom<T>
as a React hook, so your APIs can be flexible.
- Utilities for animation
animationSignal
- provides updates perrequestAnimationFrame
(must subscribe to theanimationSignal
or callmanuallyUpdateAnimationFrame
)manuallyUpdateAnimationFrame
- function to manually trigger animationSignal updates when not subscribed (this prety well defeats the purpose of animations...)tweenedSignal
- tweens anAtom<number>
via anEasingFunction
(not provided, but see tween.js'sEasing
export for examples) using theanimationSignal
- Utilities for binding to elements
withSignal
- takes the name of a React intrinsic element and a mapping of React props to functions to update the element to create a new component that will update your element in real-time.mapProperty
- helper function for mapping a value to an element propertymapAttribute
- helper function for mapping a value to an element attribute
Usage example, as a Storybook entry:
import { Story, Meta } from '@storybook/react';
import { useMemo } from 'react';
import { useStore } from 'jotai';
import {
tweenedSignal,
useComputedAtom,
mapAttribute,
type PartialMapping,
withSignal,
useAsAtom,
} from '@mdekrey/jotai-react-signals';
import { Easing } from '@tweenjs/tween.js';
const sampleMapping = {
cx: mapAttribute('cx'),
cy: mapAttribute('cy'),
r: mapAttribute('r'),
strokeWidth: mapAttribute('stroke-width'),
} satisfies PartialMapping;
const AnimatedCircle = withSignal('circle', sampleMapping);
type Props = {
size: number;
};
export default {
title: 'Example/Jotai Signals',
argTypes: {
size: {
control: {
type: 'number',
},
},
},
parameters: {},
} as Meta<Props>;
const Template: Story<Props> = (args) => {
const store = useStore();
const size$ = useAsAtom(args.size);
const tweenedSize$ = useMemo(
() => tweenedSignal(store, size$, Easing.Quadratic.Out),
[store, size$]
);
const strokeWidth$ = useComputedAtom((get) => get(tweenedSize$) / 15);
return (
<svg width="300px" height="300px">
<AnimatedCircle
cx={150}
cy={150}
r={atom((get) => get(tweenedSize$).toFixed(3))}
strokeWidth={atom((get) => get(strokeWidth$).toFixed(3))}
stroke="red"
fill="none"
/>
</svg>
);
};
export const Primary = Template.bind({});
Primary.args = {
size: 30,
};