-1

Problem

I have a custom hook which is used for updating the query string in the url with the help of location and history from 'react-router-dom'. So in the components whenever I want to add a new query I use this hook. In this custom hook I used useRef to avoid updating the custom hook so that the component using the hook will not render, but components using this hooks are rendered every time the location is changed.

Custom hook to update query string in the url

import { useRef } from 'react'
import { useHistory, useLocation } from 'react-router-dom'

export const useUpdateQueryString = () => {
  const history = useHistory()
  const location = useLocation()

  const routerRef = useRef({ search: '', pathname: '' })

  useEffect(() => {
    routerRef.current = { pathname: location.pathname || '', search: location.search || '' }
  }, [location.pathname, location.search])

  const onUpdateQueryString = useCallback((newQueryObj) => {
    const newQueryParamString = setQueryStringValue(newQueryObj, routerRef.current.search)
    history.push(`${routerRef.current.pathname}${newQueryParamString}`)
  }, [])

  return {
    onUpdateQueryString
  }
}

Component which uses this hook

import React, { useCallback } from 'react'
import { getTestQueryObj, useTestQueryString } from 'utils/query-string'


const TestComponent: React.FC = () => {
  const { onUpdateQueryString } = useTestQueryString()

  const handleClick = useCallback(() => {
    onUpdateQueryString(getTestQueryObj())
  }, [])

  console.log('TestComponent rendered')
  return <div onClick={handleClick}>{'Hello Component'}</div>
}

export default React.memo(TestComponent)

Note: getQueryObj is a function which gets the query obj and setQueryObj is a another function which takes the query obj and returns a string

praveen guda
  • 97
  • 2
  • 10
  • You are changing the `location` though, so the routed components will rerender. What is the question/issue? – Drew Reese Feb 22 '22 at 22:12
  • All components using this hook are getting rendered, that should not happen right? because the handlers returning from hook are memoized. Please correct me if I'm wrong. – praveen guda Feb 23 '22 at 07:17
  • The components are rerendered because the location changed, not because of simply using this `useUpdateQueryString` hook. The `useUpdateQueryString` updates the location, so it will trigger a location change, which rerenders anything in the router. – Drew Reese Feb 23 '22 at 07:33
  • Thanks for the repsone @DrewReese, yeah I'm aware that change in location will rerenders everything in the router. But it should not happen when the components inside that route are memoized. https://codesandbox.io/s/frosty-wilson-h1bbvi?file=/src/Component2.js If you the code sandbox link, I have two components which is using this hook and when I update the query from one component, the other components also gets rendered. – praveen guda Feb 23 '22 at 10:44
  • Why do you think it shouldn't happen? Why do you think the components are memoized? `React.memo` only memoizes against the props and is also only used as a hint to React to not rerender, and neither component receives any props. The docs even say not to use `memo` to prevent rerenders. In your sandbox I see two components rendered directly as children of the `HashRouter`. If the location changes, the router rerenders. When a React component rerenders it rerenders its entire subtree, i.e. all its children also rerender. – Drew Reese Feb 23 '22 at 17:57

1 Answers1

0

As you can see here I'm not using useLocation from react-router-dom which will render all the components using it, instead I used window location. My components just need a hook to add the query params to the location. Few components need info about location change so created another hook to watch the location updates.

const location = useLocation()
// Hook
export const useUpdateQueryString = () => {
  const history = useHistory()

  const onAddQueryString = (newQueryObj: Record<string, unknown>) => {
    const newLocationWithQueryParams = addQueryToLocation(newQueryObj)
    history.push(`${newLocationWithQueryParams}`)
  }

  return {
    onAddQueryString,
    onDeleteQueryStrings,
  }
}

// Method to add query to the location url
const addQueryToLocation = (value: Record<string, unknown>): string => {
  const location = window.location
  const existingQueryParamString = location.hash.split('?')[1] || ''
  const values = qs.parse(existingQueryParamString, { sort: false })
  const newQsValue = qs.stringify({ ...value, ...values }, { sort: false })
  return `?${newQsValue}`
}

https://codesandbox.io/s/pedantic-mountain-zmv4qn

You can check the link, now only Component 3 is rendered whenever the query params change.

praveen guda
  • 97
  • 2
  • 10