Search code examples
reactjsreact-hooks

Combining multiple useState state values into another state value


I've created two custom hooks usePagination and useSearch. These handle the logic for paging and searching and only expose getters/setters for the current page and search query.

Here's how I would use them in my list component:

const List = () => {
  const [currentPage, setCurrentPage] = usePagination()
  const [searchQuery, setSearchQuery] = useSearch()

  const queryParams = useMemo(() => ({
    page: currentPage,
    search: searchQuery
  }), [currentPage, searchQuery])

  // Pass the `queryParams` to an API endpoint to load my list items.
}

When the search query changes, I need to reset the current page to 1. Something like this:

useEffect(()=>{
  setCurrentPage(1)
}, [searchQuery, setCurrentPage])

Of course, with the above logic, this results in two API calls being made. One when the search query changes and then another immediately after when the current page value is reset.

Is there any way to combine the two values from my custom hooks currentPage and searchQuery into a combined state so that I can update both the page number and the query simultaneously? In other words, is there anything I can do here other than scrap my individual hooks and use a single state for everything?

E.g.

const [queryState, setQueryState] = useState({
  currentPage: 1,
  searchQuery: ""
})

Solution

  • Yes and you almost got it right. You could do something like this:

    const [queryState, setQueryState] = useState({
      currentPage: 1,
      searchQuery: ""
    })
    
    setQueryState((previousState) => {
      // change the state
      return previousState;
    })
    

    You could even wrap it into a hook that takes the initial state and returns the getter and a modified setter. Something like the following:

    // hook body
    const [queryState, setQueryState] = useState(initialState)
    
    const modifiedSetter = (newValues) => {
     setQueryState((previousState) => {
        return {...previousState, ...newValues};
     })
    }
    
    return [queryState, modifiedSetter]
    

    and the usage for it would be:

    const [queryState, updateQueryState] = useMyCustomHook({...someinitialState})
    
    // Well, getter works the same: queryState.searchQuery
    
    // but the setter goes like this
    
    updateQueryState({searchQuery: 'whatever'});
    // or
    updateQueryState({searchQuery: 'whatever', currentPage: 1});