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

infinite render loop with data table components #1588

Closed
cdreier opened this issue May 9, 2023 · 19 comments
Closed

infinite render loop with data table components #1588

cdreier opened this issue May 9, 2023 · 19 comments

Comments

@cdreier
Copy link

cdreier commented May 9, 2023

Hi,

in version 3.0.0-alpha-1b4ddaba.0 i am running in infinite render loops when using https://v2.grommet.io/datatable or https://react-data-table-component.netlify.app/?path=/story/getting-started-intro--page

the component renders over and over - when downgrading to 2.3.0 everything is working fine

import { DataTable, Text } from "grommet"
import { useQuery } from "../gqty"


const Start = () => {

  console.log("render loooop!")
  const q = useQuery()

  return (
    <div>
      <DataTable
        data={q.students()}
        columns={[
          {
            property: 'lastname',
            header: <Text>Name</Text>,
          },
        ]} />
    </div>
  )
}

@vicary
Copy link
Member

vicary commented May 9, 2023

@cdreier Thanks again for reporting the issue.

I don't think I am familiar with Grommet enough, it throws the following error when I try to add <DataTable /> in a freshly created next.js app.

TypeError: defaultValidatorFn is not a function
Error Source Code
image image

Would you mind sharing a minimum reproducing repo?

@vicary
Copy link
Member

vicary commented May 9, 2023

I can reproduce the issue now, styled-components just released v6 RC and it's breaking grommet.

I'll look into it.

@cdreier
Copy link
Author

cdreier commented May 9, 2023

Grommet is worth a look, also with the hpe theme and styleguide ;)

i did not upgrade styled-components yet, so glad you found the issue! thanks again for your work and fast support!

@vicary
Copy link
Member

vicary commented May 10, 2023

<DataTable /> is reading GQty's proxy outside of a render cycle, where GQty assumes to be a user interaction, e.g. button click, triggering another render.

@cdreier I tried replacing the re-render with a soft refetch in 3.0.0-alpha-d01132d2.0, give it a try!

@cdreier
Copy link
Author

cdreier commented May 10, 2023

Hi @vicary - just tested the new release in my project and it is working pretty well now :)

thanks a lot!

@vicary
Copy link
Member

vicary commented May 10, 2023

I'm closing this for now, feel free to reopen if it happens again!

@vicary vicary closed this as completed May 10, 2023
@cdreier
Copy link
Author

cdreier commented May 11, 2023

Hi @vicary ,

i created a repo with another few strange bugs, also fetching loops in certain conditions - i am not sure if grommet is doing strange things, but with GQty 2.3 everything was working

i created a small graphql server where you can test against, and i added descriptions on the passengers page.

short description of the issues

  • every hover over a row, triggers a fetch from the server - even if the table does something with the proxy, there should be a cache?
  • there are strange dependencies between different useQuery hooks - it is hard to describe, so just open the passengers page and click one passenger with your network monitor open 🚀

https://github.com/cdreier/gqty_rendering

i really like the idea and philosophy behind GQty - feel free to ping me if you have other questions or if i can help at some point

edit: and i think i am not allowed to reopen this issue?

@vicary vicary reopened this May 12, 2023
@vicary
Copy link
Member

vicary commented May 12, 2023

Hey @cdreier, I have reopened the issue. Replying your points below:

every hover over a row, triggers a fetch from the server - even if the table does something with the proxy, there should be a cache?

The new cache defaults to a more aggressive SWR refetching, it compounded with the aggressive reading in grommet.

Try increasing the maxAge to something like 5 seconds, it would greatly reduce the fetching frequency. You may also put maxAge: Infinity to disable expiry entirely.

there are strange dependencies between different useQuery hooks - it is hard to describe, so just open the passengers page and click one passenger with your network monitor open 🚀

Passing child proxies upwards to parent would mess up query scopes a little bit, you may prop drill from the parent <Passengers data={query.passengers()} /> to prevent this for now.

I'll figure out a way to force them into one single fetch, so they don't race with each other.

@cdreier
Copy link
Author

cdreier commented May 12, 2023

Hi @vicary - you are right, it seems like for my usecases, a disabled cache expiry solves lots of problems! I try to dig a bit deeper during the weekend and provide more feedback 👍

@vicary
Copy link
Member

vicary commented May 13, 2023

@cdreier Ended up hashing all variable names to properly dedupe this, but with alpha-4c58a180.0 I think the racing condition is gone now. Please feel free to give it a test, I'll wait for this one before announcing beta.

@cdreier
Copy link
Author

cdreier commented May 13, 2023

no changes for me with the default settings maxAge: 0 - and even with maxAge: 5000 it happens when you hover the modal and the cache refreshes - maxAge: Infinity was the only working solution for now

Bildschirmaufzeichnung.vom.2023-05-13.23-21-04.webm

@cdreier
Copy link
Author

cdreier commented May 13, 2023

and i found another issue in the latest version with the cache normalization - the rerendering does not understandable things... when disabling the normalization, it is working

Bildschirmaufzeichnung.vom.2023-05-13.23-27-11.webm

