'Custom hook to add query params always renders the component the using it when the location is changed. Used react-router-dom

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



Solution 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 praveen guda