ReactContext用のLoggerを作った


軽量なサービスの場合Reduxを使うのをやめてReactContextで管理するようになったんだけど、値の変更とかのタイミングでログ出してくれるredux-logger的なのあったらいいなと思ったので自作した話
(作ったの3ヶ月ぐらい前だから今だとすでにもっといいのがあるかも・・・

作ったもの

hooks/useLogger.js
import { useCallback, useEffect, useRef } from "react"
import moment from "moment"

const colors = {
  prefix: "#4CAF50",
  log: "inherit",
  prev: "#9E9E9E",
  next: "#03A9F4",
  error: "#F20404",
}

function usePrevious(value) {
  const ref = useRef(null)
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}

export function useContextLogger(prefix, inputs) {
  Object.keys(inputs).forEach(key => {
    const value = inputs[key]
    const prev = usePrevious(value)
    useEffect(() => {
      const timestamp = moment().format("HH:mm:ss.SSS")
      console.group(
        `%c[${prefix}] %c${key} %c@${timestamp}`,
        `color: ${colors.prefix}`,
        `color: ${colors.log}; font-weight: bold;`,
        "color: gray; font-weight: lighter;",
      )
      console.log(`%c prev`, `color: ${colors.prev}`, prev)
      console.log(`%c next`, `color: ${colors.next}`, value)
      console.groupEnd()
    }, [value])
  })
}

使い方

10分ぐらいでぱぱっと作った例なので微妙なところ満載ですがご容赦を

contexts/todo.jsx
const TodosContext = createContext(null)
const TodosContextProvider = ({ children}) => {
  const [todos, setTodos] = useState([])

  const add = useCallback((todo) => {
    const id = todos.length + 1
    setTodos([
      ...todos,
      {
        id,
        text: todo,
        completed: false,
        createdAt: moment().format('YYYY/MM/DD HH:mm:ss'),
      }
    ])
  }, [todos])

  const toggle = useCallback((id) => {
    const todo = todos.find(t => t.id === id)
    if (!todo) return

    setTodos([
      ...todos.filter(t => t.id !== id),
      { ...todo, completed: !todo.completed },
    ].sort((a, b) => a.id - b.id))
  }, [todos])

  const remove = useCallback((id) => {
    setTodos(todos.filter(t => t.id !== id))
  })

  // ロガーで変更を監視したいパラメータをセット
  useContextLogger("TodosContext", { todos })

  return (
    <TodosContext.Provider value={{ todos, add, toggle, remove }}>
      {children}
    </TodosContext.Provider>
  )
}

こんな感じでログを仕込むと、↓のように変更前後の値がログとして吐き出される。

本家redux-loggerには全然及ばないけど、まぁそれなりに実用的なものが出来て満足

おまけ

単純なロガーもReactHooks使って作成したので置いとく。

hooks/useLogger.js
function useLogger(prefix) {
  const log = useCallback(
    (log, src) => {
      const timestamp = moment().format("HH:mm:ss.SSS")
      console.log(
        `%c[${prefix}] %c${log} @ %c${timestamp}`,
        `color: ${colors.prefix}`,
        `color: ${colors.log}; font-weight: bold;`,
        "color: gray; font-weight: lighter;",
        src || "",
      )
    },
    [prefix],
  )
  return { log }
}

こっちはconsole.logの代わりに使えて、useLogger呼び出す際にprefixを設定できるから、どの部分で吐かれたログかを管理するのにいい感じ。

const hogeLogger = useLogger('HogeComponent')

hogeLogger.log('test')
hogeLogger.log('obj ->', obj)

みたいに使える。