Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jimmyn/apollo-client

Apollo React Hooks with automatic cache updates
https://github.com/jimmyn/apollo-client

apollo apollo-react apollo-react-hooks react react-hooks

Last synced: about 4 hours ago
JSON representation

Apollo React Hooks with automatic cache updates

Awesome Lists containing this project

README

        

# Apollo Client

A drop-in replacement
for [@apollo/client](https://www.apollographql.com/docs/react/get-started/) with automatic cache
updates. It will update apollo cache based on a mutation or subscription result.

## Install

```
npm i @jimmyn/apollo-client @apollo/client --save
```

or

```
yarn add @jimmyn/apollo-client @apollo/client
```

## Setup

```typescript jsx
import React from 'react';
import {render} from 'react-dom';
import {ApolloClient, InMemoryCache} from '@jimmyn/apollo-client';

const client = new ApolloClient({
uri: 'localhost:8080',
cache: new InMemoryCache()
});

const App = () => (


My first Apollo app 🚀




);

render(, document.getElementById('root'));
```

## Mutations

This package
extends [useMutation](https://www.apollographql.com/docs/react/api/react/hooks/#options-2) options
allowing to update cached queries in one line of code instead of writing complex `update` functions.

For example this code

```typescript jsx
import React from 'react';
import {useMutation, useQuery} from '@jimmyn/apollo-client';
import {createTodoMutation, todosQuery} from './api/operations';
import {TodosList} from './TodosList';

export const Todos = () => {
const {data} = useQuery(todosQuery);
const todos = data?.todos || [];

const [createTodo] = useMutation(createTodoMutation, {
updateQuery: todosQuery // <== pass a gql query you want to update
});

const handleCreateTodo = () => {
return createTodo({
variables: {
task: 'New todo',
createdAt: new Date().toISOString()
}
});
};

return (


Create todo


);
};
```

is equivalent to

```typescript jsx
import React from 'react';
import {useMutation, useQuery} from '@apollo/react-hooks';
import {createTodoMutation, todosQuery} from './api/operations';
import {TodosList} from './TodosList';

export const Todos = () => {
const {data} = useQuery(todosQuery);
const todos = data?.todos || [];

const [createTodo] = useMutation(createTodoMutation);

const handleCreateTodo = () => {
return createTodo({
variables: {
task: 'New todo',
createdAt: new Date().toISOString()
},
update: (proxy, {data}) => {
const newTodo = data.createTodo;
try {
const cache = proxy.readQuery({query: todosQuery});
proxy.writeQuery({
query: todosQuery,
data: {
todos: [...cache.todos, newTodo]
}
});
} catch (error) {
console.log(error);
}
}
});
};

return (


Create todo


);
};
```

And this code

```typescript jsx
import React from 'react';
import {useMutation} from '@jimmyn/apollo-client';
import {Todo} from './api/generated';
import {deleteTodoMutation, todosQuery, updateTodoMutation} from './api/operations';

type Props = {
todo: Todo;
};

export const Todo: React.FC = ({todo}) => {
const [deleteTodo] = useMutation(deleteTodoMutation, {
updateQuery: todosQuery,

// to delete an item we need to provide it's id
// if our api simply returns true when item is deleted
// we need to return an id explicitly
mapResultToUpdate: data => todo
});
const [updateTodo] = useMutation(updateTodoMutation);

const handleDeleteTodo = () => {
return deleteTodo({
variables: {id: todo.id}
});
};

const handleUpdateTodo = () => {
return updateTodo({
variables: {id: todo.id, done: !todo.done}
});
};

return (



  • {todo.task}
    delete

  • );
    };
    ```

    is equivalent to

    ```typescript jsx
    import React from 'react';
    import {useMutation} from '@apollo/react-hooks';
    import {Todo} from './api/generated';
    import {deleteTodoMutation, todosQuery, updateTodoMutation} from './api/operations';

    type Props = {
    todo: Todo;
    };

    export const Todo: React.FC = ({todo}) => {
    const [deleteTodo] = useMutation(deleteTodoMutation);
    const [updateTodo] = useMutation(updateTodoMutation);

    const handleDeleteTodo = () => {
    return deleteTodo({
    variables: {id: todo.id},
    update: proxy => {
    try {
    const cache = proxy.readQuery({query: todosQuery});
    proxy.writeQuery({
    query: todosQuery,
    data: {
    todos: cache.todos.filter(item => item.id !== todo.id)
    }
    });
    } catch (error) {
    console.log(error);
    }
    }
    });
    };

    const handleUpdateTodo = () => {
    // apollo client is clever enough to update an item in cache
    // although if you want to update an item with different type you'll have to write
    // a manual update function
    return updateTodo({
    variables: {id: todo.id, done: !todo.done}
    });
    };

    return (



  • {todo.task}
    delete

  • );
    };
    ```

    ## `useMutation` offline options

    | Option | Description | Default |
    | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- |
    | `updateQuery` | A graphql query (wrapped in `gql` tag) that should be updated. You can pass query directly or specify it with variables `{query: todosQuery, variables: {limit: 10}}` |
    | `updatePath` | Overrides a path inside the query where the item should be updated. For example if your query has structure like `{items: [...], total: 10}`, you can specify `updatePath: ['items']`. In most cases it is automatically detected. Pass `[]` to update data in the root query object |
    | `idField` | Unique field that is used to find the item in cache. It should be present in the mutation response | `id` |
    | `operationType` | Indicates what type of the operation should be performed e.g. add/remove/update item. By default operation type is automatically detected from mutation name e.g. `createTodo` will result in `OperationTypes.ADD`. | `OperationTypes.AUTO` |
    | `mapResultToUpdate` | A function that receives mutation result and returns an updated item. Function result should contain at least an id field |

    [Other `useMutation` hook options](https://www.apollographql.com/docs/react/api/react/hooks/#options-2)

    Offline options can be passed to the `useMutation` hook or to the mutation function directly.

    ```typescript jsx
    const [deleteTodo] = useMutation(deleteTodoMutation, {
    updateQuery: todosQuery,
    mapResultToUpdate: data => todo
    });

    const handleDeleteTodo = () => {
    return deleteTodo({
    variables: {id: todo.id}
    });
    };
    ```

    is the same as

    ```typescript jsx
    const [deleteTodo] = useMutation(deleteTodoMutation);

    const handleDeleteTodo = () => {
    return deleteTodo({
    variables: {id: todo.id},
    updateQuery: todosQuery,
    mapResultToUpdate: data => todo
    });
    };
    ```

    ## Subscriptions

    `useSubscription` accepts the same offline options as `useMutation`

    ```typescript jsx
    useSubscription(onTodoUpdate, {updateQuery: todosQuery});
    ```

    [Other `useSubscription` hook options](https://www.apollographql.com/docs/react/api/react/hooks/#options-3)

    ## Customize default configurations

    Default configurations can be customized by calling `setOfflineConfig`

    ```typescript jsx
    import {setOfflineConfig} from '@jimmyn/apollo-client';

    setOfflineConfig({
    getIdFieldFromObject(item: any) {
    switch (item.__typename) {
    case 'Todo':
    return 'id';
    case 'User':
    return 'user_id';
    }
    }
    });
    ```

    ## Configuration options

    | Option | Description | Default |
    | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
    | `idField` | Unique field that is used to find the item in cache. It should be present in the mutation response | `id` |
    | `getIdFieldFromObject` | A function that receives updated item and returns an id field name. If defined it will tke precedence over `idField` |
    | `prefixesForRemove` | A list of mutation name prefixes that will result in remove operation | [prefixesForRemove](src/const.ts#L8) |
    | `prefixesForUpdate` | A list of mutation name prefixes that will result in update operation | [prefixesForUpdate](src/const.ts#L19) |
    | `prefixesForAdd` | A list of mutation name prefixes that will result in add operation | [prefixesForAdd](src/const.ts#L32) |

    ## Update Apollo cache directly

    This package also exposes `updateApolloCache` function directly, that can be used to build custom
    implementations

    Example

    ```typescript
    import {updateApolloCache} from '@jimmyn/apollo-client';

    const newTodo = {
    __typename: 'Todo',
    id: 1,
    task: 'New todo',
    done: false,
    createdAt: new Date().toISOString()
    };

    updateApolloCache({
    client,
    data: {createTodo: newTodo},
    updateQuery: todosQuery
    });
    ```

    Function signature

    ```typescript
    type OfflineOptions = {
    updateQuery?: QueryWithVariables | DocumentNode;
    idField?: string;
    operationType?: OperationTypes;
    mapResultToUpdate?(data: NonNullable): Item;
    };

    type UpdateCacheOptions = OfflineOptions & {
    client: ApolloClient | DataProxy;
    data: TData;
    };

    const updateApolloCache: ({
    client,
    data,
    idField,
    updateQuery,
    operationType,
    mapResultToUpdate
    }: UpdateCacheOptions) => void;
    ```

    ## Credits

    This package is based
    on [Amplify Offline Helpers](https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/OFFLINE_HELPERS.md)