2022-01-31
Gatsby Theme Aoi で App State を設定する
post by cieloazul310
Gatsby Theme Aoi には Gatsby サイト全体で保持されるグローバルな state である App State を設定できます。この 機能も前回同様、Gatsby テーマの Shadowing で実装します。
App State は Redux でお馴染みの reducer (リデューサ) によって実装されています。リデューサについての基礎知識が必要になります。以下の記事を参照してください。
App State ファイルと App State Context ファイルを作成
AppState.ts
// ./src/@cieloazul310/gatsby-theme-aoi-top-layout/utils/AppState.ts
export type AppState = {
count: number;
};
export const initialAppState: AppState = {
count: 0,
};
export type Action = { type: 'RESET' } | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'SET_VALUE'; value: number };
export default function reducer(state: AppState, action: Action): AppState {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_VALUE':
return { ...state, count: action.value };
case 'RESET':
return initialAppState;
default:
throw new Error("Reducer don't match the action type.");
}
}
AppState
: App State の型定義initialAppState
: App State の初期値Action
: リデューサに通すアクションの型定義reducer
(default export): App State のリデューサ
AppState.ts
では Gatsby サイトで使う App State の型定義、初期値、リデューサ、リデューサに渡すアクションの型定義を行います。
AppStateContext.tsx
// ./src/@cieloazul310/gatsby-theme-aoi-top-layout/utils/AppStateContext.tsx
import * as React from 'react';
import { initialAppState, AppState, Action } from './AppState';
const AppStateContext = React.createContext<{
state: AppState;
dispatch: React.Dispatch<Action>;
}>({
state: initialAppState,
dispatch: () => {
throw new Error();
},
});
export default AppStateContext;
/**
* A hook returns global `AppState`.
* @returns {Object} AppState
*/
export function useAppState() {
const { state } = React.useContext(AppStateContext);
return React.useMemo(() => state, [state]);
}
/**
* A hook returns `AppState` dispatch function.
* @returns {function} React.Dispatch<Action>
*/
export function useDispatch() {
const { dispatch } = React.useContext(AppStateContext);
return React.useCallback(
(action: Action) => {
dispatch(action);
},
[dispatch]
);
}
AppStateContext.tsx
が元のファイルと同じ内容なのであれば、Shadowing する必要はないのではと思われるかもしれませんが、AppState
や dispatch
の型の不一致を引き起こします。実はそれでも動作面では問題はないのですが、ESLint に怒られてしまうので素直に AppStateContext.tsx
をコピペしましょう。
App State を使う
App State はフックを使うことで全てのページ、全てのコンポーネントから利用できます。作成した AppStateContext.tsx
から useAppState
フックをインポートしてください。
// ./src/pages/index.tsx
import { useAppState } from '../@cieloazul310/gatsby-theme-aoi-top-layout/utils/AppStateContext.tsx';
function IndexPage() {
const { count } = useAppState();
return (
<Layout>
<h1>App State Example</h1>
<p>current count is {count}</p>
</Layout>
);
}
export default IndexPage;
App State は各ページよりも上位のコンポーネントで管理されているので、ページを移動しても値が保持されます。
App State を更新する
App State を更新したい場合は、useDispatch
フックをインポートして dispatch
メソッドを使用します。useDispatch
も全てのページ、全てのコンポーネントで利用できます。
// ./src/pages/index.tsx
import { useAppState, useDispatch } from '../@cieloazul310/gatsby-theme-aoi-top-layout/utils/AppStateContext.tsx';
function IndexPage() {
const { count } = useAppState();
const dispatch = useDispatch();
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
const decrement = () => {
dispatch({ type: 'DECREMENT' });
};
const set_value = (value: number) => () => {
dispatch({ type: 'SET_VALUE', value });
}
return (
<Layout>
<h1>App State Example</h1>
<p>current count is {count}</p>
<button onClick={increment}>
Increment
</button>
<button onClick={decrement}>
Decrement
</button>
<div>
{[0, 10, 100].map((d) => (
<button key={d.toString()} onClick={set_value(d)}>{d}</button>
))}
</div>
</Layout>
);
}
export default IndexPage;