icon

Tec

sters

.

HomeBlogProductsWork with UsContact

Posted on Sun Jan 01 2023, 7:57:26 PM

Some problems of useState and solutions to face them

React

BLOG
blog-post

 There is no doubt that react.js is one of the most powerful web development tools that can facilitate the development, maintenance, and expansion of web-based applications and websites. However, with the addition of hooks to this tool, React entered an entirely new environment. Fewer codes and simpler syntax were among the features of this new space.

Before Hooks, State was one of the main components of React, which changed its usage with the introduction of hooks.

 

What is "state" exactly?

 

In fact, states are a type of variable that can be assigned different values. The only difference between them and a normal variable is in the way of assigning the value and the consequence of this assignment.

Here is an example of "useState" in react:

import React, { useState } from 'react';
function Comp() {
 // Declare a new state variable, which we'll call "count"
 const [count, setCount] = useState(0);
  return (
    <div>
      <p>Counts: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Count up
      </button>
    </div>
  );
}

As you can see in the above example of using useState in a simple component, setting the state is done privately through the function and it is not a simple assignment operation like other variables. On the other hand, the consequence of this assignment is rerendering on the page and as a result, the count value is displayed with the new value.

 

What is The Right way of using "useState"?

 

UseState is not a general-purpose tool in react, It is not a simple variable that can be used for any purpose. Unnecessary usage can cause non-optimality or even infinite loops. In simple words, A state is used when you want a variable not to lose its value due to a rerendering and to re-execute its dependent components (including HTML tags, components, or algorithms) after changing the value. Otherwise, you should use normal variables in appropriate scopes, and if you want these variables not to lose their value due to rerendering, use useRef

Let's Talk about its problems...

 

Asynchrony

 

The biggest structural difference between states in hooks and class components is that, unlike class components, where the change of state value happens at the same moment when using them in hooks, value assignment is an asynchronous process, although it is short (and incomprehensible by the user) but takes some time. If you don't pay attention to this issue, in some applications, you may experience seemingly illogical bugs that will bother you.

So What is the solution to this situation?

