From 083e9ef2310dcd12fb504b5ef77b161b53b8a790 Mon Sep 17 00:00:00 2001 From: wassim-k Date: Tue, 18 Jun 2024 13:29:58 +1000 Subject: [PATCH] docs #2 --- docs/docs/angular/fetching/mutations.mdx | 2 +- docs/docs/angular/fetching/queries.mdx | 35 ++- docs/docs/angular/fetching/subscriptions.mdx | 2 +- .../angular/get-started/setup-environment.mdx | 43 ++- docs/docs/angular/{index.md => index.mdx} | 8 +- docs/docs/angular/state/action.mdx | 10 +- docs/docs/angular/state/intro.mdx | 11 +- docs/docs/angular/state/local-state.mdx | 15 +- docs/docs/angular/state/mutation-update.mdx | 9 +- .../angular/state/optimistic-response.mdx | 2 +- docs/docs/angular/state/refetch-queries.mdx | 6 +- docs/docs/angular/state/resolver.mdx | 6 +- docs/docs/angular/state/state-slice.mdx | 2 +- docs/docs/angular/testing/index.mdx | 2 +- docs/docs/react/get-started.mdx | 105 +++++++ docs/docs/react/index.md | 7 - docs/docs/react/index.mdx | 16 ++ docs/docs/react/state/_category_.json | 6 + docs/docs/react/state/action.mdx | 101 +++++++ docs/docs/react/state/effect.mdx | 27 ++ docs/docs/react/state/local-state.mdx | 267 ++++++++++++++++++ docs/docs/react/state/mutation-update.mdx | 100 +++++++ docs/docs/react/state/optimistic-response.mdx | 33 +++ docs/docs/react/state/refetch-queries.mdx | 38 +++ docs/docs/react/state/resolver.mdx | 85 ++++++ docs/docs/react/state/state-slice.mdx | 66 +++++ docs/docs/shared/_codegen-tip.mdx | 9 + docs/docs/shared/_event-command.mdx | 5 + docs/docs/shared/_state-features.mdx | 6 + docs/docusaurus.config.js | 4 +- 30 files changed, 948 insertions(+), 80 deletions(-) rename docs/docs/angular/{index.md => index.mdx} (68%) create mode 100644 docs/docs/react/get-started.mdx delete mode 100644 docs/docs/react/index.md create mode 100644 docs/docs/react/index.mdx create mode 100644 docs/docs/react/state/_category_.json create mode 100644 docs/docs/react/state/action.mdx create mode 100644 docs/docs/react/state/effect.mdx create mode 100644 docs/docs/react/state/local-state.mdx create mode 100644 docs/docs/react/state/mutation-update.mdx create mode 100644 docs/docs/react/state/optimistic-response.mdx create mode 100644 docs/docs/react/state/refetch-queries.mdx create mode 100644 docs/docs/react/state/resolver.mdx create mode 100644 docs/docs/react/state/state-slice.mdx create mode 100644 docs/docs/shared/_codegen-tip.mdx create mode 100644 docs/docs/shared/_event-command.mdx create mode 100644 docs/docs/shared/_state-features.mdx diff --git a/docs/docs/angular/fetching/mutations.mdx b/docs/docs/angular/fetching/mutations.mdx index c9424cf..7aeba3d 100644 --- a/docs/docs/angular/fetching/mutations.mdx +++ b/docs/docs/angular/fetching/mutations.mdx @@ -85,7 +85,7 @@ In some **edge** cases both `data` and `error` might be undefined, for example w ::: ## `mapMutation` -Similar to `mapQuery`, `Apollo Orbit` provides a `mapMutation` RxJS operator to map the `data` of a mutation result while preserving the other properties. +Similar to `mapQuery`, **Apollo Orbit** provides a `mapMutation` RxJS operator to map the `data` of a mutation result while preserving the other properties. This is useful when you want to map the mutation result without dealing with the nullability of `data` property. ```typescript import { mapMutation } from '@apollo-orbit/angular'; diff --git a/docs/docs/angular/fetching/queries.mdx b/docs/docs/angular/fetching/queries.mdx index d644800..b2024df 100644 --- a/docs/docs/angular/fetching/queries.mdx +++ b/docs/docs/angular/fetching/queries.mdx @@ -83,7 +83,7 @@ import { BooksQuery } from '../graphql/types'; changeDetection: ChangeDetectionStrategy.OnPush }) export class BooksComponent { - protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' })); + protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery()); public constructor( private readonly apollo: Apollo @@ -92,7 +92,7 @@ export class BooksComponent { ``` Then, we subscribe to the query using `async` pipe and handle the different query states: ```html title="books/books.component.html" -

Books (Fiction)

+

Books

