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
原文:https://www.cnblogs.com/Answer1215/p/10393228.html