与React-NextJS一起使用的react-apollo抛出“无法读取未定义的属性”

时间:2019-09-18 17:30:01

标签: next.js react-apollo apollo-client

这些天来,我的NextJS项目使我对Graph QL感到悲伤。我一直在尝试实现Apollo客户端解决方案,以将数据从远程GraphQL服务器检索到自定义组件中。但是,无论我尝试哪种解决方案,我总是会遇到此错误。这是我当前的react-apollo实现:

// /lib/with-apollo-client.js
import React from "react";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";

import initApollo from "./init-apollo";

export default App => {
  return class WithData extends React.Component {
    static displayName = `WithData(${App.displayName})`;

    static async getInitialProps(ctx) {
      const { Component, router } = ctx;
      const apollo = initApollo({});

      ctx.ctx.apolloClient = apollo;

      let appProps = {};
      if (App.getInitialProps) {
        appProps = await App.getInitialProps(ctx);
      }

      // Run all GraphQL queries in the component tree
      // and extract the resulting data
      if (!process.browser) {
        try {
          // Run all GraphQL queries
          await getDataFromTree(
            <App
              {...appProps}
              Component={Component}
              router={router}
              apolloClient={apollo}
            />
          );
        } catch (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 = apollo.cache.extract();

      return {
        ...appProps,
        apolloState
      };
    }

    constructor(props) {
      super(props);
      this.apolloClient = initApollo(props.apolloState);
    }

    render() {
      return <App {...this.props} apolloClient={this.apolloClient} />;
    }
  };
};
// /lib/init-apollo.js

import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'

let apolloClient = null

// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
  global.fetch = fetch
}

function create (initialState) {
  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  return new ApolloClient({
    connectToDevTools: process.browser,
    ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
    link: new HttpLink({
      uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
      credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
    }),
    cache: new InMemoryCache().restore(initialState || {})
  })
}

export default function initApollo (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 (!process.browser) {
    return create(initialState)
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = create(initialState)
  }

  return apolloClient
}

我正在将数据检索到的组件看起来像这样:

// /components/PostsList2.jsx

import { Query } from 'react-apollo'
import gql from 'graphql-tag'

export const allUsersQuery = gql`
  query allUsers($first: Int!, $skip: Int!) {
    allUsers(orderBy: createdAt_DESC, first: $first, skip: $skip) {
      id
      firstName
      createdAt
    }
    _allUsersMeta {
      count
    }
  }
`
export const allUsersQueryVars = {
  skip: 0,
  first: 10
}

export default function PostsList2 () {
  return (
    <Query query={allUsersQuery} variables={allUsersQueryVars}>
      {({ loading, error, data: { allUsers, _allUsersMeta }, fetchMore }) => {
        if (error) return <aside>Error loading users!</aside>
        if (loading) return <div>Loading</div>

        const areMorePosts = allUsers.length < _allUsersMeta.count
        return (
          <section>
            <ul>
              {allUsers.map((user, index) => (
                <li key={user.id}>
                  <div>
                    <span>{index + 1}. </span>
                    <div>{user.firstName}</div>
                  </div>
                </li>
              ))}
            </ul>
            {areMorePosts ? (
              <button onClick={() => loadMorePosts(allUsers, fetchMore)}>
                {' '}
                {loading ? 'Loading...' : 'Show More'}{' '}
              </button>
            ) : (
              ''
            )}
          </section>
        )
      }}
    </Query>
  )
}

function loadMorePosts (allUsers, fetchMore) {
  fetchMore({
    variables: {
      skip: allUsers.length
    },
    updateQuery: (previousResult, { fetchMoreResult }) => {
      if (!fetchMoreResult) {
        return previousResult
      }
      return Object.assign({}, previousResult, {
        // Append the new users results to the old one
        allUsers: [...previousResult.allUsers, ...fetchMoreResult.allUsers]
      })
    }
  })
}

由于这是一个NextJS项目,所以我还包装了一个特殊的提供程序组件中的_app.jsx

// /pages._app.jsx

/* eslint-disable max-len */

import '../static/styles/fonts.scss';
import '../static/styles/style.scss';
import '../static/styles/some.css';

import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/styles';
import jwt from 'jsonwebtoken';
import withRedux from 'next-redux-wrapper';
import App, {
  Container,
} from 'next/app';
import Head from 'next/head';
import React from 'react';
import { Provider } from 'react-redux';

import makeStore from '../reducers';
import mainTheme from '../themes/main-theme';
import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
import getLanguageFromCookies from '../utils/get-language-from-cookies';
import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
import removeFbHash from '../utils/remove-fb-hash';

import withApolloClient from '../lib/with-apollo-client'
import { ApolloProvider } from 'react-apollo'

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let userToken;
    let sessID;
    let language;

    if (ctx.isServer) {
      ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });

      userToken = getUserTokenFromCookies(ctx.req);
      sessID = getSessIDFromCookies(ctx.req);
      language = getLanguageFromCookies(ctx.req);
      const dictionary = require(`../dictionaries/${language}`);
      ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
      if(ctx.res) {
        if(ctx.res.locals) {
          if(!ctx.res.locals.authenticated) {
            userToken = null;
            sessID = null;
          }
        }
      }
      if (userToken && sessID) { // TBD: validate integrity of sessID
        const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
        ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
      }
      ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
    }
    const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
    return { pageProps };
  }

  componentDidMount() {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentNode.removeChild(jssStyles);
    }
    // Register serviceWorker
    if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }

    // Handle FB's ugly redirect URL hash
    removeFbHash(window, document);
  }

  render() {
    const { Component, pageProps, store, apolloClient } = this.props;

    return (
      <Container>
        <Head>
          // redacted for brevity
        </Head>
        <ThemeProvider theme={mainTheme}>
          {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
          <CssBaseline />
          <ApolloProvider client={apolloClient}>
            <Provider store={store}>
                <Component {...pageProps} />
            </Provider>
          </ApolloProvider>
        </ThemeProvider>
      </Container>
    );
  }
}

export default withApolloClient(withRedux(makeStore)(MyApp));

因此,使用此设置,当我编译并运行我的应用程序时,它将引发以下内容:

  

TypeError:无法读取未定义的属性'allUsers'

我真的迷路了!回购位于https://github.com/amitschandillia/proost/tree/master/web

0 个答案:

没有答案