コンテンツにスキップ

React 連携

SynState は、シームレスな React 連携のためのコンパニオンパッケージ synstate-react-hooks を提供しています。

Terminal window
npm add synstate-react-hooks

synstate-react-hookscreateState は、生の Observable の代わりに React hook を返します:

import type * as React from 'react';
import { createState } from 'synstate-react-hooks';
const [useUserState, setUserState] = createState({
name: '',
email: '',
});
const UserProfile = (): React.JSX.Element => {
const user = useUserState();
return (
<div>
<p>{`Name: ${user.name}`}</p>
<button
onClick={() => {
setUserState({
name: 'Alice',
email: 'alice@example.com',
});
}}
>
{'Set User'}
</button>
</div>
);
};

これは synstate-react-hooks を使用しない以下のコードと同等です:

import * as React from 'react';
import { createState } from 'synstate';
const [userState, setUserState] = createState({
name: '',
email: '',
});
const UserProfile = (): React.JSX.Element => {
const user = React.useSyncExternalStore(
(onStoreChange: () => void) => {
const { unsubscribe } = userState.subscribe(onStoreChange);
return unsubscribe;
},
() => userState.getSnapshot().value,
);
return (
<div>
<p>{`Name: ${user.name}`}</p>
<button
onClick={() => {
setUserState({
name: 'Alice',
email: 'alice@example.com',
});
}}
>
{'Set User'}
</button>
</div>
);
};

追加ユーティリティ:3番目の要素

Section titled “追加ユーティリティ:3番目の要素”

synstate-react-hookscreateState も、コアパッケージと同様に3要素のタプルを返します。3番目の要素には createState 詳説 で説明されているものと同じユーティリティ(updateStateresetStategetSnapshotinitialState)に加え、コアライブラリの Observable である state が含まれています:

import type * as React from 'react';
import { createState } from 'synstate-react-hooks';
// The third element provides additional utilities and the underlying Observable.
const [
useCount,
setCount,
{
state,
updateState: updateCount,
resetState: resetCount,
getSnapshot: getCountSnapshot,
},
] = createState(0);
const increment = (): void => {
updateCount((n) => n + 1);
};
const Counter = (): React.JSX.Element => {
const count = useCount();
return (
<div>
<p>{`Count: ${count}`}</p>
<button onClick={increment}>{'Increment'}</button>
<button onClick={resetCount}>{'Reset'}</button>
</div>
);
};
// `state` is the same InitializedObservable<number> that the core
// synstate package's createState returns as its first element.
// You can use it with pipe, combine, subscribe, etc.
state.subscribe((value) => {
console.log('count changed:', value);
});
// Read the current value synchronously (outside of React rendering)
console.log('current count:', getCountSnapshot());
プロパティ説明
stateInitializedObservable<S>コアの synstate パッケージの createState が第1要素として返すのと同じ Observable。pipecombinesubscribe などと組み合わせて使用可能
updateState(updateFn: (prev: S) => S) => S前の値を受け取る関数で状態を更新(詳細は createState 詳説 を参照)
resetState() => S状態を初期値にリセット
getSnapshot() => S購読せずに現在の値を同期的に読み取る
initialStateScreateState に渡された初期値

派生 Observable の購読: useObservableValue

Section titled “派生 Observable の購読: useObservableValue”

3番目の要素の state は通常の Observable であり、pipemapcombine などで自由に変換できます。派生した Observable を React コンポーネントで購読するには useObservableValue を使用します:

import type * as React from 'react';
import { map } from 'synstate';
import { createState, useObservableValue } from 'synstate-react-hooks';
const [useCount, , { state: count$ }] = createState(0);
// Derive a new Observable using pipe + map
const doubled$ = count$.pipe(map((n) => n * 2));
const message$ = count$.pipe(
map((n) => (n === 0 ? 'Click to start' : `Count is ${n}`)),
);
const CountDisplay = (): React.JSX.Element => {
const count = useCount(); // Equivalent to using useObservableValue(count$)
// Subscribe to derived Observables with useObservableValue
const doubled = useObservableValue(doubled$);
const message = useObservableValue(message$);
return (
<div>
<p>{`Count: ${count}, Doubled: ${doubled}`}</p>
<p>{message}</p>
</div>
);
};

useObservableValuecreateStatepipecombine など、SynState のあらゆるオペレーターから得られる InitializedObservable に対して動作します。Observable を購読し、現在の値を React state として返します。値が変化するとコンポーネントが再レンダリングされます。

React 16.8–17(useSyncExternalStore なし)を使用している場合は、synstate-react-hooks-compat をインストールしてください:

Terminal window
npm add synstate-react-hooks-compat

このパッケージは synstate-react-hooks同じ API を提供しますが、内部で useState + useEffect を使用します。import を変更するだけで使えます:

import { createState } from 'synstate-react-hooks-compat';
const [useUserState, setUserState] = createState({
name: '',
email: '',
});

すべてのフック(createStatecreateReducercreateBooleanStateuseObservableValueuseObservableEffect)が同じように動作します。React 18+ にアップグレードする際は、import を synstate-react-hooks に変更するだけで、他のコード変更は不要です。

手動で実装する場合(追加パッケージ不要)

Section titled “手動で実装する場合(追加パッケージ不要)”

コアの synstate パッケージを useStateuseEffect で直接使用することもできます:

import * as React from 'react';
import { createState } from 'synstate';
// Global state (outside component)
const [userState, setUserState, { getSnapshot }] = createState({
name: '',
email: '',
});
const UserProfile = (): React.JSX.Element => {
const [user, setUser] = React.useState(getSnapshot());
React.useEffect(() => {
const subscription = userState.subscribe(setUser);
return () => {
subscription.unsubscribe();
};
}, []);
return (
<div>
<p>{`Name: ${user.name}`}</p>
<button
onClick={() => {
setUserState({
name: 'Alice',
email: 'alice@example.com',
});
}}
>
{'Set User'}
</button>
</div>
);
};