React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする

* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 01 ー atomを使った項目操作](https://qiita.com/FumioNonaka/items/3ed6b562a20f6d4abb2b)**」
* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 02 ー selectorによるフィルタリングと集計](https://qiita.com/FumioNonaka/items/d1bf46eb5437813f10e1)**」
* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える](https://qiita.com/FumioNonaka/items/dd0c988bae47ab82e012)**」


前回は、Recoilの状態をコンポーネントから直に書き替えないようにしました。運用としては、これでもよいかもしれません。けれど、あえてRecoilの設定のフック(たとえば`useSetRecoilState`)を使えば、コンポーネントから状態は変えてしまえます。今回のお題は、Recoilの状態の参照もカスタムフックを介すようにすることです。つまり、Recoilの状態はもはや`export`しません。書き上がりのコードは、つぎのサンプル001のCodeSandbox作例で確かめられます。

#### サンプル001■React + TypeScript: Recoil tutorial example 04
https://codesandbox.io/embed/react-typescript-recoil-tutorial-example-04-new-p6gk6m?fontsize=14&hidenavigation=1&theme=dark


# `todoListFilterState`の状態をカスタムフックから参照する
`atom`については、状態をカスタムフックの戻り値に加えればよいことです。`todoListFilterState`は、つぎのように書き替えてください。

```react:src/state/todoListFilterState.ts
// import { atom, useSetRecoilState } from 'recoil';
import { atom, useRecoilState } from 'recoil';

export const useFilter = () => {
	// const setFilter = useSetRecoilState(todoListFilterState);
	const [filter, setFilter] = useRecoilState(todoListFilterState);

	// return { setListFilter };
	return { filter, setListFilter };
};
```

コンポーネント(`TodoListFilters`)の側は、状態(`todoListFilterState`)を直に`import`することなく、カスタムフック(`useFilter`)から取り出します。Recoilの参照のフック(`useRecoilValue`)も使わずに済むということです。

```react:src/components/TodoListFilters.tsx
// import { useRecoilValue } from 'recoil';
import {

	// todoListFilterState,

} from '../state/todoListFilterState';

export const TodoListFilters: FC = () => {
	// const filter = useRecoilValue(todoListFilterState);
	// const { setListFilter } = useFilter();
	const { filter, setListFilter } = useFilter();

};
```


# `todoListState`の状態をカスタムフックから参照する
Todoリストのデータをもつ`todoListState`も同じです。参照(`todoList`)はカスタムフックから返すように書き替えます。

```react:src/state/todoListState.ts
export const useTodoList = () => {

	return {

		todoList,

	};
};
```

コンポーネント(`TodoItem`)は、Recoilの状態(`todoListState`)を直に`import`しません。参照(`todoList`)をカスタムフック(`useTodoList`)から得るのです。

```react:src/components/TodoItem.tsx
// import { useRecoilValue } from 'recoil';
// import { todoListState, useTodoList } from '../state/todoListState';
import { useTodoList } from '../state/todoListState';

export const TodoItem: FC<Props> = ({ item }) => {
	// const todoList = useRecoilValue(todoListState);
	const {

		todoList,

	} = useTodoList();

};
```


# `todoListStatsState`の`selector`は関数`selectorFamily`でつくる
`selector`は、Recoilの他の状態を参照して、返す値を決めます。前述の`atom`と同じように、カスタムフックから状態を取り出したいところです。けれど、オプションオブジェクトの`get`コールバックからは、カスタムフックが呼び出せません(トップレベルではないのでエラーになります)。あらかじめ参照は外で得て、`selector`に渡すかたちを取らなければならないのです。

そのようなとき、関数[`selectorFamily`](https://recoiljs.org/docs/api-reference/utils/selectorFamily/)を使えば、`get`コールバックに引数として値が渡せます(「[selectorFamily(options)](https://qiita.com/Daichi44/items/f552dfda4765596d83d7#selectorfamilyoptions)」参照)。`todoListStatsState`の書き替えは、つぎのとおりです。

```react:src/state/todoListStatsState.ts
// import { selector } from 'recoil';
import { selectorFamily, useRecoilValue } from 'recoil';
// import { todoListState } from './todoListState';
import { useTodoList } from './todoListState';
import type { TodoItemType } from './todoListState';

// export const todoListStatsState = selector<TodoStatsType>({
export const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>({

	// get: ({ get }) => {
	get: (todoList) => () => {
		// const todoList = get(todoListState);

	}
});
export const useTodoListStats = () => {
	const { todoList } = useTodoList();
	const {
		totalNum,
		totalCompletedNum,
		totalUncompletedNum,
		percentCompleted
	} = useRecoilValue(todoListStatsState(todoList));
	return {
		totalNum,
		totalCompletedNum,
		totalUncompletedNum,
		percentCompleted
	};
};
```

そうすると、コンポーネント(`TodoListStats`)は、Recoilの状態(`todoListStatsState`)は直に`import`しません。カスタムフック(`useTodoListStats`)から参照が取り出せるからです。`useRecoilValue`フックも、もはや要りません。

```react:src/components/TodoListStats.tsx
import { FC } from 'react';
// import { useRecoilValue } from 'recoil';
// import { todoListStatsState } from '../state/todoListStatsState';
import { useTodoListStats } from '../state/todoListStatsState';

export const TodoListStats: FC = () => {
	const {

		// } = useRecoilValue(todoListStatsState);
	} = useTodoListStats();

};
```



# `filteredTodoListState`の`selector`を関数`selectorFamily`でつくる
`filteredTodoListState`も、`selector`は関数`selectorFamily`を用いてつくります。状態は`filter`と`list`のふたつ使いますので、オブジェクトに収めたうえで引数として渡しました。

```react:src/state/filteredTodoListState.ts
// import { selector } from 'recoil';
import { selectorFamily, useRecoilValue } from 'recoil';
// import { todoListFilterState } from './todoListFilterState';
import { useFilter } from './todoListFilterState';
import type { FilterType } from './todoListFilterState';
// import { todoListState } from '../state/todoListState';
import { useTodoList } from './todoListState';

type ItemListWithFilter = {
	filter: FilterType;
	list: TodoItemType[];
};
// export const filteredTodoListState = selector<TodoItemType[]>({
export const filteredTodoListState = selectorFamily<
	TodoItemType[],
	ItemListWithFilter
>({

	// get: ({ get }) => {
	get: ({ filter, list }) => () => {
		// const filter: FilterType = get(todoListFilterState);
		// const list = get(todoListState);

	}
});
export const useFilteredTodoList = () => {
	const { filter } = useFilter();
	const { todoList: list } = useTodoList();
	const filteredTodoList = useRecoilValue(
		filteredTodoListState({ filter, list })
	);
	return filteredTodoList;
};
```

コンポーネント(`TodoList`)は、状態(`filteredTodoListState`)は直に`import`することなく、カスタムフック(`useFilteredTodoList`)から参照(`todoList`)を得ればよいのです。

```react:src/components/TodoList.tsx
// import { useRecoilValue } from "recoil";
// import { filteredTodoListState } from "../state/filteredTodoListState";
import { useFilteredTodoList } from "../state/filteredTodoListState";

export const TodoList: FC = () => {
	// const todoList = useRecoilValue(filteredTodoListState);
	const todoList = useFilteredTodoList();

};
```


# Recoilの状態の`export`を外す
これで、Recoilの状態を直に`import`するコンポーネントモジュールはなくなりました。`atom`と`selector`の`export`は、すべて外して構わないということです。機械的な書き替えなので、コードは以下に羅列します。Recoilの状態は、もはやカスタムフックを介さないかぎり、外から参照も設定もできません。各モジュールのコードについては、冒頭に掲げたサンプル001のCodeSandbox作例でお確かめください。

```react:src/state/filteredTodoListState.ts
// export const filteredTodoListState = selectorFamily<
const filteredTodoListState = selectorFamily<
	TodoItemType[],
	ItemListWithFilter
>({

});
```

```react:src/state/todoListFilterState.ts
// export const todoListFilterState = atom<FilterType>({
const todoListFilterState = atom<FilterType>({

});
```

```react:src/state/todoListState.ts
// export const todoListState = atom<TodoItemType[]>({
const todoListState = atom<TodoItemType[]>({

});
```

```react:src/state/todoListStatsState.ts
// export const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>(
const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>({

});
```


* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 01 ー atomを使った項目操作」](https://qiita.com/FumioNonaka/items/3ed6b562a20f6d4abb2b)**」
* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 02 ー selectorによるフィルタリングと集計](https://qiita.com/FumioNonaka/items/d1bf46eb5437813f10e1)**」
* 「**[React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える](https://qiita.com/FumioNonaka/items/dd0c988bae47ab82e012)**」