Skip to content

Render re-entering after async function #186

Open
@k1w1

Description

@k1w1

React Easy State version: 6.3.0
Platform: browser

There is another subtle problem after calling an async function. Array mutation causes immediate re-rendering for each fundamental operation (set, get, etc). This will cause the list to re-render while it is in an internally inconsistent state.

import React from 'react';
import { store, view } from '@risingstack/react-easy-state';

async function longFunction() {}

const counterStore = store({
  counters: []
});

counterStore.counters.push({value: 1});
counterStore.counters.push({value: 2});
counterStore.counters.push({value: 3});

export default view(() => {
  const updateList = async () => {
    await longFunction();
    
    const c = counterStore.counters;
    console.log("Before removing item")
    c.splice(0, 1);
    console.log("After removing item")
  }

  console.log("Current list:")
  counterStore.counters.forEach((counter, index) =>
    (console.log(`${index}: ${counter.value}`))
  );

  return (
    <>
      <button onClick={() => updateList()}>Update list</button>
      <ul>
        {counterStore.counters.map((counter, index) => (
          <li key={index}>{counter.value}</li>
        ))}
      </ul>
    </>
  );
});

Which generates the output:

Current list:
0: 1
1: 2
2: 3
Before removing item    <- Button is clicked
Current list:
0: 2                                  <- Note that value 2 is duplicated
1: 2
2: 3
Current list:
0: 2
1: 3                                    <- Now value 3 is duplicated
2: 3    
Current list:
0: 2                                    <- length is updated so list is correct again
1: 3
Current list:
0: 2
1: 3
After removing item

In this output we are seeing the internal implementation of splice as it copies down the array elements, and then finally updates the length. This is a problem for React rendering because if the value is being used as a key value then the React display will become corrupted because of the duplicated keys. There will be a warning, but then you will also see duplicated items even after all of the renders have finished.

It seems that some kind of batching is required. There is batching around the event callback, but the async function closes the batch and subsequent changes are not batched.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions