[React] Safely setState on a Mounted React Component through the useEffect Hook

In the class version of this component, we had a method called safeSetState which would check whether the component was still mounted before trying to call setState. This is because our graphql client library is unable to cancel in-flight requests. Let's make that same kind of thing work by tracking the mounted state of our component using the useRef and useEffect hooks.

 

We want a "lock", which should run once when component inited, after component unmounted, it should be reseted.

We can use 'useRef' to build a container to hold our lock:

  const mountedRef = useRef(false);

Then we can use useEffect:

  useEffect(() => {
    mountedRef.current = true
    return () => (mountedRef.current = false)
  }, [])

The reason to use '[]' as second arguement, is because we don't want useEffect be triggered second times, we only want to run once, therefore, we use empty array, it won't trigger scecond time.

 

Then we can create a safe setSetate function:

  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    {
      loaded: false,
      fetching: false,
      data: null,
      error: null,
    },
  )

 const setSafeState = (...args) => mountedRef.current && setState(...args);

 

----

Full code:

import {useContext, useReducer, useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import isEqual from 'lodash/isEqual'
import * as GitHub from '../../../github-client'

function useSetState(initialState) {
  return useReducer(
    (state, newState) => ({...state, ...newState}),
    initialState,
  )
}

function useSafeSetState(initialState) {
  const [state, setState] = useSetState(initialState)

  const mountedRef = useRef(false)
  useEffect(() => {
    mountedRef.current = true
    return () => (mountedRef.current = false)
  }, [])
  const safeSetState = (...args) => mountedRef.current && setState(...args)

  return [state, safeSetState]
}

function Query({query, variables, normalize = data => data, children}) {
  const client = useContext(GitHub.Context)
  const [state, setState] = useSafeSetState({
    loaded: false,
    fetching: false,
    data: null,
    error: null,
  })

  useEffect(() => {
    if (isEqual(previousInputs.current, [query, variables])) {
      return
    }
    setState({fetching: true})
    client
      .request(query, variables)
      .then(res =>
        setState({
          data: normalize(res),
          error: null,
          loaded: true,
          fetching: false,
        }),
      )
      .catch(error =>
        setState({
          error,
          data: null,
          loaded: false,
          fetching: false,
        }),
      )
  })

  const previousInputs = useRef()
  useEffect(() => {
    previousInputs.current = [query, variables]
  })

  return children(state)
}

Query.propTypes = {
  query: PropTypes.string.isRequired,
  variables: PropTypes.object,
  children: PropTypes.func.isRequired,
  normalize: PropTypes.func,
}

export default Query

 

转载于:https://www.cnblogs.com/Answer1215/p/10410150.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值