These applications are generally classified into three categories:

  1. Using the new value of state in the continuation of the algorithm:
    In this category of applications, there is a need to use this new value next to setting the state value. The solution to this problem is very simple. It is enough to use the input of "SetState()" function instead of using the state value.
    here is some example:
    import React, { useState } from 'react';
    
    
    function Example() {
       // Declare a new state variable, which we'll call "count"
       const [count, setCount] = useState(0);
    
    
       function onClickHandler(newCount) {
           setCount(newCount);
           countLog(newCount);
       }
    
    
       function countLog(value) {
           // console.log(count); this line logs previous value
           console.log(value);
       }
    
    
       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => onClickHandler(count + 1)}>Click me</button>
           </div>
       );
    }
    
  2.  using useState after state assignment is finished:
    In some cases, the work is not as easy as the first category and we don't have access to the original value, but we really have to do something after changing the state value. This situation is the best time to use useEffect. By using the dependency of a useEffect on your state, you can execute your commands in useEffect after changing the state value.
    here is an example of this situation:
    import React, { useEffect, useState } from 'react';
    
    
    function Example() {
       // Declare a new state variable, which we'll call "count"
       const [count, setCount] = useState(0);
    
    
       useEffect(() => {
           console.log(count);
       }, [count]);
    
    
       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>Click me</button>
           </div>
       );
    }
    

     

  3. Using the new value at random time:
    In the most complicated situation possible, you need to update the value of state according to its current value at random times. This means that this random time may be in the time interval required to change the state value, in which case the useEffects will no longer be used. The solution that can be used in this case is to use an auxiliary ref variable. Because the refs change in the process of assigning a new value and can store the real value for us. The only important point in using this method is that you should be very careful about creating inconsistencies and updating the ref value every time you set state.
    import React, { useEffect, useRef, useState } from 'react';
    
    
    function Example() {
       // Declare a new state variable, which we'll call "count"
       const countRef = useRef(0);
       const [count, setCount] = useState(countRef.current);
    
    
       useEffect(() => {
           console.log(count);
       }, [count]);
    
    
       function onTimeUpdateHandler() {
           countRef.current += 1;
           setCount(countRef.current);
       }
    
    
       function onClickHandler() {
           countRef.current += 1;
           setCount(countRef.current);
       }
    
    
       return (
           <div>
               <audio onEnded={onTimeUpdateHandler} src="..."></audio>
               <p>You clicked or played {count} times</p>
               <button onClick={onClickHandler}>Click me</button>
           </div>
       );
    

    For ease of work, you can define a hook according to the following code and use it:
     

    import { useRef, useState } from 'react';
    
    
    function useRealTimeState<T>(initialValue: T): { dep: T; get: () => T; set: (e: T) => void } {
       const ref = useRef<T>(initialValue);
       const [state, setState] = useState<T>(initialValue);
    
    
       const setValue = (value: T) => {
           ref.current = value;
           setState(ref.current);
       };
    
    
       return { dep: state, get: () => ref.current, set: setValue };
    }
    
    
    export default useRealTimeState;

    In this case, our component will be as follows:

    import React, { useEffect, useRef, useState } from 'react';
    
    
    function Example() {
       // Declare a new state variable, which we'll call "count"
       const count = useRealTimeState(0);
    
    
       return (
           <div>
               <audio onEnded={() => count.set(count.get() + 1)} src="..."></audio>
               <p>You clicked or played {count.dep} times</p>
               <button onClick={() => count.set(count.get() + 1)}>Click me</button>
           </div>
       );
    }
    

Ignoring setState on assigning current value to the state
 

Another difference between useState in hooks and state in class components is that when we are using useState, if the current value is equal to the previous value, the value will not change and re-render will not happen.Object.is is used to detect this equality. Although this issue is considered a strong point, it will cause us problems in cases where we want to use states as a trigger. To solve this problem, first, check your algorithm once again because in most cases the desired functionality can be implemented in a better way without using state. But if there is no other way, you can use another state with boolean or multiple rotation values for the trigger and just use the previous state value or convert it to ref.

 

Setting multiple states simultaneously
 

Sometimes it is necessary to assign several states at the same time. Here, the immediate change of variables is not the criterion, but the simultaneous change of more than one state is the problem. As an example, consider a situation where a page that displays a list of products has two filters, page and page size, to manage the list of products, and these two filters are managed by two states. Api call related to the list of products is also done in a useEffect depending on these two states.
 

import React, { useEffect, useState } from 'react';


function Example() {
   const [page, setPage] = useState(1);
   const [pageSize, setPageSize] = useState(10);
   const [list, setList] = useState([]);


   useEffect(() => {
       getDataFromServer(page, pageSize).then((result) => {
           setList(result);
       });
   }, [page, pageSize]);


   return (
       <div>
           {list.map((item) => (
               <p>{item}</p>
           ))}
           <Pagination
               pageSize={pageSize}
               page={page}
               onPageChange={(e) => setPage(e)}
               onPageSizeChange={(e) => {
                   setPage(1);
                   setPageSize(e);
               }}
           />
       </div>
   );

So far, everything seems correct, but the problem starts when we want to set the page value to 1 when changing the pageSize. Although this issue is called in two consecutive lines, in practice, due to the asynchronous setting of the states, it will create two changes and as a result, two re-render operations, which creates two consecutive API calls. One of them is pointless and even in special circumstances it can act randomly and damage the accuracy of the data. For such a situation, two solutions can be considered. First, create something like a transaction through a ref variable that takes on new values at the moment. This method does not seem logical in terms of readability. The second method is to combine the page and pageSize variables into one object variable, in this case, one or both parameters can be revalued with just one call.
 

import React, { useEffect, useState } from 'react';


function Example() {
   const [pagination, setPagination] = useState({ page: 1, pageSize: 1 });
   const [list, setList] = useState([]);


   useEffect(() => {
       getDataFromServer(pagination.page, pagination.pageSize).then((result) => {
           setList(result);
       });
   }, [pagination]);


   return (
       <div>
           {list.map((item) => (
               <p>{item}</p>
           ))}
           <Pagination
               pageSize={pagination.pageSize}
               page={page}
               onPageChange={(e) => setPagination({ ...pagination, page: e })}
               onPageSizeChange={(e) => setPagination({ page: 1, pageSize: e })}
           />
       </div>
   );
}

Conclusion

 

UseStates is one of the bases of functional components. As you read above in most cases there is no problem with using useState and it's easy to implement. But in some rare cases, we need to understand how the state works exactly. In these cases, we need to know the exact problem and use the appropriate solution. In Summary, nothing is impossible in the usage of useStates it only takes a little more attention.

Tags:

Reacthooksusestate