Search code examples
reactjsreact-reduxgraphqlreact-hooksreact-lifecycle-hooks

How to use a GraphQL hook in a base app component in react without re-rendering?


I am trying to use redux with GraphQL const sliceQuery = useQuery(ALL_SLICES) on React but it seems as if i cant use useEffect to minimize renders due to GraphQL hooks and their promise Object { data: {}, variables: {}, refetch: (), fetchMore: (), updateQuery: (), startPolling: (), stopPolling: (), subscribeToMore: (), loading: true, networkStatus: 1, … }. So instead i am forced to update props.slices outside of a useEffect since i have to wait for the GraphQL hook to finish loading with the following:

if (!sliceQuery.loading && props.slices === null) {
  const allSlices = sliceQuery.data.allSlices
  props.initializeSlices(allSlices)
  props.setCurrentSlice(allSlices.find(s => s._id === splitUrl[5]))
}

Here is my full App.js component:

import React, { useEffect } from 'react'
import { initializeSlices } from './reducers/sliceReducer'
import { setCurrentSlice } from './reducers/currentSliceReducer'
import { connect } from 'react-redux'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import { useQuery } from '@apollo/react-hooks'
import NavBar from './components/navBar/NavBar'
import Slice from './components/pages/Slice'
import About from './components/pages/About'
import Policies from './components/pages/Policies'
import Contact from './components/pages/Contact'
import './static/css/base.css'
import { ALL_SLICES } from './schemas'

const App = (props) => {
  console.log('App.js is ran')
  const sliceQuery = useQuery(ALL_SLICES)
  const splitUrl = window.location.href.split('/')
  if (!sliceQuery.loading && props.slices === null) {
    const allSlices = sliceQuery.data.allSlices
    props.initializeSlices(allSlices)
    props.setCurrentSlice(allSlices.find(s => s._id === splitUrl[5]))
  }

  const getSliceById = (id) => {
    return props.slices.find(s => s._id === id)
  }

  if (!props.slices || !props.currentSlice) {
    return (
      <div>
        <h1>loading..</h1>
      </div>
    )
  }
  return (
    <div className="wrapper">
      <Router>
        <NavBar />
        <Route exact path="/" render={() => <Slice slice={getSliceById('5e2db26efa3d070ec879b0e9')} /> } />
        <Route path="/slice/:name/:id" render={({match}) => <Slice slice={getSliceById(match.params.id)} /> } />
        <Route path="/about" render={() => <About /> } />
        <Route path="/policies" render={() => <Policies /> } />
        <Route path="/contact" render={() => <Contact /> } />
      </Router>
    </div>
  );
}
const mapStateToProps = (state) => {
  return {
    slices: state.slices,
    currentSlice: state.currentSlice,
  }
}

export default connect(
  mapStateToProps,
  { initializeSlices, setCurrentSlice }
)(App)

Here is the console log of just the page loading:

The development server has disconnected. Refresh the page if necessary.

[HMR] Waiting for update signal from WDS...

App.js is ran

./src/App.js Line 1:17: 'useEffect' is defined but never used no-unused-va

Warning: Render methods should be a pure function of props and state; triggering nested component updates from render is not allowed. If necessary, trigger nested updates in componentDidUpdate. Check the render method of App.

App.js is ran

App.js is ran

App.js is ran


Solution

  • You can still use useEffect to only execute your methods when your data becomes available.

    const allSlices = 
        !sliceQuery.loading && props.slices === null 
            ? sliceQuery.data.allSlices 
            : null;
    
    useEffect(() => {
        if(allSlices !== null) {
            props.initializeSlices(allSlices)
            props.setCurrentSlice(allSlices.find(s => s._id === splitUrl[5]))
        }
    }, [allSlices, props.initializeSlices, props.setCurrentSlice])
    

    This will only call the methods props.initializeSlices and props.setCurrentSlice when allSlices becomes available.

    If these methods are also created at render time, you may need to use useCallback where they are created to stop them triggering the useEffect hook to execute on subsquent renders.