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

[React] Refactor a Class Component with React hooks to a Function的更多相关文章

  1. [React] Refactor componentWillReceiveProps() to getDerivedStateFromProps() in React 16.3

    The componentWillReceiveProps() method is being deprecated in future version of React (17). Many of ...

  2. [React] Refactor a Stateful List Component to a Functional Component with React PowerPlug

    In this lesson we'll look at React PowerPlug's <List /> component by refactoring a normal clas ...

  3. [React Native] Create a component using ScrollView

    To show a list of unchanging data in React Native you can use the scroll view component. In this les ...

  4. React.createClass和extends Component的区别

    React.createClass和extends Component的区别主要在于: 语法区别 propType 和 getDefaultProps 状态的区别 this区别 Mixins 语法区别 ...

  5. React.Component 与 React.PureComponent(React之性能优化)

    前言 先说说 shouldComponentUpdate 提起React.PureComponent,我们还要从一个生命周期函数 shouldComponentUpdate 说起,从函数名字我们就能看 ...

  6. React Native 中的component 的生命周期

    React Native中的component跟Android中的activity,fragment等一样,存在生命周期,下面先给出component的生命周期图 getDefaultProps ob ...

  7. [React Router] Prevent Navigation with the React Router Prompt Component

    In this lesson we'll show how to setup the Prompt component from React Router. We'll prompt with a s ...

  8. React 的 PureComponent Vs Component

    一.它们几乎完全相同,但是PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能 1.所谓浅比较 ...

  9. how to design a search component in react

    how to design a search component in react react 如何使用 React 设计并实现一个搜索组件 实时刷新 节流防抖 扩展性,封装,高内聚,低耦合 响应式 ...

随机推荐

  1. Python的环境搭建——万丈高楼平地起

    Python的环境搭建,远程连接,端口映射,虚拟机 写在正文之前 python语言的开发环境还是相对比较简单的,但是也是有很多需要注意的地方,对于初次接触python或者以前很少用到虚拟环境的朋友来说 ...

  2. db2部署与数据仓库应用

    概念特性 安装 基础命令 连接 监控 存储过程 数据合并 Merge Into是增量备份 结果集分组 row_number() OVER (PARTITION BY COL1 ORDER BY COL ...

  3. ssvm和console 模板机 连接不上管理节点

    说明: cloudstack 版本http://www.shapeblue.com/packages/    并不是官方的 systemvm64template-4.6.0-vmware.ova  官 ...

  4. Hibernate 单项一对多的关联映射

    在上一篇中我们简单介绍了多对一的关联映射,本文介绍hibernate中一对多的关联映射. 1.设计表结构 虽然关联关系由多对一变为一对多,但是我们表结构不会发生改变,只是指向变了. 2.创建stude ...

  5. 2017四川省赛D题《Dynamic Graph》

    题意:给出一个n个点m条边的有向无环图(DAG),初始的时候所有的点都为白色.然后有Q次操作,每次操作要把一个点的颜色改变,白色<->黑色,对于每次操作,输出满足下列点对<u,v&g ...

  6. 【atcoder F - Namori】**

    F- Namori http://agc004.contest.atcoder.jp/tasks/agc004_f Time limit : 2sec / Memory limit : 256MB S ...

  7. luogu P2619 [国家集训队2]Tree I

    题目链接 luogu P2619 [国家集训队2]Tree I 题解 普通思路就不说了二分增量,生成树check 说一下坑点 二分时,若黑白边权有相同,因为权值相同优先选白边,若在最有增量时出现黑白等 ...

  8. luoguP3714 [BJOI2017]树的难题 点分治

    以后传数组绝对用指针... 考虑点分治 在点分的时候,把相同的颜色的在一起合并 之后,把不同颜色依次合并 我们可以用单调队列做到单次合并$O(n + m)$ 如果我们按照深度大小来合并,那么由于每次都 ...

  9. 多个Fragment在屏幕翻转会重影问题的解决

    fragment使用add和hide而不用replace的方法添加到activity中,如果屏幕翻转可能会又add新的fragment进去,所以会重影. 如果有一个sparsearray保存fragm ...

  10. Java并发(十三):并发工具类——同步屏障CyclicBarrier

    先做总结 1.CyclicBarrier 是什么? CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点) ...