Downsides of Passing object as `useState` hook value.
Published: 2023-08-27 20:05:22

In this article, I'll talk about using objects with the `useState` hook. there are downsides to this approach. To explore this, let's consider a pagination component as an example. Although I'm using pagination here, similar considerations apply to other cases like when working with the `useState` hook. Imagine we have a `<Pagination/>` component.

Let's use it inside of the screen that you want to have a pagination. 
```tsx 
  function Screen() {
     const [pagination, setPagination] = useState({
         ... data // bring some defautl data from some where.  
     }); 
    
     // bringing other pagination related functions in the body of this component
 
     return ( 
        <div> 
           <Pagiantion {...pagination}/>
        </div>
     )
  }

```  
Is this logic related to pagination functionality reusable? The answer is "No." This is because the reusable logic is placed on the consumer side. To reuse it, you would need to copy the code from the component and paste it elsewhere. This might sound straightforward, but it's not ideal. By encapsulating all the pagination data within an object, maybe  people might miss the opportunity to create a reusable custom hook named `usePagination`.

This is one of the main reasons why I believe using objects as values for the `useState` is problematic. When we use an object as the value for the `useState` hook, we miss out on a significant chance for reusability. So, what's a better approach? The solution lies in writing a custom hook.

Let's take a step further and imagine that we've decided to implement a custom hook. However, even in this scenario, we continue to use an object for the `useState` hook. The code snippet below illustrates 2 version of the implementation of the `usePagination` hook.


```tsx 
export const usePagination = (paginationProperties) => {
  const [pageLimit, setPageLimit] = useState(paginationProperties?.pageSize || 40);
  const [current, setCurrent] = useState(paginationProperties?.currentPage || 1);
  const [hasNext, setHasNext] = useState(paginationProperties?.hasNext || false);
  const [hasPrevious, setHasPrevious] = useState(paginationProperties?.hasPrevious || false);
  const [totalPages, setTotalPages] = useState(paginationProperties?.totalPages || 0);
  const [totalCount, setTotalCount] = useState(paginationProperties?.totalCount || 0);

  const nextPage = () => {
    if (hasNext) {
      setCurrent((current + 1) % (totalPages + 1) || 1);
    }
  };

  const previousPage = () => {
    if (hasPrevious) {
      setCurrent((current - 1 + totalPages) % (totalPages + 1) || totalPages);
    }
  };

  const skipBack = () => setCurrent(1);  
 
  const skipForward = () =>  setCurrent(totalPages);

  const resetPagination = () => {
    setPageLimit(10);
    setCurrent(1);
  };

  const goToPage = (pageNumber: number) => {
    setCurrent(pageNumber);
  };

  // return everything that Pagination component needs to work with 
  return ...; 
};

``` 
 

```tsx 
export const usePagination = (paginationProperties) => {
  const [pagination, setPagination] = useState({
    pageLimit: paginationProperties?.pageSize || 40,
    current: paginationProperties?.currentPage || 1,
    hasNext: paginationProperties?.hasNext || false,
    hasPrevious: paginationProperties?.hasPrevious || false,
    totalPages: paginationProperties?.totalPages || 0,
    totalCount: paginationProperties?.totalCount || 0,
  });

  const nextPage = () => {
    if (pagination.hasNext) {
      setPagination(prevPagination => ({
        ...prevPagination,
        current: (prevPagination.current + 1) % (prevPagination.totalPages + 1) || 1,
      }));
    }
  };

  const previousPage = () => {
    if (pagination.hasPrevious) {
      setPagination(prevPagination => ({
        ...prevPagination,
        current: (prevPagination.current - 1 + prevPagination.totalPages) % (prevPagination.totalPages + 1) || prevPagination.totalPages,
      }));
    }
  };

  const skipBack = () => setPagination(prevPagination => ({
    ...prevPagination,
    current: 1,
  }));

  const skipForward = () => setPagination(prevPagination => ({
    ...prevPagination,
    current: prevPagination.totalPages,
  }));

  const resetPagination = () => {
    setPagination(prevPagination => ({
      ...prevPagination,
      pageLimit: 10,
      current: 1,
    }));
  };

  const goToPage = (pageNumber) => {
    setPagination(prevPagination => ({
      ...prevPagination,
      current: pageNumber,
    }));
  };

  // return everything that Pagination component needs to work with 
  return ...;
};

```

Let's delve into the drawbacks of using an object as the `useState` value:

**complexity:** 
Let's compare the complexity of both hook versions and see which one is clearer. Clearly, the first version is more straightforward. Some argue that using an object as the hook value makes code tidier by consolidating related data. However, I find this less compelling. The `usePagination` hook, as its name suggests, should handle all relevant aspects. As the state object grows, managing and debugging can become more intricate. Another option is to use the useReducer hook, designed for situations where states need to be collocated. 

**Immutability**: 
When you're using the `setState` function or its equivalent with an object as the value, it's crucial to ensure immutability while updating a single property within that object. Sometimes, individuals resort to third-party libraries like 'Immer' to simplify managing immutability during updates. do you need another package for just updating your state ? 

**Shallow Comparison:** 
React's default behavior for comparing state updates involves shallow comparisons of objects, which might lead to unexpected behavior if not managed carefully.

**Performance:**
While using objects can be performant for many cases, extremely large or deeply nested state objects might impact performance.
 
In conclusion, using an object as the `useState` value in React might make your code look organized and readable, but it can be misleading. This method might hinder your ability to create custom hooks and understand your components' needs fully. You could miss chances to make your code more reusable.

Moreover, this approach requires you to carefully think about aspects like complexity, immutability, and potential performance issues. It's crucial to understand the downsides of using an object with `useState`.