apollographql / apollo-feature-requests

🧑‍🚀 Apollo Client Feature Requests | (no 🐛 please).
Other
130 stars 7 forks source link

Any plans on adding useFragment() hook and/or subscribing to a fragment change? #198

Open dko-slapdash opened 4 years ago

dko-slapdash commented 4 years ago

Hi.

There is useQuery(), but there is no useFragment() which would allow to watch some fragment in the cache and re-render accordingly.

An example use-case would be the following:

const UserFragment = gql`
  fragment UserFragment on User {
    id
    name
  }
`;

const userFragment = gql`
  query User($id: ID!) {
    viewer {
      user(id: $id) {
        ...UserFragment
      }
    }
  }
  ${UserFragment}
`;

function MyComponent({ id }) {
  // If the user is already somewhere in the cache (most likely it is!),
  // return its data immediately.
  const { data } = useFragment(UserFragment, id);
  // In case the user is not in the cache yet, query it explicitly by ID
  // from some parametrized field.
  useQuery(userFragment, { id });
  // Render the user's name fetched by useFragment.
  return <div>{data?.name}</div>;
  // Next time if the user's name changes in the cache, useFragment will
  // trigger re-rendering again.
}
wgottschalk commented 4 years ago

I think this would be really useful to help with data masking

rajington commented 4 years ago

could this be the same as (rough code):

const useFragment = (fragment, id) => useApolloClient().readFragment({ id, fragment })

?

dminkovsky commented 3 years ago

@rajington it's close except readFragment doesn't return an Observable so the result won't update when the store data changes.

itdhsc commented 2 years ago

Is there any solution to observe a fragment change in the cache?

benjamn commented 2 years ago

Here's our tracking issue for the implementation of useFragment in Apollo Client v3.5 (the next minor version).

Is there any solution to observe a fragment change in the cache?

To answer @itdhsc's question, yes, but useEffect complicates things, so it's better to keep the details hidden behind an abstraction like useFragment.

I should have a PR open soon using my useFragment-hook branch.

danReynolds commented 2 years ago

I was playing around with an implementation earlier this week here: https://github.com/NerdWalletOSS/apollo-cache-policies/pull/33 for useFragment and a collection filter useFragmentWhere. If folks have feedback/suggestions feel free to take a look

gen1lee commented 2 years ago

Why not to add useEntity also?

const entity = useEntity(User:${id})

danReynolds commented 2 years ago

Why not to add useEntity also?

const entity = useEntity(User:${id})

I think because you still need to specify a fragment to indicate which fields you want to read. For performance it's inefficient to return all fields by default. Then if you need to pass a fragment anyways, you can just use useFragment.

gen1lee commented 2 years ago

@danReynolds for performance it is much more inefficient to map entity into fragment, I guess problem here could be with proper typing in typescript.

danReynolds commented 2 years ago

@danReynolds for performance it is much more inefficient to map entity into fragment, I guess problem here could be with proper typing in typescript.

I was thinking of performance related to the reactive changes. If you return the entire object, Apollo will think you're dependent on all of those fields so if any of them change, your component or other places where you're subscribed to that object will update.

The reactive part of Apollo client is built around invalidation based on field dependencies

clayne11 commented 2 years ago

I am definitely very interested in this API at the apolloCache layer which is necessary for this to work at the React hook layer.

We have a bunch of cases (ie regulatory requirements) where we want to fire analytics / auditing events when entities change in the cache. This would be a super powerful pattern.

Either being able to watchFragment similar to watchQuery or something like this:

apollo.watchTypes({
  // __typename
  Trip: {
    onAdd: (trip) => {
      // ... do something
    },
    onUpdate: (oldTrip, newTrip) => {
      // ... do something
    },
    onDelete: (trip) => {
      // ... do something
    },
  }
})
danReynolds commented 2 years ago

I am definitely very interested in this API at the apolloCache layer which is necessary for this to work at the React hook layer.

We have a bunch of cases (ie regulatory requirements) where we want to fire analytics / auditing events when entities change in the cache. This would be a super powerful pattern.

Either being able to watchFragment similar to watchQuery or something like this:


apollo.watchTypes({

  // __typename

  Trip: {

    onAdd: (trip) => {

      // ... do something

    },

    onUpdate: (oldTrip, newTrip) => {

      // ... do something

    },

    onDelete: (trip) => {

      // ... do something

    },

  }

})

Yea that's what I made https://github.com/NerdWalletOSS/apollo-cache-policies for a while back initially.

clayne11 commented 2 years ago

@danReynolds that looks interesting and solves some of my goals, but not all of them. Also would love to see this on native as well.

Dmo16 commented 1 year ago

Question about useFragment(), is it supposed to support adding the fragments in our queries?

ie, child component with a fragment written in it:

export const DriverCardFragment = gql`
    fragment DriverCardFragment on Driver {
        id
        firstName
        lastName
        driverName
        createdAt
        updatedAt
    }
`;

I'd like to use that fragment in the query itself, so that I can keep the data logic scoped to the child component and not have to worry about updating parent components or affecting other children, like so:

const DRIVER_GRID_QUERY = gql`
  query QueryDrivers() {
    drivers: queryDrivers() {
     /// fields
      nodes {
        id
        ...DriverCardFragment
      }     
    }
  }
`;

If I write this query and fragment in Apollo studio, it works just fine, but if I try and send the same query with the spread fragment in my app using Apollo Client, I get an error:

"The specified fragment DriverCardFragment does not exist."

The hook portion of the child component, in case I'm doing something wrong:

  const { complete, data } = useFragment_experimental({
    fragment: DriverCardFragment,
    fragmentName: 'DriverCardFragment',
    from: {
      __typename: 'Driver',
      id: driverId,
    },
    returnPartialData: true,
    optimistic: false,
  });
jerelmiller commented 1 year ago

@Dmo16 as of now, there is no automatic registration of that fragment, so you'd need to import it and include in the query like you normally would when not using useFragment.

import { DriverCardFragment } from './path/to/fragment'

const DRIVER_GRID_QUERY = gql`
  query QueryDrivers() {
    drivers: queryDrivers() {
      # fields
      nodes {
        id
        ...DriverCardFragment
      }     
    }
  }

  ${DriverCardFragment} # <-- Ensure this is added to the query
`;

That's the missing piece in your code right now to make it work. Hope this helps!

TSMMark commented 1 year ago

I just found out about microsoft's apollo-react-relay-duct-tape package. Looks promising but I haven't tried it. Definitely posting it here as it might help some of us with this.

https://microsoft.github.io/graphitation/docs/apollo-react-relay-duct-tape/use-lazy-load-query

https://github.com/microsoft/graphitation

Anyone had any success with it?