react メモ

## ディレクトリ
```
- src
    // プロジェクトによって変えればok
    // 各componentはContainerとPresentationで分けるとPresentationはpropsを利用すればいいだけになる
    - components // 以下各ディレクトリで使うもの
        - index.ts // ContainerComponetをexport
        - hooks.ts // ロジックを分離, テストできるようにする, useDispatch, useStateなど
        - ${component名}Container.tsx // hooksやpropsを受け取り${component名}.tsxに渡す
        - ${component名}.tsx // Presentational Component
    - enums
    - consts
    - hooks // 共通hooks定義
    - models
        - ${domain名.ts}
    - redux
        - ${domain名}
            - actions.ts // thunkのみ
            - index.ts // adapter, initialState, reducer, selector, Stateの型, actions(actionとthunkActionをマージ)をexport
            - module.ts // slice
            - selectors.ts
            - types.ts // actionのname spaceとthunkの型をexport
    - router
        - index.tsx // 各pagesへのrouter定義, lazyで読み込んだほうが軽い
        - AuthRouterなど
    - repositories
    - utils
```

## 型定義(TypeScript)
下の定義を利用して上にtypeを拡張していくと、修正や拡張がしやすい

## repository層
- apiのデータを表示用に加工してしまうとstoreにその値が反映されてしまう
    - storeには純粋なデータを保持、各セレクターで取得時に加工するなど

### エラーハンドリング
- apiClientなどでエラーハンドリングして加工してから再スローすると、他の箇所でエラーハンドリングの処理が書きやすい
- エラーハンドリングの処理を共通化できるもの
    - reduxのactionで書いちゃう
