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. PBR Step by Step(一)立体角

    转载请注明出处:http://www.cnblogs.com/jerrycg/p/4924761.html  本系列从零起步,作为学习笔记与大家分享,从基础的数学和图形理论,一步一步实现基于物理的渲染 ...

  2. hdu1232 畅通工程 并查集的 应用

    畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  3. socketserver用法列子

    socketserver socketserver内部使用IO多路复用以及“多线程”和“多进程”,从而实现并发处理多个客户端请求的scoket服务端.即,每个客户端请求连接到服务器时,socket服务 ...

  4. Entity Framework(实体框架 EF)

    什么是Entity Framework呢(下面简称EF)? EF(实体框架)是ADO.NET中的一组支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架.ORM(对象关系映射框架):指的是面向 ...

  5. 我的OI生涯 第五章

    我的OI生涯 第五章 千古诗才,蓬莱文章建安骨 一身傲骨,青莲居士谪仙人 李白追月逆江河 包黑斩龙顺民心 豪气压群雄,能使力士脱靴,贵妃捧砚; 仙才媲众美,不让参军俊逸,开府清新 我辈此中惟饮酒 先生 ...

  6. 「SCOI2015」情报传递

    「SCOI2015」情报传递 题目描述 奈特公司是一个巨大的情报公司,它有着庞大的情报网络.情报网络中共有 \(n\) 名情报员.每名情报员可能有若干名(可能没有)下线,除 \(1\) 名大头目外其余 ...

  7. [BZOJ 4719] 天天爱跑步

    Link: BZOJ 4719 传送门 Solution: 感觉求LCA又有了新姿势啊:$Tarjan$离线$O(n+m)$ 每次递归返回时将子树和父节点合并,如果询问节点已访问过则LCA就是已合并的 ...

  8. 【期望DP】BZOJ4008- [HNOI2015]亚瑟王

    题目大意 有\(n\)张卡牌,\(r\)轮游戏.每张卡牌只能用至多一次,每张卡牌被用到的概率为\(p_i\).现在从左往右轮,直到最右一张卡片或者某张卡片被用到.如果某张卡牌被用到,产生\(d_i\) ...

  9. Android Studio NDK开发浅谈

    环境: Android Studio 1.1.0 NDK-r10d 1.新建项目--->包名:com.mxl.az.ndk 新建包含native方法的类:JniOperation.class p ...

  10. python使用UnboundMethodType修改类方法

    from types import UnboundMethodType class class1(object): def fun1(self): print 'fun1' oldfun1 = cla ...