Search code examples
react-admin

React-admin: useQuery not fetching data on Dashboard after logging in


I have react-admin set up with GraphQL and works fine for the most part... however there seems to be an issue with logging in initially and having the Dashboard make a query; the issue being that it will not resolve upon initial loading. It goes from logging in -> Dashboard and I'm met with a component that loads forever with no data or errors.

My Admin is set up like this:

const routes = [
    <Route exact path="/support-chat-list" component={SupportChatPage} noLayout />,
    <Route exact path="/group-chat" component={GroupChatPage} noLayout />,
    <Route exact path="/" component={Dashboard} />,
];

const App = () => (
    <Admin dashboard={Dashboard} layout={MyLayout} customRoutes={routes} dataProvider={dataProvider} authProvider={authProvider} i18nProvider={i18nProvider}>
        {/* my resources... */}
    </Admin>
);

and my Dashboard looks like this:

const requestOptions = (startingAfter, startingBefore) => {
    return {
        type    : 'getList',
        resource: 'appointments',
        payload : {
            pagination: {
                perPage: 100,
                page   : 0,
            },
            sort      : {
                field: 'start_at',
                order: 'asc'
            },
            filter    : {
                startingAfter: startingAfter.toString(),
                startingBefore: startingBefore.toString(),
            }
        }
    };
};

const handleSelectEvent = (event, e, redirectTo) => {
    redirectTo('show', ROUTES.APPOINTMENTS, event.resource.id);
};

const initialState = {
    middleDate: DateTime.local().startOf('day'),
    startDate: DateTime.local().minus({days: 3}).startOf('day'),
    endDate: DateTime.local().plus({days: 3}).endOf('day'),
};

export function Dashboard() {
    useAuthenticated();
    const redirectTo = useRedirect();
    const [dateRanges, setDateRanges] = useState(initialState);
    const {data, loading, error} = useQueryWithStore(requestOptions(dateRanges.startDate, dateRanges.endDate));

    if (error) return <Error />;

    return (
        <Card>
            <Title title="Admin" />
            <CardContent>
                {loading && <Loading/>}
                {!loading &&
                    <Calendar
                        events={translateAppointmentData(data)}
                        //.......

I've tried playing around with useQuery and useQueryWithStore but nothing seems to work. It will only resolve when I refresh the page, and only then it will load everything.

I have a suspicion that it has to do with my authProvider, which is a class that handles authentication. It's a bit bloated so I'll try my best to show the things related to login:

async login({username, password}) {
        const response = await authClient.passwordGrant(username, password, ACCESS_TOKEN_SCOPES);
        await this.processTokenResponse(response);

        return Promise.resolve();
    }

authClient.passwordGrant looks like this:

passwordGrant(username, password, scopes) {
        const request = new Request(`${this.url}/oauth/access_token`, {
            method: 'POST',
            body: JSON.stringify({
                username: username,
                password: password,
                client_id: this.clientId,
                client_secret: this.clientSecret,
                grant_type: 'password',
                scope: scopes.join(' '),
            }),
            headers: new Headers({ 'Content-Type': 'application/json' }),
        });

        return fetch(request)
            .then(response => {
                if (response.status < 200 || response.status >= 300) {
                    throw new Error(response.statusText);
                }

                return response.json();
            });
    }

while processTokenResponse looks like this:

processTokenResponse(response) {
        const now = DateTime.local();
        localStorage.setItem(TOKEN_STORAGE_KEY, response.access_token);
        localStorage.setItem(TOKEN_EXPIRY_STORAGE_KEY, now.plus({seconds: response.expires_in - EXPIRY_BUFFER}).toString());

        localStorage.setItem(SCOPES_STORAGE_KEY, JSON.stringify({scopes: response.scopes}));

        localStorage.setItem(REFRESH_TOKEN_STORAGE_KEY, response.refresh_token);
        localStorage.setItem(REFRESH_TOKEN_EXPIRY_STORAGE_KEY, now.plus({seconds: response.refresh_token_expires_in - EXPIRY_BUFFER}).toString());

        return this.fetchActiveUserData();
    }

and fetchActiveUserData looks like this:

async fetchActiveUserData() {
        let dataProvider = new ActiveUserDataProvider();

        return await dataProvider.getOne({}).then((response) => {
            return this.processLoadUserResponse(response.data);
        })
    }

in which my dataProvider looks like this:

import apolloClient from '../apolloClient';
import { BaseResourceDataProvider } from './baseResourceDataProvider';
import {FetchMeQuery} from "../graphql/users";

const FetchMeQuery = gql`
    query FetchMe {
        me {
            id
            firstName
            lastName
            name
            username
            timezone
        }
    }
`;

class ActiveUserDataProvider extends BaseResourceDataProvider {
    getOne({ id }: { id?: string }) { // maybe something wrong here?
        return apolloClient.query({
            query: FetchMeQuery,
            fetchPolicy: 'cache-first'
        }).then((result) => {
            return {
                data: result.data.me,
            };
        });
    }
}

I apologize for the long post but I'm completely lost on why my Dashboard isn't querying data once initially logged in, and needing a refresh for it to work. I'm hoping someone can point where something's wrong. Thank you.


Solution

  • After much digging, it turned out to be how I set up my ApolloClient. Previously I was using Apollo Link to set up my auth middleware, and discovered that it wasn't calling the middleware at all.

    I discovered this question and replaced ApolloLink with setContext and it worked perfectly. This is the code now:

    const authMiddleware = setContext(async (_, { headers }) => {
        const token = await authProvider.checkAuth();
    
        return {
            headers: {
                ...headers,
                authorization: token ? `Bearer ${token}` : '',
            }
        }
    });
    
    const apolloClient = new ApolloClient({
        cache: new InMemoryCache(),
        link: from([
            authMiddleware,
            httpLink
        ]),
    });