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

Question: Reset error on useQuery retry #129

Closed cliedeman closed 3 years ago

cliedeman commented 3 years ago

A pattern I use quite often is simliar to the example

const AppTodo = function (appProps)  {
  const {props, error, retry} = useQuery(query, variables, options);

  if (props && props.user) {
    return <TodoApp user={props.user} />;
  } else if (error) {
    return <Error error={error} retry={retry}/>;
  }
  return <div>loading</div>;
}

But on failure if I invoke retry the error is not reset and the loading section is displayed

Ciaran

morrys commented 3 years ago

Hi @cliedeman, I did not understand the problem well.

When you invoke the Retry, the dispose of the previous request is performed and the previous error value is reset: https://github.com/relay-tools/relay-hooks/blob/master/src/QueryFetcher.ts#L113

Following the new fetch they will be updated based on its success or failure.

cliedeman commented 3 years ago

Hi @morrys , let me try again

Sample Error Component

interface Props {
  error: Error;
  retry?(): void;
}

export function Error({error, retry}: Props) {
  return (
    <div>
      {error.message}
      {retry && <Button onClick={retry}>Retry</Button>}
    </div>
  );
}

I will try to explain what happens based on the props

# 1st Render
 {props: null, error: null} => Render Loading Component

# 2nd Render:Server unreachable
{props: null, error: SomeError} => Render Error Component

# 3rd User Clicks retry button
{props: null: error: SomeError => Render Error Component

# 4th Render, call succeeds
{props: User, error: null} => Render data

After the user clicks the error button I expect the error to be cleared or alternatively some way to detect that a retry is being executed

An Alternative solution I just thought up

const AppTodo = function (appProps)  {
  const {props, error, retry, cached, isRetrying} = useQuery(query, variables, options);

  if (props && props.user) {
    return <TodoApp user={props.user} />;
  } else if (isRetrying) {
    return <div>loading</div>;
  } else if (error) {
    return <Error error={error} retry={retry}/>;
  }
  return <div>loading</div>;
}

Ciaran

morrys commented 3 years ago

Try this sample project without starting the server and editing the app.js file with this:

import * as React from 'react';

import { useQuery, RelayEnvironmentProvider } from 'relay-hooks';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';

import { create } from './mutations/create';

import QueryApp from './query/QueryApp';
import Entries from './components/Entries';

async function fetchQuery(operation, variables) {
    const response = await fetch('http://localhost:3003/graphql', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            query: operation.text,
            variables,
        }),
    });

    return response.json();
}

const modernEnvironment = new Environment({
    network: Network.create(fetchQuery),
    store: new Store(new RecordSource()),
});

const AppTodo = (propsApp) => {
    const [, forceUpdate] = React.useReducer(x => x + 1, 0);
    const { props, error, retry } = useQuery(
        QueryApp,
        {},
        {
            fetchPolicy: 'store-or-network',
        },
    ); /*propsApp; */
    async function submitEntry() {
        await create('try', modernEnvironment).catch(console.error);
    }
    console.log("renderer", error)
    if (props && props.entries) {
        return (
            <React.Fragment>
                <button onClick={submitEntry} className="refetch">
                    Add
                </button>
                <Entries entries={props.entries} />
            </React.Fragment>
        );
    } else if (error) {

        return <button onClick={() => {retry(); forceUpdate();}} className="refetch">
        Retry
</button>;
    }
    return <div>loading</div>;
};

const App = (
    <RelayEnvironmentProvider environment={modernEnvironment}>
        <AppTodo />
    </RelayEnvironmentProvider>
);

export default App;
mohsensaremi commented 3 years ago

@cliedeman Did you find any solution for this problem? I have the same issue.

morrys commented 3 years ago

in version 3.x it is necessary to perform the forceUpdate as described in this comment https://github.com/relay-tools/relay-hooks/issues/129#issuecomment-737316301

While in version 4.x it is managed natively

cliedeman commented 3 years ago

@morrys Thanks, I can confirm this is working as expected in version 4.0.0