LoginSignup
1
1

More than 3 years have passed since last update.

[メモ] useState をラップしたようなフック

Last updated at Posted at 2020-07-28

[メモ] useState をラップしたようなフック

useState をラップしたようなフックです。
親子関係のコンポーネントにて子側で props.value を処理するような際に使用します。

  • 親側からの props.value の変更を受けて値を同期
    • 値の比較により同期するかを制御
  • propsOnChange を関数として確定した onChange を第三要素として返す
useValue
import { useState, useEffect, Dispatch, SetStateAction } from 'react'

/** オプション指定 */
type Options = {
  /** `propsValue` に変更があった場合 `setValue` による同期を行う (デフォルトは `true` 扱い) */
  sync?: boolean
  /**
   * `useEffect` にて `value` と `propsValue` を比較して差があった場合に更新する制御
   * - `boolean` 指定の場合そのまま更新制御に使用
   */
  diff?: (<T = unknown>(value: T, propsValue: T) => boolean) | boolean
}

/** 値の比較にデフォルトで使用する関数 */
const diffDefault = <T>(val: T, pVal: T): boolean => {
  if (Array.isArray(val) && Array.isArray(pVal)) {
    const unified = Array.from(new Set([...val, ...pVal]))
    return unified.length !== val.length || unified.length !== pVal.length
  }
  // オブジェクトの場合の比較
  if (typeof val === 'object' || typeof pVal === 'object') {
    return JSON.stringify(val) !== JSON.stringify(pVal)
  }
  return val !== pVal
}

/**
 * `useState` をラップしたような関数
 * - `propsValue` の変更が立った場合 `options?.sync` 次第で同期する (デフォルト同期)
 * - 戻り値として `onChange` を関数として確定したものを返す
 * - `options` の構成
 * -- `sync`: `boolean`\
 *    `propsValue` に変更がある場合に同期するフラグ
 * -- `diff`: `((value: unknown, propsValue: unknown) => boolean) | boolean`\
 *    関数指定により `propsValue` との差分抽出のロジックを指定可能
 */
const useValue = <T, F extends Function | undefined>(
  /** useState に渡す値 */
  propsValue: T,
  /** props などで渡される `onChange` を `Function` として確定させてから返す */
  propsOnChange: F,
  /** オプション指定 */
  options?: Options,
): [T, Dispatch<SetStateAction<T>>, NonNullable<F>] => {
  const [value, setValue] = useState<T>(propsValue)
  const sync = options?.sync ?? true
  const onChange = propsOnChange ?? (() => {})
  const optionsDiff = options?.diff ?? diffDefault
  const diff = typeof optionsDiff === 'boolean' ? () => optionsDiff : optionsDiff
  useEffect(() => {
    if (sync && diff(value, propsValue)) {
      setValue(propsValue)
    }
  }, [propsValue, sync])
  return [value, setValue, onChange as NonNullable<F>]
}

export default useValue

options.diff の指定の事例

options.diff
const [value, setValue, onChange] = useValue(props.value, props.onChange, {
  diff<T = string>(val: T, pVal: T) {
    return val !== pVal
  },
})
1
1
0

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
1
1