Apollo Client(React) - Web SDK
項目一覧
GraphQL は非推奨です。 詳細はこちら。
Overview
Apollo Client を使用できます を使用して、 Realmアプリの 公開GraphQL API にReactアプリケーションから接続します。 Apollo Client は、クエリとミューテーションを実行し、クライアント側のデータキャッシュを維持し、慣用的なReactコンポーネントとフックを使用してアプリに統合します。
注意
動作中のデモアプリケーションを見る
の確認 RealmGraphQL- Apollo(React ) のリポジトリを参照して、独自のGithubReact バックエンドに接続する準備ができている完全に設定された & ApolloAtlas App Services アプリケーションを確認してください。MongoDB Atlasサンプル データ セットに含まれる sample_mflix.movies
コレクションを使用します。
リポジトリをクローンしたくない場合は、 Realm GraphQL CodeSandbox のブラウザ内でデモ アプリケーションも利用できます。
Apollo Client を設定する
依存関係のインストール
どの Realm プロジェクトと同様に、 Realm Web SDK をインストールする必要があります ユーザーとリクエストを認証するため。
npm install realm-web
Apollo は、クライアントを作成するために必要なコア コンポーネントを @apollo/client というパッケージにバンドルします。 。GraphQLGraphQLも必要です クエリを解析するためのパッケージ。
npm install @apollo/client graphql
Apollo GraphQL クライアントの作成
新しい ApolloClient を作成する Realm アプリの GraphQL API エンドポイントを示す オブジェクト。Realm App ID に基づいてエンドポイント URL を生成するか、Realm UI の GraphQL ページで確認できます。
// Add your App ID const graphqlUri = `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`; // Local apps should use a local URI! // const graphqlUri = `https://us-east-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://eu-west-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` // const graphqlUri = `https://ap-southeast-1.aws.services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql` const client = new ApolloClient({ link: new HttpLink({ uri: graphqlUri, }), cache: new InMemoryCache(), });
ユーザー認証を設定する
ApolloClient
はリクエストをアプリに送信するように構成されています。 ただし、すべての Realm GraphQL リクエストにはリクエストを認証するための有効なユーザー アクセス トークンが含まれている必要があるため、Apollo から送信されたすべての操作は失敗します。 アクセス トークンは 30 分後に期限切れになり、更新する必要があります。
リクエストを認証するには、有効な Realm ユーザー アクセス トークンを各GraphQLリクエストに追加する必要があります。
Realm Web SDKを使用してユーザーを認証し、アクセス トークンを取得できます。 Apollo HttpLink
オブジェクトを使用すると、カスタムfetch
関数を定義することで、すべてのリクエストにカスタム ヘッダーを追加できます。
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });
Apollo Client をアプリに追加する
Apollo client
オブジェクトは、認証された GraphQL リクエストを App Services バックエンドに送信するように構成されました。 残りの操作は、React アプリケーションの残りの部分で使用できるようにすることです。
@apollo/client
パッケージはApolloProvider
コンポーネントをエクスポートします。これにより、子コンポーネントから呼び出す任意の Apollo フックでclient
を使用できるようになります。 アプリをApolloProvider
でラップし、 client
オブジェクトをプロバイダーに渡します。
// ... code to create the GraphQL client const AppWithApollo = () => ( <ApolloProvider client={client}> <App /> </ApolloProvider> );
クエリとミューテーションの実行
@apollo/client
パッケージには、コンポーネントを GraphQL API に接続し、クエリとミューテーションの実行を処理する宣言型の React フックのセットが含まれています。
フックに渡すことができるクエリとミューテーションを定義するには、 graphql タグ をインストールします。
npm install graphql-tag
関連するフックと GraphQL クエリ コンストラクターを、それらを使用しているファイルの上部にインポートします。
// import whichever Apollo hooks you're using import { useQuery, useMutation } from "@apollo/client"; import gql from "graphql-tag";
注意
Apollo フックには ApolloProvider が必要です
クエリとミューテーション フックを呼び出すコンポーネントは、 ApolloProvider の子孫である必要があります App Services バックエンド用に構成したフックは クエリ を呼び出します および ミューテーション 提供された オブジェクトのメソッド。client
クエリの実行
Apollo Client には、クエリを実行するための 2 つのフックが含まれています。 フックは同一のパラメーターを受け入れますが、クエリを実行するタイミングは異なります。
useQuery() は、 コンポーネントがマウントされると自動的に実行されます。また、クエリを呼び出すたびにクエリを再実行するコールバックも返します。
useLazyQuery() は、呼び出すたびにクエリを実行するコールバック関数を返します。コンポーネント マウントではクエリは実行されません。
どちらのフックも、Apollo がクエリに渡すvariables
などのクエリ定義と追加のオプションを受け入れます。 また、クエリの現在の実行状態に関する情報と最新の実行から返されたデータも返します。
const ALL_MOVIES = gql` query AllMovies { movies { _id title year runtime } } `; // Must be rendered inside of an ApolloProvider function Movies() { const { loading, error, data } = useQuery(ALL_MOVIES); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error}</div>; } return <MovieList movies={data.movies} />; }
ミューテーションを実行する
useMutation() フックは、ミューテーション定義とオプションの構成オブジェクトを受け入れます。渡す必要がある最も一般的なオプションは、variables
GraphQL 変数 にマップされる オブジェクトです ミューテーション定義内の 。
このフックは、配列内の複数のオブジェクトを返します。
ミューテーションを実行する コールバック関数
ミューテーションの実行状態に関する情報と最新の実行から返されたデータを含むオブジェクト。
const UPDATE_MOVIE_TITLE = gql` mutation UpdateMovieTitle($oldTitle: String!, $newTitle: String!) { updateOneMovie(query: { title: $oldTitle }, set: { title: $newTitle }) { title year } } `; // Must be rendered inside of an ApolloProvider function MovieList({ movies }) { const [updateMovieTitle] = useMutation(UPDATE_MOVIE_TITLE); return ( <ul> {movies.map((movie) => ( <li key={movie._id}> <div>{movie.title}</div> <button onClick={() => { updateMovieTitle({ variables: { oldTitle: movie.title, newTitle: "Some New Title", }, }); }} > Update Title </button> </li> ))} </ul> ); }
Paginate Data
API で生成された GraphQL スキーマによって提供されるタイプを使用して、クエリのデータをページ分割できます。 フックと フックを使用して、Apollo GraphQL クライアントでデータをページ分割できます。useQuery()
useLazyQueryHook()
Atlas GraphQL API には、 GraphQL offset
のドキュメントがページ分割を推奨して いるように、 演算子 はありません 。
次の例では、 useQuery()
フックと GraphQL クエリを使用します。これらのクエリは、 に渡す変数に応じて、昇順または降順でデータをクエリできます。
const PAGINATE_MOVIES = gql` query PaginateMovies( $prevTitle: String $nextTitle: String $limit: Int! $sortDirection: MovieSortByInput! ) { movies( # Can add other query filters here if you'd like query: { title_gt: $prevTitle, title_lt: $nextTitle } limit: $limit sortBy: $sortDirection ) { _id title year } } `; const resultsPerPage = 5; function PaginateMovies() { const [variables, setVariables] = useState({ prevTitle: undefined, nextTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); const [firstTitle, setFirstTitle] = useState(); const { data, error, loading } = useQuery(PAGINATE_MOVIES, { variables, }); const [pagePreviousDisabled, setPagePreviousDisabled] = useState(true); const [pageNextDisabled, setPageNextDisabled] = useState(false); useEffect(() => { if (data?.movies?.length && firstTitle === undefined) { setFirstTitle(data.movies[0].title); } setPagePreviousDisabled(false); if (data?.movies?.length < resultsPerPage) { setPageNextDisabled(true); setPagePreviousDisabled(false); } if ( variables.prevTitle === undefined || data?.movies[0]?.title === firstTitle ) { setPagePreviousDisabled(true); setPageNextDisabled(false); } }, [data, data?.movies?.length, firstTitle, variables.prevTitle]); if (loading) { return <div>loading</div>; } if (error) { return <div>encountered an error: {error.message}</div>; } function goToNextPage() { setVariables({ nextTitle: undefined, prevTitle: data.movies[data.movies.length - 1].title, limit: resultsPerPage, sortDirection: "TITLE_ASC", }); } function goToPrevPage() { setVariables({ nextTitle: data.movies[0].title, prevTitle: undefined, limit: resultsPerPage, sortDirection: "TITLE_DESC", }); } const sorted = data.movies.sort((a, b) => { const titleA = a.title.toUpperCase(); // ignore upper and lowercase const titleB = b.title.toUpperCase(); // ignore upper and lowercase if (titleA < titleB) { return -1; // titleA comes first } if (titleA > titleB) { return 1; // titleB comes first } }); return ( <div> <h1>Movies</h1> {data?.movies?.length ? ( sorted.map((movie) => ( <div key={movie._id}> <h3>{movie.title}</h3> <p>Year Published: {" " + movie.year}</p> <br /> </div> )) ) : ( <p>No movies in system</p> )} <div> <button disabled={pagePreviousDisabled} onClick={goToPrevPage}> ← Previous Page </button> <button disabled={pageNextDisabled} onClick={goToNextPage}> Next Page → </button> </div> </div> ); }
アクセス トークンを更新
Realm GraphQL と Apollo クライアントを使用する場合、アクセス トークンは付与後 30 分で期限切れになります。 Realm Web SDK のrefreshAccessToken()
メソッドを使用してユーザー アクセス トークンを更新できます。
// Connect to your MongoDB Realm app const app = new Realm.App(APP_ID); // Gets a valid Realm user access token to authenticate requests async function getValidAccessToken() { // Guarantee that there's a logged in user with a valid access token if (!app.currentUser) { // If no user is logged in, log in an anonymous user. The logged in user will have a valid // access token. await app.logIn(Realm.Credentials.anonymous()); } else { // An already logged in user's access token might be stale. Tokens must be refreshed after // 30 minutes. To guarantee that the token is valid, we refresh the user's access token. await app.currentUser.refreshAccessToken(); } return app.currentUser.accessToken; } // Configure the ApolloClient to connect to your app's GraphQL endpoint const client = new ApolloClient({ link: new HttpLink({ uri: `https://services.cloud.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`, // We define a custom fetch handler for the Apollo client that lets us authenticate GraphQL requests. // The function intercepts every Apollo HTTP request and adds an Authorization header with a valid // access token before sending the request. fetch: async (uri, options) => { const accessToken = await getValidAccessToken(); options.headers.Authorization = `Bearer ${accessToken}`; return fetch(uri, options); }, }), cache: new InMemoryCache(), });