- 分岐するもの
    - hooks内でそれぞれハンドリング処理を書き、Componentで呼び出しするとよい
    [unwrap](https://redux-toolkit.js.org/api/createAsyncThunk#handling-thunk-results)を使うとthunkの結果をそのまま受け取れる
    thunk内でのエラーのスローも、rejectedWithValueもtry-catchで受けれる
```ts
      try {
        await dispatch(todoActions.getTodosAsync()).unwrap()
      } catch (e) {
        // error handling
      }
```

＊ thunk内でエラー時の挙動
- エラーをスローした場合
    - reducerのactionにはerrorに値が格納されてくる
- rejectedWithValueをreturnした場合
    - reducerのactionにはpayloadに値が格納されてくる

## useeffect
https://overreacted.io/ja/a-complete-guide-to-useeffect/

- 早期return

https://snamiki1212.com/react-useeffect-pitfall#%E6%97%A9%E6%9C%9F%E3%83%AA%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AF%E5%87%BA%E6%9D%A5%E3%82%8B%E3%81%8C-unmount-%E6%99%82%E3%81%AE-callback-%E3%81%AB%E6%B0%97%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B

## ref

## stateが絡むhooks
### custom hooks内で他のhooksのstate更新をする場合
- state更新をする関数を渡してあげないとダメ

```js
const useCount = () => {
  const [count, setCount] = useState(0)
}
```
## redux

### type
https://redux.js.org/usage/usage-with-typescript#define-typed-hooks

### action
#### ActionのPayloadのtypeについて

```slice.js
const counterSlice = createSlice({
  // ...
  reducers: {
    incrementByAmount(state, action) {
      // ...
    },
  },
})

export const { incrementByAmount } = counterSlice.actions
```
```ts
type IncrementByAmountParam = ReturnType<typeof incrementByAmount>['payload']
```

### thunk
型定義をexportする

### selector
- stateの参照のみはselectorの切り出しをしないでいい
    - 第二引数にfast-deep-equalを利用して再レンダリングを回避
        - https://www.npmjs.com/package/fast-deep-equal
        - ＊ただし、state参照の処理自体はかかる
    - テストも不要
- ロジック含む、配列処理(map, filter)などは検証や重い処理になってしまう場合があるのでselectorに切り出し
    - createSelectorを利用
    - inputSelectorsが返す値が前回と同じだった場合は、resultFunction を呼ぶことなく、キャッシュした前回の値を返す
    - キャッシュ数は1

https://9oelm.github.io/2020-09-13--How-to-make-useSelector-not-a-disaster/
https://scrapbox.io/mrsekut-p/createSelector
https://scrapbox.io/mrsekut-p/useSelector

### caseReducer
https://redux-toolkit.js.org/api/createSlice#examples
https://stackoverflow.com/questions/67792374/react-redux-alerting-best-practices

reducerの中で他のreducerをcallできる
```ts
  reducers: {
    addTodo: (state, action: PayloadAction<Omit<Todo, 'id' | 'isDone'>>) => {
      const id =
        state.todos.map((todo) => todo.id).reduce((a, b) => Math.max(a, b), 0) +
        1
      const updatedTodo = { ...action.payload, id, isDone: false }
      const updatedTodos = state.todos.concat(updatedTodo)
      state.todos = updatedTodos
      const deleteTodoAction = slice.actions.deleteTodo(1)
      slice.caseReducers.deleteTodo(state, deleteTodoAction) // ここ
    },
```

また、他のsliceのactionもcallできる
uiStateのloadingとかもcomponent側ではなく、reducer側で処理を共通でかけたりもできる
```ts
const slice = createSlice({
  name: 'ui',
  initialState: {
    loading: false,
  },
  reducers: {
    updateLoading: (state, action: PayloadAction<{ loading: boolean }>) => {
      state.loading = action.payload.loading
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(todoActions.getTodosAsync.pending, (state, _action) => {
        const action = slice.actions.updateLoading({ loading: true })
        slice.caseReducers.updateLoading(state, action)
      })
      .addMatcher(isFulfilled(todoActions.getTodosAsync), (state, _action) => {
        const action = slice.actions.updateLoading({ loading: false })
        slice.caseReducers.updateLoading(state, action)
      })
      .addMatcher(isRejected(todoActions.getTodosAsync), (state, _action) => {
        const action = slice.actions.updateLoading({ loading: false })
        slice.caseReducers.updateLoading(state, action)
      })
  },
})
```

### reselector
https://github.com/reduxjs/reselect

#### 引数を渡す（不要かも）
https://scrapbox.io/tosuke/reselect%E3%81%A7createSelector%E3%81%AB%E5%BC%95%E6%95%B0%E3%82%92%E6%8C%81%E3%81%A4Selector%E3%82%92%E6%B8%A1%E3%81%97%E3%81%A6%E3%81%AF%E3%81%84%E3%81%91%E3%81%AA%E3%81%84

https://github.com/reduxjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument

※ただし、引数が変わるとstateが変わっていなくても再レンダリングかかる(キャッシュ数1なので、頻繁に再レンダリングされちゃいそう)

これがわかりやすい

https://qiita.com/japanesebonobo/items/843753903fdfa173f516

こっちには普通に書いてるけどダメっぽい

https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc


## custom hooks
### useref
コンポーネントのDOMにセットしたrefをhooks内で使用する場合
1. hooks内でrefを作成してそれをreturnしてあげる
2. 利用側はhooksから受けたrefをセットしてあげればOK

#### メリット
利用側が毎回refを生成しないで済む

### useref vs callback ref
#### callback ref
https://ja.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

https://dev.to/thekashey/the-same-useref-but-it-will-callback-8bo

React が ref を DOM ノードにアタッチまたはデタッチするときにコードを実行したい場合は、callback refを使うといい

- refはcurrentが更新されても通知を受けれない
- callback refを使うと後から、クリックなどでrefに指定した要素が後から表示された場合でも変更を受け取ることができる

ここ読んでみる
https://www.robinwieruch.de/react-ref

## test
### custom hooks
- 非同期処理は、react-testing-libraryのwaitForを使うこと  
使わないと、処理が終わる前にexpectが走ってしまう

## tips
### selectのoptionなどの値はconst定義
```ts
export const Role = {
  USER: {
    code: 1,
    label: 'ユーザー'
  },
  ADMIN: {
    code: 2,
    label: '管理者'
  },
  OWNER: {
    code: 3,
    label: 'オーナー'
  },
  UNKNOWN: {
    code: 0,
    label: ''
  }
} as const;
export type RoleKey = keyof typeof Role;
export type RoleValue = typeof Role[RoleKey];

export const findRoleByCode = (
  code: number
): RoleValue =>
  Object.values(Role).find(p => p.code === code) ||
  Role.UNKNOWN;
```
利用側では
```ts
Object.values(Role)
```
で[{code:"",label:""}]がとれる

### DFlex
```tsx
import * as React from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';

type BoxProps = {
  children: React.ReactNode;
  direction?: string;
  justify?: string;
  align?: string;
} & React.InputHTMLAttributes<HTMLDivElement>;
const Wrapper = styled.div<Omit<BoxProps, 'children'>>`
  display:flex;
  ${props => css`
    flex-direction: ${props.direction ?? 'row'};
  `}
  ${props =>
    css`
      align-items: ${props.align ?? 'center'};
    `}
  ${props => css`
    justify-content: ${props.justify ?? 'start'};
  `}
`;

export type DFlexProps = BoxProps;
export const DFlex: React.VFC<DFlexProps> = React.memo(
  ({ children, ...props }) => {
    return <Wrapper {...props}>{children}</Wrapper>;
  }
);
DFlex.displayName = 'DFlex';
```
※reactのcomponentだと型エラーでcomponent selectorが使えないので、atomはmemoでwrapしない
https://qiita.com/kotarella1110/items/a243c7bde41fad23343f

### react-tooltip
offsetと吹き出しの矢印の位置変更
```tsx
                <StyledTooltip
                  tooltipId={'testId'}
                  html={true}
                  width={`${TOOLTIP_WIDTH}px`}
                  offset={{ right: TOOLTIP_WIDTH / 2 - TOOLTIP_ARROW_WIDTH }}
                />

const StyledTooltip = styled(Tooltip)`
  &:after {
    /* 三角吹き出しの位置調整 */
    left: ${TOOLTIP_ARROW_WIDTH}px !important;
  }
`;
```