Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
apollo-offline-hooks
Advanced tools
A drop-in replacement for @apollo/react-hooks with automatic cache updates. It will update apollo cache based on a mutation or subscription result.
npm i apollo-offline-hooks @apollo/react-hooks --save
or
yarn add apollo-offline-hooks @apollo/react-hooks
import React from 'react';
import {render} from 'react-dom';
import ApolloClient from 'apollo-boost';
import {ApolloProvider} from 'apollo-offline-hooks';
const client = new ApolloClient({
uri: 'localhost:8080',
});
const App = () => (
<ApolloProvider client={client}>
<div>
<h2>My first Apollo app 🚀</h2>
</div>
</ApolloProvider>
);
render(<App />, document.getElementById('root'));
This package extends useMutation options allowing to update cached queries in one line of code instead of writing complex update
functions.
For example this code
import React from 'react';
import {useMutation, useQuery} from 'apollo-offline-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, {
updateQuery: todosQuery // <== pass a gql query you want to update
});
const handleCreateTodo = () => {
return createTodo({
variables: {
task: 'New todo',
createdAt: new Date().toISOString()
}
});
};
return (
<div>
<button onClick={handleCreateTodo}>Create todo</button>
<TodosList todos={todos} />
</div>
);
};
is equivalent to
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 (
<div>
<button onClick={handleCreateTodo}>Create todo</button>
<TodosList todos={todos} />
</div>
);
};
And this code
import React from 'react';
import {useMutation} from 'apollo-offline-hooks';
import {Todo} from './api/generated';
import {deleteTodoMutation, todosQuery, updateTodoMutation} from './api/operations';
type Props = {
todo: Todo;
};
export const Todo: React.FC<Props> = ({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 (
<li>
<input type="checkbox" checked={todo.done} onChange={handleUpdateTodo} />
{todo.task}
<button onClick={handleDeleteTodo}>delete</button>
</li>
);
};
is equivalent to
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<Props> = ({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 (
<li>
<input type="checkbox" checked={todo.done} onClick={handleUpdateTodo} />
{todo.task}
<button onClick={handleDeleteTodo}>delete</button>
</li>
);
};
useMutation
offline optionsOption | 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}} | |
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
Offline options can be passed to the useMutation
hook or to the mutation function directly.
const [deleteTodo] = useMutation(deleteTodoMutation, {
updateQuery: todosQuery,
mapResultToUpdate: data => todo
});
const handleDeleteTodo = () => {
return deleteTodo({
variables: {id: todo.id}
});
};
is the same as
const [deleteTodo] = useMutation(deleteTodoMutation);
const handleDeleteTodo = () => {
return deleteTodo({
variables: {id: todo.id},
updateQuery: todosQuery,
mapResultToUpdate: data => todo
});
};
useSubscription
accepts the same offline options as useMutation
useSubscription(onTodoUpdate, {updateQuery: todosQuery});
Other useSubscription
hook options
Default configurations can be customized by calling setOfflineConfig
import {setOfflineConfig} from 'apollo-offline-hooks';
setOfflineConfig({
getIdFieldFromObject(item: any) {
switch (item.__typename) {
case 'Todo':
return 'id';
case 'User':
return 'user_id'
}
}
});
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 |
prefixesForUpdate | A list of mutation name prefixes that will result in update operation | prefixesForUpdate |
prefixesForAdd | A list of mutation name prefixes that will result in add operation | prefixesForAdd |
This package also exposes updateApolloCache
function directly, that can be used to build custom implementations
Example
import {updateApolloCache} from 'apollo-offline-hooks';
const newTodo = {
__typename: 'Todo',
id: 1,
task: 'New todo',
done: false,
createdAt: new Date().toISOString()
};
updateApolloCache({
client,
data: {createTodo: newTodo},
updateQuery: todosQuery
});
Function signature
type OfflineOptions<TData> = {
updateQuery?: QueryWithVariables | DocumentNode;
idField?: string;
operationType?: OperationTypes;
mapResultToUpdate?(data: NonNullable<TData>): Item;
};
type UpdateCacheOptions<TData = any> = OfflineOptions<TData> & {
client: ApolloClient<any> | DataProxy;
data: TData;
};
const updateApolloCache: <TData = any>({
client,
data,
idField,
updateQuery,
operationType,
mapResultToUpdate
}: UpdateCacheOptions<TData>) => void;
This package is based on Amplify Offline Helpers
FAQs
Apollo React Hooks with automatic cache updates
We found that apollo-offline-hooks demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.