Skip to content

<Async.Fulfilled>'s function gets called with a mix of old&new data on subsequent updates #61

Open
@slawomir-brzezinski-at-clarksons

Description

Hi. This seems like a new case for #8 , except that one was closed based on side effects in render. Here we have a valid side-effect in componentDidUpdate that gets triggered unexpectedly.

Essentially, we have a

loadRootEntityAsync({ rootEntityId }) {...}

render:
              <Async
                promiseFn={this.loadRootEntityAsync}
                rootEntityId={rootEntityId}
              >
                <Async.Fulfilled>
                  {rootEntity => (
                    <RootEntity
                      rootEntity={rootEntity}
                      selectedChildEntityId={selectedChildEntityId}
                    />
                  )}
                </Async.Fulfilled>
              </Async>

Note that selectedChildEntityId can be null, when the user doesn't navigate using a precise URL, but then there's some logic in <RootEntity> that will select the appropriate child entity - this logic happens in <RootEntity>'s componentDidUpdate.

So, it works perfect on the show of the first root entity, but when user navigates to another root entity (by changing rootEntityId and setting selectedChildEntityId to null), the old <Async.Fulfilled>, just before the <Async.Pending> for the new fetch, gets another invocation with the old rootEntity, and empty selectedChildEntityId, which triggers a new finding of selectedChildEntityId, resetting the user navigation. This is the usual shape of the problem, but if the user navigates straight to a specific selectedChildEntityId (e.g. using browser back button or using a direct link) it will result in creating a component tree where the new child entity is in the context of an old root entity. This, will throw an error in the best case, but might as well cause corruption, depending on what the components do in their lifecycle methods.

For now, the workaround is to use <Async key={rootEntityId}>, a common technique for preventing components reuse, which prevents that last unnecessary firing of the <Async.Fulfilled>'s function.

It seems though this could be prevented elegantly by preventing the render of the <Async.Fulfilled>'s function the moment the arguments to promiseFn change. Instead of that one, <Async.Pending> could be used in this render, which, semantically, seems to be even more correct - another execution of the async action is imminent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementEnhancement to existing feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions