Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTK Query - Infinite loop when arg is nested object #1526

Closed
lukascivil opened this issue Sep 19, 2021 · 6 comments
Closed

RTK Query - Infinite loop when arg is nested object #1526

lukascivil opened this issue Sep 19, 2021 · 6 comments
Milestone

Comments

@lukascivil
Copy link

lukascivil commented Sep 19, 2021

The problem

image

The problem infinite loop with multiple calls occurs when filter is an object inside another object, which assembles arg.

  const [filter, setFilter] = useState({email: "cafe@gmail.com"})

If filter is a direct value, as string or number, or the object with the same key, the problem does not occur, example:

  const [filter, setFilter] = useState("cafe@gmail.com")

The "useGetUsersQuery" (example below) hook will make multiple calls as if it were in loop.

I don't know if this is expected behavior with wrong use or a possible bug, however I would like to understand the hook behavior based on prev Arg and next Arg, how does Hook handle this?

Full example with infinite loop

export const TabLocalList: FC = () => {
  const [filter, setFilter] = useState({ email: '' })
  const currentSort = { field: 'created_at', order: 'ASC' }
  const { isFetching, data, refetch } = useGetUsersQuery({
    filter,
    pagination: { page: 1, perPage: 50 },
    sort: { field: 'created_at', order: 'ASC' }
  })

  return (
    <LocalList
      isLoading={isFetching}
      data={data?.data}
      onRefresh={refetch}
      onSubmit={values => {
        setFilter(values as any) // {created_at_start: "..."}
      }}
      sort={currentSort}
      filters={[<TextInput source="created_at_start" validate={required()} />]}
    >
      <Datagrid>
        <TextField source="id" label="Id" />
        <TextField source="email" label="E-mail" />
      </Datagrid>
    </LocalList>
  )
}

I did some examples and got different results

      onSubmit={values => {
        // Works ok
        // setFilter({ email: values?.email } as any)

        // Works ok (If values were {email: "..."}) , same key with diferent value
        // setFilter(values as any)

        // Works ok
        // setFilter({ email: '55' } as any) // OK

        // Crash with infinite loop, add a new prop
        // setFilter({ email: '55', created_at_start: '' } as any)

        // Crash with infinite loop, remove email and use new key for diferent filter
        // setFilter({ created_at_start: '' } as any)
      }}

From the above results, I believe the internal check to see if the args have changed may be causing this issue.

@lukascivil
Copy link
Author

lukascivil commented Sep 20, 2021

I did another test with Pokemon example. I changed the parameter to nested object and it works, but when I change the route - from pokemon/ to pokemong/ - to force the 404 error, I get "infinite loop"

https://codesandbox.io/s/boring-voice-9n2xk?file=/src/services/pokemon.ts

(Test)
image

(Error)
image
image

Why this behavior when I have nested object? Is it a bug?

@phryneas
Copy link
Member

These are two different things.

That you have to keep nested arguments stable is documented.:

The queryArg param is handed to a useEffect dependency array internally. RTK Query tries to keep the argument stable by performing a shallowEquals diff on the value, however if you pass a deeper nested argument, you will need to keep the param stable yourself, e.g. with useMemo.

Then, there is a "known issue"/"non-optimal behaviour" that if you mount a component with an error state, it will make another request for that endpoint. This is not optimal, but also I don't know what would be a better way of handling this, since most of the time a new request is intended.

The question is how that leads into your infinite loop, since it seems that you are not remounting any components anywhere. 🤔

I will try to look into that later, but if you cal already come up with a theory, I'm all up for that :)

@Shrugsy
Copy link
Collaborator

Shrugsy commented Sep 20, 2021

The question is how that leads into your infinite loop, since it seems that you are not remounting any components anywhere.

IMO the loop is roughly like so:

  1. component renders with arg 0, query is fired off
  2. query resolves with an error, and the query hook returns selected values, re-rendering the component.
  3. simultaneously, the query hook receives a new arg: arg 1.
  • since the arg changed (due to being unstable from nested objects), a new initiate call is dispatched
  • since the last result was an error, the call proceeds to re-attempt the query (a successful query would also dispatch initiate due to the arg change, but would abort due to having an existing cache value, and not re-render the component)
  • loop goes back to step 2 - resolving with an error, re-rendering etc.

So it's expected that a constantly changing arg + failing query causes an infinite loop.

Note: If you use selectFromResult on the query to limit the values returned by the hook, it can break the infinite loop as expected. E.g. by returning an empty object from selectFromResult, once the query resolves with an error, it doesn't inherently re-render the component itself.

@Malaka98
Copy link

Malaka98 commented Nov 2, 2021

You can use the skip parameter. Ex: First create a global variable and set it to false. Then initialize it to the skip parameter. If an error occurs in the rtk query when the component is rendered, it can be captured by if (error) {}. Now make that variable true in the if the condition

Ex: >>>>>

let error = false

const component = () => {
const {data, error} = useQuery(payload, {skip: error})

     if(error){
         error = true
     }

  return(
      <>
      </>
)

}

@markerikson
Copy link
Collaborator

#1533 addressed this and is available in the 1.7 betas.

@irzhywau
Copy link

#1533 addressed this and is available in the 1.7 betas.

saved my day, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants