176
172

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React / Next】Recoilを隠蔽して、Flux pattern風に使う

Last updated at Posted at 2022-06-19

Recoil を隠蔽して使いたかったので、その個人的な備忘録です。

やりたいこと

  • Recoil をカプセル化して、Flux patternのように Action / Getter を作りたい
  • 全レイヤーが Recoil を直接 import しないようにしたい
  • サンプルとして jsonplaceholder から fetch

環境

  • React: 17.0.2
  • Next: 11.1.3
  • Recoil: ^0.7.3-alpha.2

サンプルコード

src/store/todoList/types.ts

src/store/todoList/types.ts
// 本来は domain層で定義
export type Todo = {
  id: number;
  title: string;
  userId: number;
  completed: false;
};

/** State の型 */
export type TodoListState = {
  todoList: Todo[];
};

/** Getters の型 */
export type TodoListGetters = {
  useTodoList: () => TodoListState;
};

/** Actions の型 */
export type TodoListActions = {
  useFetchTodoList: () => {
    fetchTodoList: () => void;
  };
  useSetTodo: () => {
    setTodo: (todo: Todo) => void;
  };
};

src/store/todoList/main.ts

src/store/todoList/main.ts
import { useCallback } from 'react';
import { atom, useRecoilValue, useSetRecoilState } from 'recoil';
import recoilUseCase from '@/application/recoilTest';
import { Todo, TodoListActions, TodoListGetters, TodoListState } from './types';
import { RECOIL_ATOMS_KEYS } from '../keys';

// state はそのまま export しない
const todoListState = atom<TodoListState>({
  key: RECOIL_ATOMS_KEYS.TODO_LIST_STATE,
  default: {
    todoList: [],
  },
});

// Getter的役割
const useTodoList = () => {
  return useRecoilValue(todoListState);
};

export const todoListGetters: TodoListGetters = {
  useTodoList,
};

// Action をカスタムフックとして定義
/**  todoList の fetch */
const useFetchTodoList = () => {
  const setState = useSetRecoilState(todoListState);
  const { data } = recoilUseCase.fetch();

  const fetchTodoList = useCallback(() => {
    setState(() => {
      if (!data) {
        return {
          todoList: [],
        };
      }
      return {
        todoList: data,
      };
    });
  }, [data, setState]);

  // TodoListState の保持している state が複数ある場合
  // const fetchTodoList = useCallback(() => {
  //   setState((prev) => {
  //     if (!data) {
  //       return {
  //         todoList: [],
  //       };
  //     }
  //     return {
  //       ...prev,
  //       todoList: data,
  //     };
  //   });
  // }, [data, setState]);

  return { fetchTodoList };
};

/** Todoを追加 */
const useSetTodo = () => {
  const setState = useSetRecoilState(todoListState);

  // 引数がある場合は、useCallbackの第一引数へ記述
  const setTodo = useCallback(
    (todo: Todo) => {
      setState((prev) => {
        return {
          todoList: [...prev.todoList, todo],
        };
      });
    },
    [setState],
  );

  return { setTodo };
};

export const todoListActions: TodoListActions = {
  useFetchTodoList,
  useSetTodo,
};

src/pages/recoil/index.tsx

src/pages/recoil/index.tsx
import { todoListActions } from '@/store/testTodoList';
import RecoilTestTemplate from '@/components/templates/recoilTest';

const RecoilPage = () => {
  // pages フォルダで fetch だけする
  const { fetchTodoList } = todoListActions.useFetchTodoList();
  fetchTodoList();

  return <RecoilTestTemplate />;
};

export default RecoilPage;

src/components/recoilTest.tsx

src/components/recoilTest.tsx
import { todoListActions, todoListGetters } from '@/store/testTodoList';
// 本来は domain層
import { Todo } from '@/store/testTodoList/types';

export const RecoilTestTemplate = () => {
  // Action
  const { setTodo } = todoListActions.useSetTodo();
  // Getter
  const todoListState = todoListGetters.useTodoList();
  const [inputData, setInputData] = useState<string>();

  const handleChange = (e: string) => {
    setInputData(e);
  };

  const handleClickButton = () => {
    const newTodo: Todo = {
      userId: 500,
      id: 500,
      title: inputData ? inputData : '',
      completed: false,
    };
    setTodo(newTodo);
  };

  return (
    <>
      {todoListState.todoList &&
        todoListState.todoList.map((item: any) => <p key={item.id}>{item.title}</p>)}
      <div>
        <input type="text" onChange={(e) => handleChange(e.target.value)} />
        <button onClick={handleClickButton}>add</button>
      </div>
    </>
  );
};
176
172
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
176
172

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?