HomeBlogErrors / FixesWhy does React render an intermediate state between two setState calls? [duplicate]
Errors / FixesSeptember 5, 20263 min

Why does React render an intermediate state between two setState calls? [duplicate]

Why React Displays Intermediate State Between setState Calls ## Introduction During the development of user interfaces, it is common to encounter situations where unexpected intermediate states may appear between...

Why does React render an intermediate state between two setState calls? [duplicate]
Why does React render an intermediate state between two setState calls? [duplicate] - image 2

Why React Displays Intermediate State Between setState Calls

Introduction

During the development of user interfaces, it is common to encounter situations where unexpected intermediate states may appear between calls to setState in React. This can be particularly noticeable on pages where a user performs multiple actions consecutively. In this article, we will explore an example from real-world development related to address search and displaying results.

Implementation Example

Consider the AddressSearch page, which allows users to enter an address, receive suggested options from the Google Maps API, and display the results on the screen. The crucial parts of the code are shown below:

export const useSystemAddressStrategy = ({ companyId, gMaps, initialAddress }: SystemAddressStrategyOptions): StrategyResult => {
    // ----- Address
    const [addressParams, setAddressParams] = useState<AdvancedSearchAddressPayload & { formattedAddress: string }>(convertToAddressPayload(initialAddress));

    // ----- Page
    const [page, setPage] = useState<number>(0);

    // ----- Sort Direction
    const [sortDirection, setSortDirection] = useState<SortDirection>(SortDirection.ASC);
};

Main Actions

  1. Changing the Address: When the address is changed, all UI data is cleared, a new request is made with the updated address, and the data is displayed.
  2. Changing Sorting Direction: When the sorting direction is changed, all UI data is also cleared, a new request is made with the updated sorting direction, and the data is displayed.
  3. Scrolling to the End of the Page: When scrolling to the end of the page, a new API request is made with an incremented page number, and new data is added to the existing data.

Intermediate State Problem

The problem lies in the fact that between calls to setState, an intermediate state may appear that does not match any of the expected states. This happens due to the asynchronous nature of API calls and component state updates.

How It Works

When a user performs an action, such as changing the address, React updates the state and renders the component. However, until the new API request completes and updates the state, the intermediate state may be visible on the screen.

Example

Suppose a user enters a new address. React updates the state and renders the component with a cleared UI. Afterward, an API request is made. Until the request completes, the user may see an intermediate state where some data has already been updated while other data has not.

Solution to the Problem

To avoid intermediate states, various approaches can be used:

1. Using Loading Flags

Add loading flags for each action to display a loading state instead of an intermediate state.

const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
    if (isLoading) {
        return;
    }

    setIsLoading(true);

    fetchNewData()
        .then(data => {
            // Update state after receiving data
            setPage(0);
            setAddressParams(convertToAddressPayload(newAddress));
        })
        .finally(() => setIsLoading(false));
}, [isLoading, newAddress]);

2. Freezing the UI

Freeze the UI during the request and unfreeze it after receiving the data.

const handleAddressChange = (newAddress) => {
    setIsLoading(true);
    fetchNewData(newAddress)
        .then(data => {
            setPage(0);
            setAddressParams(convertToAddressPayload(newAddress));
        })
        .finally(() => setIsLoading(false));
};

3. Managing State Inside Functions

Use functional methods to manage state inside event handlers.

const handleAddressChange = (newAddress) => {
    const fetchData = async () => {
        setIsLoading(true);
        await fetchNewData(newAddress);
        setPage(0);
        setAddressParams(convertToAddressPayload(newAddress));
        setIsLoading(false);
    };

    fetchData();
};

Practical Tips

  1. Use useEffect to manage state after API requests.
  2. Add loading flags to display a loading state.
  3. Freeze the UI during requests.
  4. Use functional methods to manage state.

Conclusion

Intermediate states between calls to setState can cause problems in React applications. Using loading flags, freezing the UI, and functional methods will help manage state more effectively and avoid unwanted intermediate states.