@vicary
Copy link
Member

vicary commented May 14, 2023

Yes I can reproduce it, sorry for making a hasty fix and I should test it more thoroughly. Feels like it is triggered by something else, I'll take a look.

For the second one, with updatedPassenger?.id alone, mutation and normalization shouldn't work. A selection of passenger.id and passenger.car.id should be the minimum information for a normalized cache to update the reference.

I have tested with the old versions and the car column in passenger table is still not updating after clicking. What is the expected behaviour?

@cdreier
Copy link
Author

cdreier commented May 14, 2023

you are right, now i can see the same behaviour with the old version 😕

the expectation was, as shown without the cache normalization - the update runs, i can see in the network tab, that the data in the response is correct, and the view should update to the correct car name. I did not find a way to make it work with normalization, even if i fetch all the attributes from the updatedPassenger

@vicary
Copy link
Member

vicary commented May 15, 2023

I am not sure how deep this is gonna get, let's go as far as we are comfortable with.

I'll show my investigation process below, let me know if you see anything else worth trying.

Click to see investigation progress

Started a new repo and try to reproduce it bottom-up, the goal is to bisect my way to your repo to pin down the root cause.

Your API is returning empty arrays, so I am falling back to my toy API. Reduced the codebase a little bit, but keeping the fetch loop there.

https://github.com/vicary/gqty_rendering/blob/master/src/App.tsx

Screen.Recording.2023-05-15.at.14.00.30.mov

Findings

  1. In gqty_rendering we don't need <DataTable /> to make it happen.
  2. In gnt it doesn't happen even if grommet elements are used, e.g. Layer, DataTable, Grommet and hpe theme.
  3. gqty@^3.0.0-alpha-4c58a180.0 will install older version of alpha because our canary release is using commit hashes, but pinning down to 4c58a180.0 still reproduces it.
  4. Removing all grommet related things from gqty_rendering still reproduces it.
  5. Using separated useQuery() for both elements can reliably trigger the issue.

Tested Scanarios

  1. Rendering a data array with simple <ol>, it only triggers one more query and it is the expected behavior.

    Screen.Recording.2023-05-15.at.00.40.53.mov
  2. Replacing the <ol> with <DataTable />, still works.

    Screen.Recording.2023-05-15.at.00.45.47.mov
  3. Adding onClickRow, this is where it re-renders on hover. Fetches are firing from my cursor movements, but it's not looping.

    Screen.Recording.2023-05-15.at.00.46.00.mov
  4. Adding hpe theme, still no avail.

    https://github.com/gqty-dev/gqty/blob/feat/scoped-query/examples/gnt/app/backflow/page.tsx

    Screen.Recording.2023-05-15.at.13.56.22.mov

I can pin down the reproduction now, working on it.

@vicary
Copy link
Member

vicary commented May 15, 2023

@cdreier Both minreps are no longer looping with alpha-524ea531.0, we can move on to the mutation issue when you confirm it working.

Note: Don't use caret selection in package.json, e.g. ^3.0.0-alpha..., some old hashes may be lexically sorted upwards and I am gonna replace this flaky release action when we go beta. Instead, use exact version like this "3.0.0-alpha-524ea531.0".

@cdreier
Copy link
Author

cdreier commented May 15, 2023

@vicary i am a bit short in time, but i discovered one thing: it only loops (still) when a proxy is used in the modal!

Bildschirmaufzeichnung.vom.2023-05-15.17-22-20.webm

here, i am using {activePassenger?.name} - if i remove the name, no loops happen

{modalOpen && (<Layer position={'top'} onEsc={() => setModalOpen(false)} >
        <Box pad="medium" gap="medium" overflow="auto">
          <h1>assign {activePassenger?.name}</h1>
          <Cars onClick={c => {
            updatePassenger({
              args: {
                carID: c?.id,
              }
            })
          }} />
        </Box>
      </Layer >)}

editing: added a short clip

and thanks a lot for all your work, this is awesome!

@vicary
Copy link
Member

vicary commented May 16, 2023

With alpha-085ddbf4.0 your repo is no longer looping, moving on to mutations.

So when you're querying two lists, refetching is the only way.

There are two top-level cache key at play:

  1. query.cars, and
  2. query.passengers

When you update the car of a passenger, you can only query the new car, thus update its population, leaving the population of the old car dangling/stale.

With the mutation response alone, we have no way to directly update the top level cache key query.cars, post-mutation refetching is required in this scenario.

i am a bit short in time

I'll leave this open until Friday, but you may leave a comment here if you're still hitting anything.

@cdreier
Copy link
Author

cdreier commented May 17, 2023

i can confirm, no more loops! 🚀