@if (booksQuery | async; as booksResult) { @if (booksResult.loading) { Loading... } @if (booksResult.error) { {{ booksResult.error.message }} } @@ -104,13 +104,30 @@ Then, we subscribe to the query using `async` pipe and handle the different quer } ``` +## Variables +Variables are passed as constructor parameters to `new BooksQuery()`: + +```typescript title="books/books.component.ts" +@Component({ + ... +}) +export class BooksComponent { + // code-add-line + protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' })); + + ... +} +``` + +This ensures that a query with required variables generates a class with required constructor parameters as explained in the [Codegen](../codegen/generated-code) section. + ## Options For the complete list of options available to query methods, please refer to Apollo Client docs In the next example, let's see how we can modify the query from the previous example in order to allow users to manually refetch the data. First, we will need to pass `notifyOnNetworkStatusChange` option to our `watchQuery` method, this tells Apollo Client to set `loading` property to true whenever a refetch is in flight. -In order to do that, we will need to spread the `query`, `variables` & `context` properties of `new BooksQuery({ genre: 'Fiction' })` and add `notifyOnNetworkStatusChange: true` to the options object. +In order to do that, we will need to spread the `query`, `variables` & `context` properties of `new BooksQuery()` and add `notifyOnNetworkStatusChange: true` to the options object. Secondly, we define a method in our component that calls `refetch` method on the `QueryObservable` instance. @@ -127,9 +144,9 @@ import { BooksQuery } from '../graphql/types'; }) export class BooksComponent { // code-remove-line - protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' })); + protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery()); // code-add-line - protected readonly booksQuery = this.apollo.watchQuery({ ...new BooksQuery({ genre: 'Fiction' }), notifyOnNetworkStatusChange: true }); + protected readonly booksQuery = this.apollo.watchQuery({ ...new BooksQuery(), notifyOnNetworkStatusChange: true }); public constructor( private readonly apollo: Apollo @@ -145,11 +162,11 @@ export class BooksComponent { Then, we modify the template to include a button that calls `refetch`, and finally, we replace `booksResult.data?.books` with `booksResult.data ?? booksResult.previousData`. This is because while a refetch is in flight, the `QueryResult` object will have `{ loading: true, data: undefined, ... }` which can provide a jarring user experience as the data disappears off the screen until the new data arrives. -**Apollo Orbit** exposes a `previousData` property which stores the last non-nil value of the `data` property which can be used to provider a smoother user experience. +**Apollo Orbit** exposes a `previousData` property which stores the last non-nil value of the `data` property that can be used to provide a smoother user experience. ```html title="books/books.component.html"

- Books (Fiction) + Books // code-add-line

@@ -168,12 +185,12 @@ This is because while a refetch is in flight, the `QueryResult` object will have ``` ## `mapQuery` -`Apollo Orbit` provides a `mapQuery` RxJS operator to map both `data` and `previousData` of a query result while preserving the other properties. +**Apollo Orbit** provides a `mapQuery` RxJS operator to map both `data` and `previousData` of a query result while preserving the other properties. This is useful when you want to map the query result without dealing with the nullability of `data` and `previousData` properties. ```typescript import { mapQuery } from '@apollo-orbit/angular'; ... -protected readonly bookNames$: Observable>> = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' })).pipe( +protected readonly bookNames$: Observable>> = this.apollo.watchQuery(new BooksQuery()).pipe( // code-add-line mapQuery(data => data.books.map(book => book.name)) ); diff --git a/docs/docs/angular/fetching/subscriptions.mdx b/docs/docs/angular/fetching/subscriptions.mdx index 69e7851..7a03426 100644 --- a/docs/docs/angular/fetching/subscriptions.mdx +++ b/docs/docs/angular/fetching/subscriptions.mdx @@ -76,7 +76,7 @@ Then, we subscribe to the data using `async` pipe: ``` ## `mapSubscription` -`Apollo Orbit` provides a `mapSubscription` RxJS operator to map `data` of a subscription result while preserving the other properties. +**Apollo Orbit** provides a `mapSubscription` RxJS operator to map `data` of a subscription result while preserving the other properties. This is useful when you want to map the subscription result without dealing with the nullability of `data` property. ```typescript import { mapSubscription } from '@apollo-orbit/angular'; diff --git a/docs/docs/angular/get-started/setup-environment.mdx b/docs/docs/angular/get-started/setup-environment.mdx index be8330c..dbccf53 100644 --- a/docs/docs/angular/get-started/setup-environment.mdx +++ b/docs/docs/angular/get-started/setup-environment.mdx @@ -3,9 +3,9 @@ sidebar_position: 2.1 --- import Link from '@docusaurus/Link'; - import TabItem from '@theme/TabItem'; import Tabs from '@theme/Tabs'; +import CodegenTip from '../../shared/_codegen-tip.mdx'; # Setup Environment The following steps will get you fully setup with `@apollo-orbit/angular` and `@apollo-orbit/codegen`. @@ -28,12 +28,7 @@ All the code samples in the docs assume that the following steps were completed ### Setup `@apollo-orbit/codegen` - -:::info -While `@apollo-orbit/angular` can work fully without `@apollo-orbit/codegen` it is highly recommended to use codegen in order to unlock its full potential. -::: - -More information on `@apollo-orbit/codegen` can be found in the [Codegen guide](../codegen/intro). + #### Install codegen dependencies @@ -66,7 +61,6 @@ More information on `@apollo-orbit/codegen` can be found in the [Codegen guide]( `yarn codegen` can also be executed manually to trigger GraphQL codegen. ::: - #### Create codegen.ts ```typescript title="codegen.ts" @@ -112,32 +106,35 @@ const config: CodegenConfig = { export default config; ``` +More information on `@apollo-orbit/codegen` can be found in the [Codegen guide](../codegen/intro). -### Install Apollo GraphQL extension +### Setup GraphQL syntax highlighting & auto-complete -#### Install extension -VSCode Marketplace - Apollo GraphQL +#### Install VSCode extension +GraphQL: Language Feature Support -#### Create apollo.config.js +#### Create graphql.config.js Create the following file in the root folder of your project. -For more information, visit Configuring Apollo projects -```javascript title="apollo.config.js" +```javascript title="graphql.config.js" module.exports = { - client: { - service: { - name: 'API', - url: 'http://localhost:4000/graphql', - skipSSLValidation: true - }, - includes: [ - `${__dirname}/src/app/**/*.graphql`, - `${__dirname}/src/app/**/*.state.ts` + schema: [ + 'http://localhost:4000/graphql', + 'src/app/**/*.state.ts' + ], + documents: [ + 'src/app/**/*.graphql' + ], + extensions: { + customDirectives: [ + 'directive @client on FIELD' // required for Apollo Client local schema ] } }; ``` +For more information, visit configuration documentation + ### Update ESLint Add the following rule to *.eslintrc.js* to ensure the correct import path is used in your application. ```javascript title=".eslintrc.js " diff --git a/docs/docs/angular/index.md b/docs/docs/angular/index.mdx similarity index 68% rename from docs/docs/angular/index.md rename to docs/docs/angular/index.mdx index 7a53c84..63c3b6e 100644 --- a/docs/docs/angular/index.md +++ b/docs/docs/angular/index.mdx @@ -3,6 +3,7 @@ sidebar_position: 1 --- import Link from '@docusaurus/Link' +import StateFeatures from '../shared/_state-features.mdx' # Introduction **Apollo Orbit** is a fully-featured **Apollo Client** for **Angular**. @@ -20,12 +21,7 @@ import Link from '@docusaurus/Link' * **Battle tested and production ready:** used in production environment for 3+ years. ### State Management -* **Comprehensive state management**: **Apollo Orbit** combines the strengths of **Apollo Client** and traditional state management libraries, providing a unified solution for managing both local and remote data. -* **Decoupling**: Separate state management code from component code using modular `state` definitions and `action` handlers. -* **Modular:** Each `state` definition manage its own slice of the cache. -* **Separation of concerns (SoC):** Different `state` slices can handle the same `Mutation` or `Action` independently. -* **Event-driven architecture:** **Apollo Orbit** `@Action`s enable event driven application design. -* **Testability:** `state` logic can be tested in isolation, enhancing testability. + ## Docs The docs are designed to supplement Apollo Client docs, with focuses on the **Angular** specific API and features implemented by **Apollo Orbit**. diff --git a/docs/docs/angular/state/action.mdx b/docs/docs/angular/state/action.mdx index 0eb68c3..b3fd5cd 100644 --- a/docs/docs/angular/state/action.mdx +++ b/docs/docs/angular/state/action.mdx @@ -3,6 +3,8 @@ sidebar_position: 8 title: 'action' --- +import EventCommand from '../../shared/_event-command.mdx'; + ## Overview Actions are simple classes that have a unique `type` property and, optionally, additional information. @@ -10,11 +12,7 @@ Actions generally take one of two forms: * **Command**: A verb. For example, `[Theme] ToggleTheme` * **Event**: Past tense. For example, `[Library] BookSelected` -:::tip -When unsure whether to use a **command** or an **event** for a certain action, it is advisable to choose **event**. -Events simply relay what has already happened, without making assumptions about how the various states should react. -In contrast, a command could potentially obscure information about what has happened and dictate what states should do. -::: + ## Defining an action An action is required to have a unique `type`. By convention, `type` follows the format `[] ` to uniquely identify the action throughout the app. @@ -80,7 +78,7 @@ interface ActionType { ## Handling an action `action` is used in a `state` slice to define an action handler. -An `action` handler can be synchronous or asynchronous and can be used to update the state, call a service, or perform any other logic. +An `action` handler can be synchronous or asynchronous and can be used to update the cache, call a service, or perform any other logic. Action handlers can be chained together to form a sequence of actions that are executed in order, by using the `dispatch` function which is available in the `ActionContext` parameter. diff --git a/docs/docs/angular/state/intro.mdx b/docs/docs/angular/state/intro.mdx index 9f6492f..d07bdd3 100644 --- a/docs/docs/angular/state/intro.mdx +++ b/docs/docs/angular/state/intro.mdx @@ -3,15 +3,12 @@ sidebar_position: 1 pagination_label: State --- +import StateFeatures from '../../shared/_state-features.mdx' + # Introduction **Apollo Client** is much more than a simple GraphQL client. It offers a comprehensive caching API that leverages GraphQL's rich type information system (schema) to provide features like cache normalization, optimistic UI and more. -**Apollo Orbit** aims to bridge the gap between **Apollo Client** and a full-fledged state management solutions like NGXS, NgRx or Redux. +**Apollo Orbit** aims to bridge the gap between **Apollo Client** and a full-fledged state management solution like NGXS, NgRx or Redux. It does so by providing the following features: -* **Comprehensive state management**: **Apollo Orbit** combines the strengths of **Apollo Client** and traditional state management libraries, providing a unified solution for managing both local and remote data. -* **Decoupling**: Separate state management code from component code using modular `state` definitions and `action` handlers. -* **Modular:** Each `state` definition manage its own slice of the cache. -* **Separation of concerns (SoC):** Different `state` slices can handle the same `Mutation` or `Action` independently. -* **Event-driven architecture:** **Apollo Orbit** `@Action`s enable event driven application design. -* **Testability:** `state` logic can be tested in isolation, enhancing testability. + diff --git a/docs/docs/angular/state/local-state.mdx b/docs/docs/angular/state/local-state.mdx index 8f2437d..4d939ac 100644 --- a/docs/docs/angular/state/local-state.mdx +++ b/docs/docs/angular/state/local-state.mdx @@ -19,8 +19,7 @@ In this example, we'll build a theme management feature that allows users to swi First, we'll start by defining the local schema for our theme management feature, by creating a theme `state` slice and defining the schema in `typeDefs`. ```typescript title="states/theme/theme.state.ts" -import { state } from '@apollo-orbit/angular'; -import gql from 'graphql-tag'; +import { gql, state } from '@apollo-orbit/angular'; export const themeState = () => { return state(descriptor => descriptor @@ -38,8 +37,8 @@ export const themeState = () => { extend type Query { theme: Theme! - }` - ) + } + `) ); }; ``` @@ -180,7 +179,7 @@ export function provideGraphQL(): EnvironmentProviders { } ``` -## 3. Define theme component +## 3. Create theme component Finally, we'll bring it all together by defining our theme component which queries the cache and dispatches the relevant actions. ```typescript title="theme/theme.component.ts" @@ -226,7 +225,7 @@ There you have it! we've created our first fully local state managed feature wit
Here's the complete `theme.state.ts` code for reference - ```typescript title="states/theme/theme.state.ts"/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + ```typescript title="states/theme/theme.state.ts" import { inject } from '@angular/core'; import { gql, state } from '@apollo-orbit/angular'; import { ThemeName, ThemeQuery } from '../../graphql/types'; @@ -251,8 +250,8 @@ There you have it! we've created our first fully local state managed feature wit extend type Query { theme: Theme! - }` - ) + } + `) .typePolicies({ Theme: { fields: { diff --git a/docs/docs/angular/state/mutation-update.mdx b/docs/docs/angular/state/mutation-update.mdx index e62f5da..c07dc33 100644 --- a/docs/docs/angular/state/mutation-update.mdx +++ b/docs/docs/angular/state/mutation-update.mdx @@ -10,7 +10,7 @@ import Link from '@docusaurus/Link' It is used to update the cache following the execution of a mutation. ## Usage -As stated previously in the [`mutations guid`](../fetching/mutations#caching), unlike `UpdateBookMutation`, updating the cache after executing `AddBookMutation` requires manual intervention. +As stated previously in the [`mutations guid`](../fetching/mutations#cache-normalisation), unlike `UpdateBookMutation`, updating the cache after executing `AddBookMutation` requires manual intervention. Let's build on the same example used in [fetching guide](../fetching/queries) to demonstrate how `mutationUpdate` can be used to update the cache after executing `AddBookMutation`. @@ -36,7 +36,7 @@ export const bookState = state(descriptor => descriptor .mutationUpdate(AddBookMutation, (cache, info) => { const addBook = info.data?.addBook; if (!addBook) return; - cache.updateQuery(new BooksQuery({ genre: 'Fiction' }), query => query ? { books: [...query.books, addBook] } : query); + cache.updateQuery(new BooksQuery(), query => query ? { books: [...query.books, addBook] } : query); }) ); ``` @@ -68,6 +68,11 @@ The example above demonstrates how the same mutation can be handled independentl `info` argument is of type `MutationInfo` and all logic in `mutationUpdate` handler is type-safe. +:::note +`mutationUpdate` also accepts the name of the mutation as a `string` argument. This can be used in projects that do not have codegen setup. +So the above code can be updated to `.mutationUpdate('AddBook', (cache, info) =>` and the rest of the code remains the same, except it won't have type-safety. +::: + ## API ```typescript interface MutationInfo { diff --git a/docs/docs/angular/state/optimistic-response.mdx b/docs/docs/angular/state/optimistic-response.mdx index 4299bdf..36cf533 100644 --- a/docs/docs/angular/state/optimistic-response.mdx +++ b/docs/docs/angular/state/optimistic-response.mdx @@ -6,7 +6,7 @@ title: 'optimisticResponse' import Link from '@docusaurus/Link' ## Overview -`optimisticResponse` is analogous to `optimisticResponse` option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`. +`optimisticResponse` is analogous to `optimisticResponse` option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`. It enables optimistic UI, allowing for immediate UI updates before the server responds. ## Usage diff --git a/docs/docs/angular/state/refetch-queries.mdx b/docs/docs/angular/state/refetch-queries.mdx index b640f7a..bc8d3ea 100644 --- a/docs/docs/angular/state/refetch-queries.mdx +++ b/docs/docs/angular/state/refetch-queries.mdx @@ -6,7 +6,7 @@ title: 'refetchQueries' import Link from '@docusaurus/Link' ## Overview -`refetchQueries` is analogous to `refetchQueries` option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`. +`refetchQueries` is analogous to `refetchQueries` option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`. It enables refetching queries after a mutation has executed. ## Usage @@ -18,12 +18,12 @@ import { AddBookMutation, BooksQuery } from '../../graphql/types'; export const bookState = state(descriptor => descriptor // code-add-line - .refetchQueries(AddBookMutation, info => [new BooksQuery({ genre: 'Fiction' })]) + .refetchQueries(AddBookMutation, info => [new BooksQuery()]) // code-remove-start .mutationUpdate(AddBookMutation, (cache, info) => { const addBook = info.data?.addBook; if (!addBook) return; - cache.updateQuery(new BooksQuery({ genre: 'Fiction' }), query => query ? { books: [...query.books, addBook] } : query); + cache.updateQuery(new BooksQuery(), query => query ? { books: [...query.books, addBook] } : query); }) // code-remove-end ); diff --git a/docs/docs/angular/state/resolver.mdx b/docs/docs/angular/state/resolver.mdx index 83ce29e..ed78a69 100644 --- a/docs/docs/angular/state/resolver.mdx +++ b/docs/docs/angular/state/resolver.mdx @@ -25,7 +25,8 @@ export const bookState = state(descriptor => descriptor .typeDefs(gql` extend type Book { displayName: String! - }`) + } + `) .resolver(['Book', 'displayName'], (rootValue: Book, args, context, info): Book['displayName'] => { const { name, genre } = rootValue; return typeof genre === 'string' ? `${name} (${genre})` : name; @@ -65,7 +66,8 @@ export const bookState = state(descriptor => descriptor .typeDefs(gql` extend type Book { displayName: String! - }`) + } + `) .typePolicies({ Book: { fields: { diff --git a/docs/docs/angular/state/state-slice.mdx b/docs/docs/angular/state/state-slice.mdx index c1aee7a..cd2189f 100644 --- a/docs/docs/angular/state/state-slice.mdx +++ b/docs/docs/angular/state/state-slice.mdx @@ -6,7 +6,7 @@ title: 'state' import Link from '@docusaurus/Link' ## Overview -`state` function is used to define a state slice. +`state` function is used to create a state slice. `state` provides a `descriptor` which can be used to define one of the following: * [clientId](../advanced/multi-client) diff --git a/docs/docs/angular/testing/index.mdx b/docs/docs/angular/testing/index.mdx index 53f3378..9ab6e9e 100644 --- a/docs/docs/angular/testing/index.mdx +++ b/docs/docs/angular/testing/index.mdx @@ -33,7 +33,7 @@ class BookComponent { } ``` -In order to write unit tests for the above component, we first start by creating our `ApolloMockModule` which will be shared across all tests in the application and be customised to suit your application's needs. +In order to write unit tests for the above component, we first start by creating our `ApolloMockModule` which will be shared across all tests in the application and can be customised to suit your application's needs. ```typescript title="apollo-mock.module.ts" import { NgModule, inject } from '@angular/core'; import { ApolloOptions, InMemoryCache, provideApolloOrbit, withApolloOptions } from '@apollo-orbit/angular'; diff --git a/docs/docs/react/get-started.mdx b/docs/docs/react/get-started.mdx new file mode 100644 index 0000000..60696c8 --- /dev/null +++ b/docs/docs/react/get-started.mdx @@ -0,0 +1,105 @@ +--- +sidebar_position: 2 +--- + +import Link from '@docusaurus/Link'; +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; +import CodegenTip from '../shared/_codegen-tip.mdx'; + +# Getting Started + +## Prerequisites +In order to use **Apollo Orbit**, you'll need a **React** project setup with **Apollo Client**. +If not, please follow the Apollo Client setup guide before proceeding. + +## Install `@apollo-orbit/react` + + + + ```bash + yarn add @apollo-orbit/react + ``` + + + ```bash + npm install @apollo-orbit/react + ``` + + + +## Setup GraphQL Codegen + + +You can follow GraphQL-Codegen React guide to setup codegen for your project. + +Then, update *codegen.ts* to include state files in the schema: + +```typescript title="codegen.ts" +import { CodegenConfig } from '@graphql-codegen/cli'; + +const config: CodegenConfig = { + schema: [ + 'http://localhost:4000/graphql', + // code-add-start + { + './src/**/*.state.ts': { + noRequire: true + } + } + // code-add-end + ], + ... +}; + +export default config; +``` + +## Setup GraphQL syntax highlighting & auto-complete + +### Install VSCode extension +GraphQL: Language Feature Support + +### Create graphql.config.js +Create the following file in the root folder of your project. + +```javascript title="graphql.config.js" +module.exports = { + schema: [ + 'http://localhost:4000/graphql', + // code-add-line + 'src/**/*.state.ts' + ], + documents: [ + 'src/**/*.graphql' + ], + extensions: { + customDirectives: [ + 'directive @client on FIELD' // required for Apollo Client local schema + ] + } +}; +``` + +If you already have the extension setup in your project, make sure to include state files in the schema by adding the highlighted line above. + +For more information, visit configuration documentation + +## Provide states +Finally, we can provide states to **Apollo Orbit** by adding `ApolloOrbitProvider` under `ApolloProvider`: + +```tsx title="index.tsx" +import { themeState } from './states/theme'; + +root.render( + + // code-add-line + + + // code-add-line + + , +); +``` + +The next section will cover in detail how to create and provide states in your **React** application. diff --git a/docs/docs/react/index.md b/docs/docs/react/index.md deleted file mode 100644 index e79820f..0000000 --- a/docs/docs/react/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 ---- - -# React - -Coming soon... diff --git a/docs/docs/react/index.mdx b/docs/docs/react/index.mdx new file mode 100644 index 0000000..fc0e1d5 --- /dev/null +++ b/docs/docs/react/index.mdx @@ -0,0 +1,16 @@ +--- +sidebar_position: 1 +--- + +import Link from '@docusaurus/Link' +import StateFeatures from '../shared/_state-features.mdx' + +# Introduction +**Apollo Orbit** is an **Apollo Client** modular state management abstraction for **React**. + +**Apollo Client** is much more than a simple GraphQL client, it offers a comprehensive caching API that leverages GraphQL's rich type information system (schema) to provide features like cache normalization, optimistic UI and more. + +**Apollo Orbit** aims to bridge the gap between **Apollo Client** and a full-fledged state management solution like Redux. + +It does so by providing the following features: + diff --git a/docs/docs/react/state/_category_.json b/docs/docs/react/state/_category_.json new file mode 100644 index 0000000..6392aa8 --- /dev/null +++ b/docs/docs/react/state/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 3, + "label": "State", + "collapsible": true, + "collapsed": false +} diff --git a/docs/docs/react/state/action.mdx b/docs/docs/react/state/action.mdx new file mode 100644 index 0000000..2e792a3 --- /dev/null +++ b/docs/docs/react/state/action.mdx @@ -0,0 +1,101 @@ +--- +sidebar_position: 7 +title: 'action' +--- + +import EventCommand from '../../shared/_event-command.mdx'; + +## Overview +Actions are simple objects that have a unique `type` property and, optionally, additional information. + +Actions generally take one of two forms: +* **Command**: A verb. For example, `theme/toggle` +* **Event**: Past tense. For example, `library/bookSelected` + + + +## Defining an action +An action is required to have a unique `type`. By convention, `type` follows the format `domain/action` to uniquely identify the action throughout the app. + +```typescript title="states/theme/theme.actions.ts" +import { ThemeName } from '../../graphql'; + +export interface ToggleThemeAction { + type: 'theme/toggle'; + force?: ThemeName; +} +``` + +## Dispatching an action +The `useDispatch()` hook returns a reference to a `dispatch` function that can be used to dispatch actions. For example: + +```tsx +import { useDispatch } from '@apollo-orbit/react'; +import { useQuery } from '@apollo/client'; +import { ThemeDocument } from './graphql'; +import { ToggleThemeAction } from './states/theme/theme.actions'; + +export function Theme() { + // code-add-line + const dispatch = useDispatch(); + const { data: themeData } = useQuery(ThemeDocument); + + return ( +
+ Current theme: + {themeData?.theme.displayName} + // code-add-line + +
+ ); +} +``` + +`dispatch` returns a `Promise` which completes when all states have completed handling the action. If any of the `action` handlers throws an error, then the promise is rejected with the error. This can be useful for executing some logic only after an action has completed, especially if some of the handlers are asynchronous. + +## Handling an action +`action` is used in a `state` slice to define an action handler. + +An `action` handler can be synchronous or asynchronous and can be used to update the cache, execute a side-effect, or perform any other logic. + +Action handlers can be chained together to form a sequence of actions that are executed in order, by using the `dispatch` function which is available in the `ActionContext` parameter. + +### Usage +Let's look at an example of how a `LogoutUserAction` can be handled asynchronously in the **user** `state` and chained with another action handler. + +```typescript title="states/user/user.state.ts" +export const userState = state(descriptor => descriptor + .action('user/logout', async (action, { dispatch }) => { + await userSessionApi.endUserSession(); + return dispatch({ type: 'user/logged-out' }); + }) + .action('user/logged-out', (action, { cache }) => { + cache.writeQuery({ query: CurrentUserDocument, data: { currentUser: null } }); + }) +); +``` + +When `dispatch({ type: 'user/logout' })` is called from a component, it will: +1. First, invoke the `LogoutUserAction` handler. +2. Then, invoke the `UserLoggedOutAction` handler. +3. Finally, resolve the promise returned by `dispatch({ type: 'user/logout' })` when both handlers have finished executing. + +:::note +It is important to return the promise from an asynchronous action handler to ensure that the promise returned by `dispatch` completes only after the action handler has finished executing. +::: + +### API +```typescript +type ActionFn = (action: T, context: ActionContext) => void | Promise; + +interface Action { + type: string; +} + +interface ActionContext { + cache: ApolloCache; + dispatch(action: TAction): Promise; +} +``` + +In the next page we will see a complete example of how actions and their handlers can be used for managing [local state](./local-state). diff --git a/docs/docs/react/state/effect.mdx b/docs/docs/react/state/effect.mdx new file mode 100644 index 0000000..2ad4a33 --- /dev/null +++ b/docs/docs/react/state/effect.mdx @@ -0,0 +1,27 @@ +--- +sidebar_position: 3 +title: 'effect' +--- + +## Overview +`effect` is used to execute a side-effect after a mutation has completed whether successful or not. + +## Usage +Let's extend `bookState` to display a notification when `AddBook` mutation is executed. + +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import { AddBookDocument } from '../../graphql'; +import { notification } from '../../ui/notification'; + +export const bookState = state(descriptor => descriptor + ... + .effect(AddBookDocument, info => { + if (info.data?.addBook) { + notification.success(`New book '${info.data.addBook.name}' was added successfully.`); + } else if (info.error) { + notification.error(`Failed to add book '${info.variables?.book.name}': ${info.error.message}`); + } + }) +); +``` diff --git a/docs/docs/react/state/local-state.mdx b/docs/docs/react/state/local-state.mdx new file mode 100644 index 0000000..94a2984 --- /dev/null +++ b/docs/docs/react/state/local-state.mdx @@ -0,0 +1,267 @@ +--- +sidebar_position: 8 +title: 'Local State' +--- + +import Link from '@docusaurus/Link' + +## Prerequisites +This article assumes you're familiar with Apollo Client's local state management capabilities. + +## Overview +In this article, we'll go through a complete example of managing local state with **Apollo Orbit**, including setting up the cache, defining local state, and updating the cache. +In this example, we'll build a theme management feature that allows users to switch between light and dark themes. + + +## 1. Define theme `state` slice + +### Define local schema +First, we'll start by defining the local schema for our theme management feature, by creating a theme `state` slice and defining the schema in `typeDefs`. + +```typescript title="states/theme/theme.state.ts" +import { state } from '@apollo-orbit/react'; +import { gql } from '@apollo/client'; + +export const themeState = () => { + return state(descriptor => descriptor + .typeDefs(gql` + type Theme { + name: ThemeName! + displayName: String! + toggles: Int! + } + + enum ThemeName { + DARK_THEME + LIGHT_THEME + } + + extend type Query { + theme: Theme! + } + `) + ); +}; +``` + +Saving this file will trigger the codegen as per our setup. + +### Define GraphQL query +Next, we'll define a GraphQL query to fetch the current theme from the cache. + +```graphql title="states/theme/gql/theme.graphql" +query Theme { + theme @client { + name + toggles + displayName + } +} +``` + +### Initialise cache +Next, we'll define `onInit` function to initialise the cache with the default theme. + +```typescript title="states/theme/theme.state.ts" +import { state } from '@apollo-orbit/react'; +import { ThemeDocument, ThemeName } from '../../graphql'; + +export const themeState = () => { + return state(descriptor => descriptor + .onInit(cache => { + cache.writeQuery({ + query: ThemeDocument, + data: { + theme: { + __typename: 'Theme', + name: ThemeName.LightTheme, + toggles: 0, + displayName: 'Light' + } + } + }); + }) + ); +}; +``` + +### Define actions +Next, we'll create `theme.actions.ts` file to define actions for toggling the theme. + +```typescript title="states/theme/theme.actions.ts" +import { ThemeName } from '../../graphql'; + +export interface ToggleThemeAction { + type: 'theme/toggle'; + force?: ThemeName; +} + +export interface ThemeToggledAction { + type: 'theme/toggled'; + toggles: number; +} +``` + +### Define action handles +```typescript title="states/theme/theme.state.ts" +import { state } from '@apollo-orbit/react'; +import { ThemeDocument, ThemeName } from '../../graphql'; +import { notification } from '../../ui/notification'; +import { ThemeToggledAction, ToggleThemeAction } from './theme.actions'; + +export const themeState = state(descriptor => descriptor + .action( + 'theme/toggle', + (action, { cache, dispatch }) => { + const result = cache.updateQuery({ query: ThemeDocument }, data => data + ? { + theme: { + ...data.theme, + toggles: data.theme.toggles + 1, + name: action.force ?? data.theme.name === ThemeName.DarkTheme ? ThemeName.LightTheme : ThemeName.DarkTheme + } + } + : data); + + return dispatch({ type: 'theme/toggled', toggles: result?.theme.toggles as number }); + }) + .action( + 'theme/toggled', + action => { + notification.success(`Theme was toggled ${action.toggles} time(s)`); + }) +); + +``` + +### Define local field policies +Finally, we'll define local field policy for the `displayName` field. + +```typescript title="states/theme/theme.state.ts" +import { state } from '@apollo-orbit/react'; +import { ThemeName } from '../../graphql'; + +export const themeState = state(descriptor => descriptor + .typePolicies({ + Theme: { + fields: { + displayName: (existing, { readField }) => readField('name') === ThemeName.LightTheme ? 'Light' : 'Dark' + } + } + }) +); +``` + +## 2. Provide theme state +Next, we'll add `themeState` to `ApolloOrbitProvider` `states`. + +```tsx title="index.tsx" +import { themeState } from './states/theme'; + +root.render( + + // code-add-line + + + + , +); +``` + +## 3. Create theme component +Finally, we'll bring it all together by defining our theme component which queries the cache and dispatches the relevant actions. + +```tsx title="Theme.tsx" +import { useDispatch } from '@apollo-orbit/react'; +import { useQuery } from '@apollo/client'; +import { ThemeDocument } from './graphql'; +import { ToggleThemeAction } from './states/theme/theme.actions'; + +export function Theme() { + const dispatch = useDispatch(); + const { data: themeData } = useQuery(ThemeDocument); + + return ( +
+ Current theme: + {themeData?.theme.displayName} + +
+ ); +} +``` + +## Summary +There you have it! we've created our first fully local state managed feature with **Apollo Orbit**. + +
+ Here's the complete `theme.state.ts` code for reference + ```typescript title="states/theme/theme.state.ts" + import { state } from '@apollo-orbit/react'; + import { gql } from '@apollo/client'; + import { ThemeDocument, ThemeName } from '../../graphql'; + import { notification } from '../../ui/notification'; + import { ThemeToggledAction, ToggleThemeAction } from './theme.actions'; + + export const themeState = state(descriptor => descriptor + .typeDefs(gql` + type Theme { + name: ThemeName! + displayName: String! + toggles: Int! + } + + enum ThemeName { + DARK_THEME + LIGHT_THEME + } + + extend type Query { + theme: Theme! + } + `) + + .typePolicies({ + Theme: { + fields: { + displayName: (existing, { readField }) => readField('name') === ThemeName.LightTheme ? 'Light' : 'Dark' + } + } + }) + + .onInit(cache => cache.writeQuery({ + query: ThemeDocument, + data: { + theme: { + __typename: 'Theme', + name: ThemeName.LightTheme, + toggles: 0, + displayName: 'Light' + } + } + })) + + .action( + 'theme/toggle', + (action, { cache, dispatch }) => { + const result = cache.updateQuery({ query: ThemeDocument }, data => data + ? { + theme: { + ...data.theme, + toggles: data.theme.toggles + 1, + name: action.force ?? data.theme.name === ThemeName.DarkTheme ? ThemeName.LightTheme : ThemeName.DarkTheme + } + } + : data); + + return dispatch({ type: 'theme/toggled', toggles: result?.theme.toggles as number }); + }) + + .action( + 'theme/toggled', + action => { + notification.success(`Theme was toggled ${action.toggles} time(s)`); + }) + ); +``` +
diff --git a/docs/docs/react/state/mutation-update.mdx b/docs/docs/react/state/mutation-update.mdx new file mode 100644 index 0000000..ba4daf4 --- /dev/null +++ b/docs/docs/react/state/mutation-update.mdx @@ -0,0 +1,100 @@ +--- +sidebar_position: 2 +title: 'mutationUpdate' +--- + +import Link from '@docusaurus/Link' + +## Overview +`mutationUpdate` is analogous to the `update` option passed to `useMutation`. +It is used to update the cache following the execution of a mutation. + +## Usage +In this example, we have a book library management app. A book is present in two lists stored in the cache, one for all books in the library and one for books written by an author. + +First we add the mutation to `book.graphql` +```graphql title="library/gql/book.graphql" +fragment BookFragment on Book { + id + name + genre + authorId +} + +query Books($name: String, $genre: String, $authorId: ID) { + books(name: $name, genre: $genre, authorId: $authorId) { + ...BookFragment + } +} + +// code-add-start +mutation AddBook($book: BookInput!) { + addBook(book: $book) { + ...BookFragment + } +} +// code-add-end +``` + +Then, we define two state slices, one for managing `book` state and one for `author` state: + +### `bookState` +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import { AddBookDocument, BooksDocument } from '../../graphql'; + +export const bookState = state(descriptor => descriptor + .mutationUpdate(AddBookDocument, (cache, info) => { + const addBook = info.data?.addBook; + if (!addBook) return; + cache.updateQuery({ query: BooksDocument }, data => data ? { books: [...data.books, addBook] } : data); + }) +); +``` + +### `authorState` +```typescript title="library/states/author.state.ts" +import { identifyFragment, state } from '@apollo-orbit/react'; +import { AddBookDocument, AuthorFragmentDoc } from '../../graphql'; + +export const authorState = state(descriptor => descriptor + .mutationUpdate(AddBookDocument, (cache, info) => { + const addBook = info.data?.addBook; + if (!addBook) return; + const authorId = info.variables?.book.authorId as string; + + cache.updateFragment( + identifyFragment(AuthorFragmentDoc, authorId), + author => author ? ({ ...author, books: [...author.books, addBook] }) : author + ); + }) +); +``` + +`identifyFragment` is a helper function provided by **Apollo Orbit** for returning a fragment object that uniquely identifies a fragment in the cache. + +`info` argument is of type `MutationInfo` and all logic in `mutationUpdate` handler is type-safe. + +Then, we add the states to `ApolloOrbitProvider` as [demonstrated previously](./state-slice#adding-state): +```tsx title="index.tsx" +import { authorState } from './library/states/author.state'; +import { bookState } from './library/states/book.state'; + +root.render( + + // code-add-line + + + + , +); +``` + +Now, when a component calls `useMutation(AddBookDocument)`, the above mutation update functions are automatically executed and the UI displaying books and author books is updated. + +The example above demonstrates how the same mutation can be handled independently by different states, achieving separation of concerns (SoC) and complete decoupling between component and state logic. + +:::note +`mutationUpdate` also accepts the name of the mutation as a `string` argument. This can be used in projects that do not have codegen setup. +So the above code can be updated to `.mutationUpdate('AddBook', (cache, info) =>` and the rest of the code remains the same, except it won't have type-safety. +::: diff --git a/docs/docs/react/state/optimistic-response.mdx b/docs/docs/react/state/optimistic-response.mdx new file mode 100644 index 0000000..08bf2c2 --- /dev/null +++ b/docs/docs/react/state/optimistic-response.mdx @@ -0,0 +1,33 @@ +--- +sidebar_position: 4 +title: 'optimisticResponse' +--- + +import Link from '@docusaurus/Link' + +## Overview +`optimisticResponse` is analogous to `optimisticResponse` option passed to `useMutation`. +It enables optimistic UI, allowing for immediate UI updates before the server responds. + +## Usage +Let's extend `bookState` to return an optimistic response when `AddBook` mutation is executed. + +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import shortid from 'shortid'; +import { AddBookDocument } from '../../graphql'; + +export const bookState = state(descriptor => descriptor + .optimisticResponse(AddBookDocument, ({ book }) => ({ + __typename: 'Mutation' as const, + addBook: { + __typename: 'Book' as const, + id: shortid.generate(), + genre: book.genre ?? null, + name: book.name, + authorId: book.authorId + } + })) +); + +``` diff --git a/docs/docs/react/state/refetch-queries.mdx b/docs/docs/react/state/refetch-queries.mdx new file mode 100644 index 0000000..25df29b --- /dev/null +++ b/docs/docs/react/state/refetch-queries.mdx @@ -0,0 +1,38 @@ +--- +sidebar_position: 5 +title: 'refetchQueries' +--- + +import Link from '@docusaurus/Link' + +## Overview +`refetchQueries` is analogous to `refetchQueries` option passed to `useMutation`. +It enables refetching queries after a mutation has executed. + +## Usage +Let's modify `bookState` to refetch the full list of books from the server instead of modifying the cache on the client when `AddBook` mutation is executed. + +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import { AddBookDocument, BooksDocument } from '../../graphql'; + +export const bookState = state(descriptor => descriptor + // code-add-line + .refetchQueries(AddBookDocument, info => [BooksDocument]) + // code-remove-start + .mutationUpdate(AddBookDocument, (cache, info) => { + const addBook = info.data?.addBook; + if (!addBook) return; + cache.updateQuery(BooksDocument, query => query ? { books: [...query.books, addBook] } : query); + }) + // code-remove-end +); + +``` + +Now, whenever `AddBook` mutation is executed, `Books` query will be refetched from the server and any components watching `Books` query will be updated with the new data. + +## API +```typescript +type RefetchQueryDescriptor = Array | 'all' | 'active'; +``` diff --git a/docs/docs/react/state/resolver.mdx b/docs/docs/react/state/resolver.mdx new file mode 100644 index 0000000..ee7c63b --- /dev/null +++ b/docs/docs/react/state/resolver.mdx @@ -0,0 +1,85 @@ +--- +sidebar_position: 6 +title: 'resolver' +--- + +import Link from '@docusaurus/Link' + +## Overview +`resolver` is analogous to `resolvers` option passed to `ApolloClientOptions`. +It allows implementing local resolvers. + +:::note +It is recommended to use field policies instead of local resolvers as described in Local-only fields. +Local resolver support will be moved out of the core of Apollo Client in a future major release. The same or similar functionality will be available via `ApolloLink`. +::: + +## Usage +Let's modify `bookState` to to return `displayName` for each `Book` which is a concatenation of the book name and genre. + +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import { gql } from '@apollo/client'; +import { Book } from '../../graphql'; + +export const bookState = state(descriptor => descriptor + .typeDefs(gql` + extend type Book { + displayName: String! + } + `) + .resolver(['Book', 'displayName'], (rootValue: Book, args, context, info): Book['displayName'] => { + const { name, genre } = rootValue; + return typeof genre === 'string' ? `${name} (${genre})` : name; + }) +); +``` + +Since `*.state.ts` files are included in our [codegen configuration](../get-started#setup-graphql-codegen), defining local schema using `typeDefs` will get automatically picked up by codegen. + +Next, we update `book.graphql` to include `displayName` field: +```graphql title="library/gql/book.graphql" +fragment BookFragment on Book { + id + name + genre + authorId + // code-add-line + displayName @client +} + +... +``` + +Now, `displayName` can be used in the component to display the book name along with genre. + +## API +`resolve` methods can return a value or a promise. + +## Local-only fields (Recommended) +In order to implement the same functionality using field policies, we can modify `bookState` as follows: + +```typescript title="library/states/book.state.ts" +import { state } from '@apollo-orbit/react'; +import { gql } from '@apollo/client'; +import { Book } from '../../graphql'; + +export const bookState = state(descriptor => descriptor + .typeDefs(gql` + extend type Book { + displayName: String! + } + `) + .typePolicies({ + Book: { + fields: { + displayName: (_existing, { readField }) => { + const name = readField('name'); + const genre = readField('genre'); + return typeof genre === 'string' ? `${name} (${genre})` : name; + } + } + } + }) +); +``` diff --git a/docs/docs/react/state/state-slice.mdx b/docs/docs/react/state/state-slice.mdx new file mode 100644 index 0000000..285904b --- /dev/null +++ b/docs/docs/react/state/state-slice.mdx @@ -0,0 +1,66 @@ +--- +sidebar_position: 1 +title: 'state' +--- + +import Link from '@docusaurus/Link' + +## Overview +`state` function is used to create a state slice. + +`state` provides a `descriptor` which can be used to define one of the following: +* typeDefs +* typePolicies +* possibleTypes +* [onInit](#oninit) +* [mutationUpdate](./mutation-update) +* [effect](./effect) +* [optimisticResponse](./optimistic-response) +* [refetchQueries](./refetch-queries) +* [resolver](./resolver) +* [action](./action) + +## Defining state +Defining a state slice is as simple as calling `state` function. + +```typescript title="states/theme/theme.state.ts" +export const themeState = state(descriptor => ...) +``` + +:::note +A `state` slice file name should be in the format `.state.ts`, this is a requirement as per our [setup guide](../get-started). +`` is singular by convention for simplicity. +::: + +## Adding state +Once a `state` slice is created it needs to be provided to `ApolloOrbitProvider` `states` array. + +`ApolloOrbitProvider` must be nested under `ApolloProvider` to ensure that it has access to the `ApolloClient` instance used in the app. + +`ApolloOrbitProvider` can be used **multiple times** and at at **any level** in the application, including in lazy loaded components. + +### Example +```tsx title="index.tsx" +import { themeState } from './states/theme'; + +root.render( + + // code-add-line + + + // code-add-line + + , +); +``` + +## `onInit` +`onInit` defines a callback that is invoked when the state is initialised. + +```typescript title="states/theme/theme.state.ts" +export const themeState = state(descriptor => descriptor + .onInit(cache => cache.writeQuery(...)) +); +``` + +`onInit` callback accepts the cache instance and can be used to initialise cache data for the state. More on this in [local state guide](./local-state). diff --git a/docs/docs/shared/_codegen-tip.mdx b/docs/docs/shared/_codegen-tip.mdx new file mode 100644 index 0000000..1e3a94d --- /dev/null +++ b/docs/docs/shared/_codegen-tip.mdx @@ -0,0 +1,9 @@ +import Link from '@docusaurus/Link'; + +:::tip +While **Apollo Orbit** can work fully without **codegen**, it is highly recommended to use codegen in order to improve developer experience and enable compile-time safety across your application, cache & state logic. +::: + +:::info +**Apollo Orbit** is compatible with any **codegen** tool that supports TypedDocumentNode. +::: diff --git a/docs/docs/shared/_event-command.mdx b/docs/docs/shared/_event-command.mdx new file mode 100644 index 0000000..25eaa05 --- /dev/null +++ b/docs/docs/shared/_event-command.mdx @@ -0,0 +1,5 @@ +:::tip +When unsure whether to use a **command** or an **event** for a certain action, it is advisable to choose **event**. +Events simply relay what has already happened, without making assumptions about how the various states should react. +In contrast, a command could potentially obscure information about what has happened and dictate what states should do. +::: diff --git a/docs/docs/shared/_state-features.mdx b/docs/docs/shared/_state-features.mdx new file mode 100644 index 0000000..5c7ce5d --- /dev/null +++ b/docs/docs/shared/_state-features.mdx @@ -0,0 +1,6 @@ +* **Comprehensive state management**: **Apollo Orbit** combines the strengths of **Apollo Client** and traditional state management libraries, providing a unified solution for managing both local and remote data. +* **Decoupling**: Separate state management code from component code using modular `state` definitions and `action` handlers. +* **Modular:** Each `state` definition manage its own slice of the cache. +* **Separation of concerns (SoC):** Different `state` slices can handle the same `Mutation` or `Action` independently. +* **Event-driven architecture:** **Apollo Orbit** actions enable event driven application design. +* **Testability:** `state` logic can be tested in isolation, enhancing testability. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index e960999..3312eef 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -9,8 +9,8 @@ import { themes as prismThemes } from 'prism-react-renderer'; /** @type {import('@docusaurus/types').Config} */ const config = { title: 'Apollo Orbit', - url: 'https://apollo-orbit.vypex.net', - baseUrl: '/', + url: 'https://wassim-k.github.io', + baseUrl: '/apollo-orbit/', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.svg',