relay-tools / relay-hooks

Use Relay as React hooks
https://relay-tools.github.io/relay-hooks/docs/relay-hooks.html
MIT License
540 stars 56 forks source link

Can relay-hooks v4 be used with relay 11? #164

Closed aleksandrlat closed 2 years ago

aleksandrlat commented 3 years ago

Relay 11 was just released https://github.com/facebook/relay/releases/tag/v11.0.0

Can relay-hooks v4 be used with new relay 11?

cc @morrys

morrys commented 3 years ago

Currently relay-hooks v4 is perfectly compatible with relay-runtime v11 https://github.com/relay-tools/relay-hooks/pull/166

KishiTheMechanic commented 3 years ago

Can you use useMutation config RANGE_DELETE as well?

configs: [
      {
        type: 'RANGE_DELETE',
        parentID: 'client:root',
        parentName: 'client:root',
        connectionKeys: [
          {
            key: 'ToolImgAllList_toolImgSearch',
          },
        ],
        pathToConnection: ['client:root', 'toolImgSearch'],
        deletedIDFieldName: 'id',
      },
    ],

I used this config in relay v10 and v11, working on v 10 but not worked in v11.

morrys commented 3 years ago

Sure, useMutation uses relay-runtime commitMutation to manage the mutation, without changing its behavior

KishiTheMechanic commented 3 years ago

I thought so but it doesn't delete the record from relay store anymore in v11. Does it look wrong config setting? (working completely in v10 though...)

aleksandrlat commented 3 years ago

Currently relay-hooks v4 is perfectly compatible with relay-runtime v11

Thank you @morrys!

matepaiva commented 3 years ago

I have a next.js app that uses relay-hooks@^4.2 with react-relay@^10.1.2 and it is working. But when I update it to use react-relay@^11.0.2, it breaks with the following error message:

Invariant Violation: `Relay(Timeline)` tried to render a context that was not valid this means that `Relay(Timeline)` was rendered outside of a query renderer.

