[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
[React] Refactor a Class Component with React hooks to a Function的更多相关文章
- [React] Refactor componentWillReceiveProps() to getDerivedStateFromProps() in React 16.3
The componentWillReceiveProps() method is being deprecated in future version of React (17). Many of ...
- [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 ...
- [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 ...
- React.createClass和extends Component的区别
React.createClass和extends Component的区别主要在于: 语法区别 propType 和 getDefaultProps 状态的区别 this区别 Mixins 语法区别 ...
- React.Component 与 React.PureComponent(React之性能优化)
前言 先说说 shouldComponentUpdate 提起React.PureComponent,我们还要从一个生命周期函数 shouldComponentUpdate 说起,从函数名字我们就能看 ...
- React Native 中的component 的生命周期
React Native中的component跟Android中的activity,fragment等一样,存在生命周期,下面先给出component的生命周期图 getDefaultProps ob ...
- [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 ...
- React 的 PureComponent Vs Component
一.它们几乎完全相同,但是PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能 1.所谓浅比较 ...
- how to design a search component in react
how to design a search component in react react 如何使用 React 设计并实现一个搜索组件 实时刷新 节流防抖 扩展性,封装,高内聚,低耦合 响应式 ...
随机推荐
- Calendar日期方法
面试居然让我获取当前月份第一天跟最后一天,主要是尴尬的回答不上来. 废话不说,直接贴代码,工作应该是够用了 public class TestCalendar { // 日期也就是这了 public ...
- SpringBoot学习(六)
1.pom 文件 <?xml version="1.0" encoding="utf-8"?> <dependencies> <d ...
- js使用s:property标签接收json格式数据
js使用s:property接收json数据时,会出现字符被转译的错误. 错误如下: 引号会被转译成'"'字符,导致解析不了. 错误原因: html的s:property接收不会出错,而js ...
- [BZOJ3676][APIO2014]回文串(Manacher+SAM)
3676: [Apio2014]回文串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 3097 Solved: 1408[Submit][Statu ...
- [BZOJ4517][SDOI2016]排列计数(错位排列)
4517: [Sdoi2016]排列计数 Time Limit: 60 Sec Memory Limit: 128 MBSubmit: 1616 Solved: 985[Submit][Statu ...
- bzoj 3165
题意: 给出平面上一些线段,在线询问与x=x0相交的线段中,交点y最大的线段的标号,支持添加线段. 大概思路: 用线段树维护,线段树每个线段记录贯穿(左右端点在该区间外或上)的原线段中能覆盖其它贯穿该 ...
- JavaScript学习笔记 isPrototypeOf和hasOwnProperty使用区别
1.isPrototypeOf isPrototypeOf是用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false. 格式如下: object ...
- [转]Android Service完全解析,关于服务你所需知道的一切
目录(?)[+] Android Service完全解析,关于服务你所需知道的一切(上) 分类: Android疑难解析2013-10-31 08:10 6451人阅读 评论(39) 收藏 举报 ...
- TortoiseSVN 图文使用教程
1 安装及下载client 端 2 什么是SVN(Subversion)? 3 为甚么要用SVN? 4 怎么样在Windows下面建立SVN Repository? 5 建立一个Working目录 ...
- Android如何检查对象的类型
The instanceof operator compares an object to a specified type. You can use it to test if an object ...