[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 设计并实现一个搜索组件 实时刷新 节流防抖 扩展性,封装,高内聚,低耦合 响应式 ...
随机推荐
- scrapy抓取拉勾网职位信息(八)——使用scrapyd对爬虫进行部署
上篇我们实现了分布式爬取,本篇来说下爬虫的部署. 分析:我们上节实现的分布式爬虫,需要把爬虫打包,上传到每个远程主机,然后解压后执行爬虫程序.这样做运行爬虫也可以,只不过如果以后爬虫有修改,需要重新修 ...
- [转]iOS开发new与alloc/init的区别
1.在实际开发中很少会用到new,一般创建对象咱们看到的全是[[className alloc] init] 但是并不意味着你不会接触到new,在一些代码中还是会看到[className new], ...
- Vue 2.0学习(五)v-bind及class与style绑定
DOM元素经常会动态地绑定一些class类名或style样式. 基本用法 <div id="app"> <a v-bind:href="url" ...
- 【树形DP】BZOJ1596-[Usaco2008 Jan]电话网络
[题目大意] 在一棵有n个节点的树上建信号塔,每个节点的信号塔可以覆盖当前节点极其相连的节点.问要覆盖所有节点,至少需要多少座信号塔? [思路] 经典的树形DP,直接复制一下. f[i][0]:以i为 ...
- 【贪心大水题】BZOJ3410-[Usaco2009 Dec]Selfish Grazing 自私的食草者
[题目大意] 给出n个区间,问最多选取多少个区间使得它们互相不重叠. [思路] 水题quq改善心情用.按照右端点大小排序,每次更新上一次的右端点,如果当前左端点大于上次右端点可取. #include& ...
- Linux sort 排序 去重 统计
先写一个命令: cut -d' ' -f1 ~/.bash_history|sort -d | uniq -c|sort -nr|head 这个命令可以统计你历史上输入的命令的次数的前十条 整个命令基 ...
- 1 Spring4 之环境搭建和HelloWorld
1 Spring 是什么? 具体描述 Spring: 轻量级:Spring 是非侵入性的- 基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API 依赖注入(DI --- dep ...
- 【ArcGIS笔记】数据处理
1.ARCGIS在导入Excel坐标点的时候出现"没有注册类"的情况怎么办? 确保你本机上装有office,并且版本要能够识别XLSX格式.2007以上. 2.导入excel时re ...
- POJ 2482 Stars in Your Window 线段树
如果按一般的思路来想,去求窗户能框住的星星,就很难想出来. 如果换一个思路,找出每颗星星能被哪些窗户框住,这题就变得非常简单了. 不妨以每个窗户的中心代表每个窗户,那么每颗星星所对应的窗户的范围即以其 ...
- 将dll文件注入到其他进程中的一种新方法
http://www.45it.com/windowszh/201212/33946.htm http://www.hx95.cn/Article/OS/201212/65095.html 我们知道将 ...