[React] Refactor a Class Component with React hooks to a Function

We have a render prop based class component that allows us to make a GraphQL request with a given query string and variables and uses a GitHub graphql client that is in React context to make the request. Let's refactor this to a function component that uses the hooks useReducer, useContext, and useEffect.

 

Class Based Component:

复制代码
import {Component} from 'react'
import PropTypes from 'prop-types'
import isEqual from 'lodash/isEqual'
import * as GitHub from '../../../github-client'

class Query extends Component {
  static propTypes = {
    query: PropTypes.string.isRequired,
    variables: PropTypes.object,
    children: PropTypes.func.isRequired,
    normalize: PropTypes.func,
  }
  static defaultProps = {
    normalize: data => data,
  }
  static contextType = GitHub.Context

  state = {loaded: false, fetching: false, data: null, error: null}

  componentDidMount() {
    this._isMounted = true
    this.query()
  }

  componentDidUpdate(prevProps) {
    if (
      !isEqual(this.props.query, prevProps.query) ||
      !isEqual(this.props.variables, prevProps.variables)
    ) {
      this.query()
    }
  }

  componentWillUnmount() {
    this._isMounted = false
  }

  query() {
    this.setState({fetching: true})
    const client = this.context
    client
      .request(this.props.query, this.props.variables)
      .then(res =>
        this.safeSetState({
          data: this.props.normalize(res),
          error: null,
          loaded: true,
          fetching: false,
        }),
      )
      .catch(error =>
        this.safeSetState({
          error,
          data: null,
          loaded: false,
          fetching: false,
        }),
      )
  }

  safeSetState(...args) {
    this._isMounted && this.setState(...args)
  }

  render() {
    return this.props.children(this.state)
  }
}

export default Query
复制代码

 

Conver props:

复制代码
 // From 
static propTypes = {
    query: PropTypes.string.isRequired,
    variables: PropTypes.object,
    children: PropTypes.func.isRequired,
    normalize: PropTypes.func,
  }
  static defaultProps = {
    normalize: data => data,
  }

// To:

function Query ({query, variables, children, normalize = data => data}) {

}
复制代码

 

Conver Context:

复制代码
// From
static contextType = GitHub.Context
...
const client = this.context

// To:
import {useContext} from 'react'

function Query ({query, variables, children, normalize = data => data}) {
  const clinet = useContext(GitHub.Context)
}
复制代码

 

Conver State:

I don't like to cover each state prop to 'useState' style, it is lots of DRY, instead, using useReducer is a better & clean apporach.

复制代码
// From
state = {loaded: false, fetching: false, data: null, error: null}

//To:
  import {useContext, useReducer} from 'react'
  ...
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}), 
    defaultState)
复制代码

 

Conver side effect:

复制代码
// From:
  componentDidMount() {
    this._isMounted = true
    this.query()
  }

  componentDidUpdate(prevProps) {
    if (
      !isEqual(this.props.query, prevProps.query) ||
      !isEqual(this.props.variables, prevProps.variables)
    ) {
      this.query()
    }
  }

  componentWillUnmount() {
    this._isMounted = false
  }

  query() {
    this.setState({fetching: true})
    const client = this.context
    client
      .request(this.props.query, this.props.variables)
      .then(res =>
        this.safeSetState({
          data: this.props.normalize(res),
          error: null,
          loaded: true,
          fetching: false,
        }),
      )
      .catch(error =>
        this.safeSetState({
          error,
          data: null,
          loaded: false,
          fetching: false,
        }),
      )
  }

// To:

  useEffect(() => {
    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,
        }),
      )
  }, [query, variables]) // trigger the effects when 'query' or 'variables' changes  
复制代码

 

Conver render:

复制代码
// From:
  render() {
    return this.props.children(this.state)
  }

// To:
function Query({children ... }) {

 ...
 return children(state);    
}
复制代码

 

-----

 

Full Code:

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

function Query ({query, variables, children, normalize = data => data}) {
  const client = useContext(GitHub.Context)
  const defaultState = {loaded: false, fetching: false, data: null, error: null}
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    defaultState)
  useEffect(() => {
    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,
        }),
      )
  }, [query, variables]) // trigger the effects when 'query' or 'variables' changes
  return children(state)
}

export default Query
复制代码

 

posted @   Zhentiw  阅读(521)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2017-02-17 [ES2016] Check if an array contains an item using Array.prototype.includes
2017-02-17 [Ramda] Handle Errors in Ramda Pipelines with tryCatch
2016-02-17 [Regular Expressions] Match the Start and End of a Line
2016-02-17 [ES7] Object.observe + Microtasks
点击右上角即可分享
微信分享提示