Я разрабатывал проект с простой авторизацией, используя куки и apollo-client. Проблема в том, что иногда, когда я пытаюсь useQUery(isAuthenticatedQuery)
получить правильные данные, а иногда нет. Этот запрос используется для проверки того, что мои пользователи вошли в систему. Я отправил в заголовке запроса токен, возвращенный после моего LoginMutation
. Я уже проверил свой запрос на вкладке сети, и когда я получил сообщение об ошибке, то заголовок отправляет «bearer undefined» вместо «bearer $ {token}».
Это мое первое приложение, использующее apollo, так что, вероятно, это глупый вопрос, я подумал, что есть некоторая проблема с асинхронным запросом, , но все запросы в useQuery уже асин c, верно ?
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 (моя настройка apollo ho c 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; проверьте клиент. Файл js внутри моего HttpLink, как я определяю заголовки, и index.tsx> Блог о том, как я использую useQuery(isAuthenticatedQuery)
для проверьте, вошел ли пользователь в систему.
obs .: Если я обновлю sh страницу, токен всегда будет установлен, и запрос будет работать как положено.