🧐 Concave
A Lens-like interface for state management in React.
Overview
Introduction to Lenses for React developers
Installation
API
stateful<S>(initialState: S): [Lens<S>, MutableRefObject<S>]
stateless<S>(): [Lens<S>, LensProvider<S>]
useStateful<S>(initialState: S): [Lens<S>, MutableRefObject<S>]
The React equivalent of stateful
as it is convenient to create a stateful lens as part of initializing a component.
Note: There is no useStateless
because the return value of stateless
is static and can just be constructed outside the React life-cycle.
LensProvider<S>
Lens<A>
A stateless Proxy around A
Lens<A>.use(shouldUpdate? ShouldUpdate<A>): [ProxyValue<A>, UpdateFn<A>]
Lens<A>.$key
Examples
Testing
Performance tips
-
Use shouldUpdate.
-
If do use a shouldUpdate argument for the lens, you can either memoize it with React.useMemo
or React.useCallback
or store it outside
of the component.
Example
Uses TypeScript and Proxy to dynamically construct a lens-like interface for your application state.
You can construct a lens/React Provider by just providing the shape of your application state
import { stateless } from "concave";
import type { State } from "./application-state";
export const [lens, LensProvider] = stateless<State>();
import type { State } from './application-state';
import { Root } from './Root';
import { lens, LensProvider } from './LensProvider';
export const App = () => {
const state: State = { ... };
<LensProvider value={state} onChange={...}>
<Root state={lens} />
</LensProvider>
}
The lens can be focused by regular member access.
import { Lens } from "concave";
import type { State } from "./application-state";
import { Profile } from "./Profile";
type Props = {
state: Lens<State>;
};
export const Root = (props: Props) => {
return <Profile state={props.state.user.profile} />;
};
And then the underlying data it can be accessed by collapsing the lens into a React hook with use
.
import { Lens } from "concave";
type Props = {
state: Lens<{ name: string; email: string }>;
};
const Profile = (props: Props) => {
const [name, updateProfileName] = props.state.name.use();
const [email, updateProfileEmail] = props.state.email.use();
return (
<>
<input type="text" value={name} onChange={(ev) => updateProfileName(() => ev.target.value)} />
<input type="email" value={email} onChange={(ev) => updateProfileEmail(() => ev.target.value)} />
</>
);
};