@vicary vicary closed this as completed May 18, 2023
vicary added a commit that referenced this issue Apr 21, 2024
* feat(package/core): scoped query
* feat(package/subscriptions): drop the package for graphql-ws
* feat(packages/utils): drop unused package
* fix(packages/logger): scoped query shim
* feat: refactor resolvers for react
* chore: error message
* feat(packages/react): new core shim
* fix: facebook/react#26230
* fix: generated subscription client
* feat(packages/gqty): compat with queryFetcher and subsscriptionsClient
* chore(compat): queryFetcher and subscriptionsClient
* fix(package/react): infinite render loop
* feat(package/gqty): separate Cache instantiation from client
* chore(package/gqty): compat and optimized normalization
* fix(package/react): react hooks
* chore(deps): upgrade multidict
* feat(package/gqty): rename fetchPolicy to cachePolicy
* feat(package/gqty): add reload in cachePolicy
* fix(package/react): consistent API
* feat(package/react): Added onComplete for useMutation
* feat(packages/cli): interactive mode
* feat(examples): added the gnt example
* fix(package/react): missing fetches in useQuery
* fix(package/react): incorrect suspense in usePrepared
* feat(package/cli): rename introspectionOptions to introspections
* fix(deps): upgrade faulty multidict
* fix(ci): use esm imports
* fix(packages/gqty): compat, cache option should be optional.
* fix(ci): dlx errors
* feat(package/gqty): batching with microtask
* chore(package/cli): refactor generated client
* chore: remove legacy website
* feat(package/gqty): passthru return types of selectors
* chore(deps): upgrade testing-library for React 18
* fix(examples/vite): compatibility with v3
* chore(package/gqty): warn about empty selections
* fix(packages/gqty): pure peer dep of graphql-ws
* feat(package/gqty): edge compatible microtask
* chore(examples/github): remove stale example
* chore(deps): upgrade and dedupe typescript
* feat(package/gqty): added extensions option
* fix(packages/react): defaults to soft  to match the docs
* fix(packages/react): better simulate useTransactionQuery
* chore(docs): self-contained images in README.md
* fix(packages/react): flaky waitFor
* chore(deps): make type-fest optional
* feat(package/cli): error handling in generated client
* chore(package/cli): code-splitting for watch mode
* chore(deps): remove ws dependency
* fix(package/react): infinite render loop
* fix(package/react): early cache hydration
* fix(package/react): default  mode
* feat(core): even more stickier fetches
* chore: verbatimModuleSyntax
* chore: test grommet fetch loop
* fix(package/react): properly dispose cache subscribers
* chore(deps): update esbuild
* feat(cli): prompt for destination
* fix(packages/react): infinite fetch via stale proxy #1588
* feat(examples/gnt): add bundle analyzer
* fix(cli): compat with config.introspection
* fix(cli): fetch all schemas before merging
* fix(cli): install peer dep 'graphql'
* fix(cli): allow disable subscription via arg
* fix(packages/cli): typo on options definition
* feat(cli): add negative options
* fix(gqty): safe auto-selection on nullable interfaces
* feat(gqty): remove default return from resolve()
* fix(package/react): clear selections post-fetch in useQuery (#1594)
* chore(compat): normalize import.meta.url to increase compatibility.
* fix(package/gqty, package/react): error handling
* feat(chore): prettier v3
* fix(cli): revert prettier to v2, deps are not ready for it
* fix(package/react): reduce over-fetching between renders #1594
* feat(cli): enable suspense in the generated client
* chore(examples/gnt): simulate noverby's infinite fetch bug
* fix(package/react): reduce infinite fetch when normalization is disabled
* fix(package/react): prevent non-query types from sticky fetching
* chore(ci): test theguild's snapshot action
* fix(cli): missing default import in watch mode
* feat(cli): do not exit on fetch errors during watch mode
* chore(deps): remove unused packages
* chore(lint): add prettier-plugin-jsdoc
* feat(package/react): add extensions option to useMutation
* chore(prettier): remove jsdoc plugin to prevent noisy changes
* feat(package/react): increase compatibility with non-web environments
* fix(package/react): prevent infinite fetches
* feat(package/react): experimental greedy fetch
* feat(package/gqty): expose aliasLength option
* feat(package/gqty): supress empty warnings when onEmptyResolve is specified
* chore(exampels/vite-react): upgrade deprecated deps
* feat(package/logger): fix fetch timer
* chore(packages/gqty): user communication on fallbacks
* chore(ci): fix release errors
* chore(packages/gqty): prevent fetching stale inputs
* chore(package/gqty): add mutation test for the new core
* chore(examples/vite-react): clean up for suspense
* fix(package/gqty): properly batch with microtask
* feat(package/cli): allow force disabling of react
* feat(package/react): add initialLoadingState option
* chore(package/gqty): tidy up file structure
* fix(packages/react): prevent fetch loops from cofetching resolvers
* fix(packages/gqty): disable initialLoadingState upon first fetch
* fix(package/gqty): rerender after first fetch to refresh isLoading
* chore(examples/gnt): upgrade nhost client
* chore: fix type resolutions in esm
* chore(packages/cli): TypeScript v5 friendly type imports
* chore(packages/react): reduce redundant iterations
* chore(deps): upgrade pnpm
* chore(examples/gnt): preparation work for Supabase example API
* chore(package/react): remove experimental feature
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants