I've been developing a project with a simple authorization using cookies and apollo-client. The challenge is, sometimes when I try to useQUery(isAuthenticatedQuery)
they retrieve correct data and sometimes not. This query is used to check if my users is loggedIn, I sent in my request Header the token returned after my LoginMutation
. I've already checked my request in the network tab and when I got error is when the header is sending "bearer undefined" instead of "bearer ${token}".
It's my first app using apollo so probably it's a dummy question, I was thinking there was some issue with the asynchronous request, but all the requests in useQuery are already async, right?
login.tsx
import React, { useState } from 'react'
import Layout from '../components/Layout'
import Router from 'next/router'
import { withApollo } from '../apollo/client'
import gql from 'graphql-tag'
import { useMutation, useQuery, useApolloClient } from '@apollo/react-hooks'
const LoginMutation = gql`
mutation LoginMutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`
function Login(props) {
const client = useApolloClient()
const [password, setPassword] = useState('')
const [email, setEmail] = useState('')
const [login] = useMutation(LoginMutation, {
onCompleted(data) {
document.cookie = `token=${data.login.token}; path=/`
}
})
return (
<Layout>
<div>
<form
onSubmit={async e => {
e.preventDefault();
await login({
variables: {
email: email,
password: password,
}
})
Router.push('/')
}}>
<h1>Login user</h1>
<input
autoFocus
onChange={e => setEmail(e.target.value)}
placeholder="Email"
type="text"
value={email}
/>
<input
onChange={e => setPassword(e.target.value)}
placeholder="Password"
type="password"
value={password}
/>
<input disabled={!password || !email} type="submit" value="Login" />
<a className="back" href="#" onClick={() => Router.push('/')}>
or Cancel
</a>
</form>
</div>
</Layout>
)
}
export default withApollo(Login)
index.tsx
import { useEffect } from 'react'
import Layout from '../components/Layout'
import Link from 'next/link'
import { withApollo } from '../apollo/client'
import { useQuery } from '@apollo/react-hooks'
import { FeedQuery, isAuthenticatedQuery } from '../queries';
export interface Item {
content: string
author: string
title: string
name: string
}
export interface Post {
post: {
[key: string]: Item
}
}
const Post = ({ post }: Post) => (
<Link href="/p/[id]" as={`/p/${post.id}`}>
<a>
<h2>{post.title}</h2>
<small>By {post.author.name}</small>
<p>{post.content}</p>
<style jsx>{`
a {
text-decoration: none;
color: inherit;
padding: 2rem;
display: block;
}
`}</style>
</a>
</Link>
)
const Blog = () => {
const { loading, error, data } = useQuery(FeedQuery)
const { loading: loadingAuth, data: dataAuth, error: errorAuth } = useQuery(isAuthenticatedQuery)
console.log("data auth", dataAuth, loadingAuth, errorAuth);
if (loading) {
return <div>Loading ...</div>
}
if (error) {
return <div>Error: {error.message}</div>
}
return (
<Layout>
<div className="page">
{!!dataAuth && !loadingAuth ? (
<h1> Welcome back {dataAuth.me.name} </h1>
) : (
<h1>My Blog</h1>
)}
<main>
{data.feed.map(post => (
<div className="post">
<Post key={post.id} post={post} />
</div>
))}
</main>
</div>
<style jsx>{`
h1 {
text-transform: capitalize;
}
.post {
background: white;
transition: box-shadow 0.1s ease-in;
}
.post:hover {
box-shadow: 1px 1px 3px #aaa;
}
.post + .post {
margin-top: 2rem;
}
`}</style>
</Layout>
)
}
export default withApollo(Blog)
client.js(my configuring apollo hoc file)
import React from 'react'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'isomorphic-unfetch'
import cookies from 'next-cookies'
let apolloClient = null
let token = undefined
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState)
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component'
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.')
}
WithApollo.displayName = `withApollo(${displayName})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
token = cookies(ctx).token || ''
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient())
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr')
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>,
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState,
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}) {
const ssrMode = typeof window === 'undefined'
const cache = new InMemoryCache().restore(initialState)
return new ApolloClient({
ssrMode,
link: createIsomorphLink(),
cache,
})
}
function createIsomorphLink() {
const { HttpLink } = require('apollo-link-http')
return new HttpLink({
headers: { Authorization: `Bearer ${token}` },
uri: 'http://localhost:4000',
credentials: 'same-origin',
})
}
TLDR; check the client.js file inside my HttpLink how I define headers, and index.tsx > Blog how I'm using the useQuery(isAuthenticatedQuery)
to check if the user is signed in.
obs.: If I refresh the page the token is always set and the query works as expected.