Call Stack
invariant
node_modules/invariant/browser.js (38:0)
Relay(Timeline)
node_modules/react-relay/lib/buildReactRelayContainer.js (57:62)
renderWithHooks
node_modules/react-dom/cjs/react-dom.development.js (14985:0)
updateForwardRef
node_modules/react-dom/cjs/react-dom.development.js (17044:0)
beginWork
node_modules/react-dom/cjs/react-dom.development.js (19098:0)
HTMLUnknownElement.callCallback
node_modules/react-dom/cjs/react-dom.development.js (3945:0)
Object.invokeGuardedCallbackDev
node_modules/react-dom/cjs/react-dom.development.js (3994:0)
invokeGuardedCallback
node_modules/react-dom/cjs/react-dom.development.js (4056:0)
beginWork$1
node_modules/react-dom/cjs/react-dom.development.js (23964:0)
performUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js (22776:0)
workLoopSync
node_modules/react-dom/cjs/react-dom.development.js (22707:0)
renderRootSync
node_modules/react-dom/cjs/react-dom.development.js (22670:0)
performSyncWorkOnRoot
node_modules/react-dom/cjs/react-dom.development.js (22293:0)
eval
node_modules/react-dom/cjs/react-dom.development.js (11327:0)
unstable_runWithPriority
node_modules/scheduler/cjs/scheduler.development.js (468:0)
runWithPriority$1
node_modules/react-dom/cjs/react-dom.development.js (11276:0)
flushSyncCallbackQueueImpl
node_modules/react-dom/cjs/react-dom.development.js (11322:0)
flushSyncCallbackQueue
node_modules/react-dom/cjs/react-dom.development.js (11309:0)
scheduleUpdateOnFiber
node_modules/react-dom/cjs/react-dom.development.js (21893:0)
dispatchAction
node_modules/react-dom/cjs/react-dom.development.js (16139:0)
onNext
node_modules/relay-hooks/lib/QueryFetcher.js (124:0)
Object.next
node_modules/relay-hooks/lib/FetchResolver.js (164:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
eval
node_modules/relay-runtime/lib/util/RelayReplaySubject.js (119:0)
Set.forEach
<anonymous>
RelayReplaySubject.next
node_modules/relay-runtime/lib/util/RelayReplaySubject.js (118:0)
Object.next
node_modules/relay-runtime/lib/query/fetchQueryInternal.js (121:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
Executor._handleNext
node_modules/relay-runtime/lib/store/RelayModernQueryExecutor.js (429:0)
eval
node_modules/relay-runtime/lib/store/RelayModernQueryExecutor.js (272:0)
Executor._schedule
node_modules/relay-runtime/lib/store/RelayModernQueryExecutor.js (241:0)
Executor._next
node_modules/relay-runtime/lib/store/RelayModernQueryExecutor.js (271:0)
Object.next
node_modules/relay-runtime/lib/store/RelayModernQueryExecutor.js (112:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
Object.eval [as next]
node_modules/relay-runtime/lib/network/RelayObservable.js (190:0)
Object.next
node_modules/relay-runtime/lib/network/RelayObservable.js (535:0)
eval
node_modules/react-relay-network-modern/node8/RelayNetworkLayer.js (54:0)
morrys commented 3 years ago

hi @matepaiva, I just created a new branch to test the integration with react-relay v11.0.2 and in no case can I reproduce your error. commit: https://github.com/relay-tools/relay-hooks/commit/4c7a95fd13cc60b32b6d87411933242f21f4305d

Have you successfully updated all relay dependencies?

But thanks to your report I was able to verify that if the legacy APIs (HOCs) are used with version 11.0.2 it is necessary to add a new context ReactRelayQueryRendererContext.Provider: commit: https://github.com/relay-tools/relay-hooks/commit/b81377ac61ad32e6db1406f0824ea43134ba06ee

import ReactRelayQueryRendererContext from 'react-relay/lib/ReactRelayQueryRendererContext'

<RelayEnvironmentProvider environment={modernEnvironment}>
    <ReactRelayQueryRendererContext.Provider value={{
      rootIsQueryRenderer: true,
    }}>
      <AppTodo />
    </ReactRelayQueryRendererContext.Provider>
  </RelayEnvironmentProvider>
matepaiva commented 3 years ago

Awesome! That was exactly the case! I am using the fragment HOC and I will update it as soon as possible. Thank you very much.

I don't want to switch to other topics here, but now that react-relay officially released their own hooks integrated to their lib, what will happen to this package? There are some functionalities that are overlapped, right? I would love to hear your opinion about the future for relay-hooks (this one).

EDIT: That was a premature celebration :( I tried it and it didn't solve the problem. Below is my _app.js Component:

function App({ Component, pageProps }) {
  const environment = createEnvironment(pageProps.relayData);

  return (
    <RelayEnvironmentProvider environment={environment}>
      <ReactRelayQueryRendererContext.Provider
        value={{
          rootIsQueryRenderer: true,
          environment // I tried without it as well
        }}>
        <Component {...pageProps} />
      </ReactRelayQueryRendererContext.Provider>
    </RelayEnvironmentProvider>
  );
}

EDIT 2: I decided to convert all my HOCs to hooks, so the problem is solved on my side.

morrys commented 3 years ago

The new context solves a fragment subscription problem because if the fragment is not used it requires the use of suspense.

While your problem seems to fail to detect the context and in the branch I have done various tests and it is always detected. It would be interesting to recreate the error although I highly recommend migrating all HOCs as they have become legacy.

As for this package, I will continue to support it as long as it is used and / or until suspense becomes the new development standard react

matepaiva commented 3 years ago

Good to hear that! Thank you for the great work.

matepaiva commented 3 years ago

@morrys Just letting you know that I had to downgrade back to react-relay 10.

I actually don't know yet where the problem is, but I realised that the SSR stopped working after installing react-relay@11. Also I downgraded to react-relay@10 and it started working back again.

I don't think the problem is with relay-hooks, but it can surely be reproduced with the following code:

// pages/_app.js

import { RelayEnvironmentProvider } from 'relay-hooks';
import { createEnvironment } from 'lib/createEnvironment';

export default function App({ Component, pageProps }) {
  return (
    <RelayEnvironmentProvider environment={createEnvironment(pageProps?.relayData)}>
        <Component {...pageProps} />
    </RelayEnvironmentProvider>
  );
}
 // lib/createEnvironment/client.js

import { RelayNetworkLayer, cacheMiddleware, urlMiddleware } from 'react-relay-network-modern/node8';
import RelaySSR from 'react-relay-network-modern-ssr/node8/client';
import { Environment, RecordSource, Store } from 'relay-runtime';

const source = new RecordSource();
const store = new Store(source);

let storeEnvironment = null;

export default {
  createEnvironment: (relayData) => {
    if (storeEnvironment) return storeEnvironment;

    storeEnvironment = new Environment({
      store,
      network: new RelayNetworkLayer([
        cacheMiddleware({ size: 100, ttl: 60 * 1000 }),
        new RelaySSR(relayData).getMiddleware({ lookup: false }),
        // eslint-disable-next-line no-unused-vars
        urlMiddleware({ url: (req) => process.env.NEXT_PUBLIC_RELAY_ENDPOINT })
      ])
    });

    return storeEnvironment;
  }
};
 // lib/createEnvironment/server.js

import { RelayNetworkLayer, urlMiddleware } from 'react-relay-network-modern/node8';
import RelaySSR from 'react-relay-network-modern-ssr/node8/server';
import { Network, Environment, RecordSource, Store } from 'relay-runtime';

export default {
  initEnvironment: () => {
    const source = new RecordSource();
    const store = new Store(source);
    const relaySSR = new RelaySSR();

    return {
      relaySSR,
      environment: new Environment({
        store,
        network: new RelayNetworkLayer([
          urlMiddleware({ url: () => process.env.NEXT_PUBLIC_RELAY_ENDPOINT }),
          relaySSR.getMiddleware()
        ])
      })
    };
  },
  createEnvironment: (relayData) => {
    const source = new RecordSource();
    const store = new Store(source);

    return new Environment({
      store,
      network: Network.create(() => relayData?.[0][1] || Promise.resolve())
    });
  }
};
// pages/index.js

import { useQuery, graphql } from 'relay-hooks';
// There is a logic that imports from client.js or server.js according to environment
import { initEnvironment } from 'lib/createEnvironment'; 

const query = graphql`query pages_indexQuery { title }`;

export default function IndexPage() {
  const { error, data, isLoading } = useQuery(query);
  if (error) return <div>{error.message}</div>;
  if (!data && isLoading) return <div>Loading</div>;

  return <div>{data.title}</div>;
}

export async function getStaticProps() {
  const { environment, relaySSR } = initEnvironment();
  await fetchQuery(environment, query);
  const relayData = (await relaySSR.getCache())?.[0];

  return {
    props: {
      relayData: !relayData ? null : [[relayData[0], relayData[1].json]]
    },
    revalidate: 1
  };
}
matepaiva commented 3 years ago

I think I already found the problem. Now fetchQuery is not a promise anymore. One way to make it work again is to call fetchQuery(...).toPromise(), but it is not recommended by react-relay team.

EDIT: using await fetchQuery(...).subscribe({}); seems to work as well.

morrys commented 2 years ago

I close the issue as relay-hooks is compatible with relay v11