aposto
v0.0.3
Published
A local state management for Apollo GraphQL
Downloads
34
Maintainers
Readme
aposto
A local state management for Apollo GraphQL
Installation
Using npm
npm i aposto --save
Using yarn
yarn add aposto
Counter App without GraphQL server
import React from "react";
import { render } from "react-dom";
import { createStore, Provider, useQuery, useAction } from "aposto";
const COUNT_STATE = "count";
const INCREASE_ACTION = "increase";
const INCREASE_ASYNC_ACTION = "increase-async";
// this saga will be triggered in initializing phase of the store
function* InitSaga({ when }) {
// wait for INCREASE_ACTION and then call OnIncreaseSaga
yield when(INCREASE_ACTION, OnIncreaseSaga);
// wait for INCREASE_ACTION and then call OnIncreaseSaga with specified payload
yield when(INCREASE_ASYNC_ACTION, OnIncreaseSaga, {
payload: { async: true },
});
}
function* OnIncreaseSaga({ merge, delay }, { async }) {
if (async) {
// delay in 1s then do next action
yield delay(1000);
}
// merge specified piece of state to the whole state
yield merge({
// can pass state value or state reducer which retrieves previous state value as first param and return next state
[COUNT_STATE]: (prev) => prev + 1,
});
}
const store = createStore({
// set initial state for the store
state: { [COUNT_STATE]: 0 },
init: InitSaga,
});
const App = () => {
// select COUNT_STATE from the store state
const count = useQuery(COUNT_STATE);
// retrieve action disspatchers
const [increase, increaseAsync] = useAction(
INCREASE_ACTION,
INCREASE_ASYNC_ACTION
);
return (
<>
<h1>{count}</h1>
<button onClick={increase}>Increase</button>
<button onClick={increaseAsync}>Increase Async</button>
</>
);
};
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Todo App with GraphQL server
import { render } from "react-dom";
import { createStore, Provider, gql, useQuery, useAction } from "aposto";
import { Suspense, useEffect, useRef, useState } from "react";
const store = createStore({
uri: "https://6dsu8.sse.codesandbox.io/",
});
const FETCH_ALL_TODO_PROPS = `
id
title
description
createdOn
updatedOn
completed
`;
const GET_TODOS = gql`
query GetTodos {
todos { ${FETCH_ALL_TODO_PROPS} }
}
`;
const UPDATE_TODO = gql`
mutation UpdateTodo($id: ID!, $title: String, $description: String) {
update(id: $id, title: $title, description: $description) {
${FETCH_ALL_TODO_PROPS}
}
}
`;
const TOGGLE_TODO = gql`
mutation ToggleTodo($id: ID!) {
toggle(id: $id) { ${FETCH_ALL_TODO_PROPS} }
}
`;
const ADD_TODO = gql`
mutation AddTodo {
add { ${FETCH_ALL_TODO_PROPS} }
}
`;
const REMOVE_TODO = gql`
mutation RemoveTodo($id: ID!) {
remove(id: $id) { ${FETCH_ALL_TODO_PROPS} }
}
`;
const App = () => {
const titleInputRef = useRef();
const pageSize = 10;
const [page, setPage] = useState(0);
const descriptionInputRef = useRef();
const [editingTodo, setEditingTodo] = useState(null);
// queries
const { todos, $refetch } = useQuery(GET_TODOS);
// mutations
const [add, { $loading: adding }] = useAction(ADD_TODO);
const [update, { $loading: updating }] = useAction(UPDATE_TODO);
const [toggle, { $loading: toggling }] = useAction(TOGGLE_TODO);
const [remove, { $loading: removing }] = useAction(REMOVE_TODO);
const loading = adding || updating || removing || toggling;
const handleEdit = (id) => {
setEditingTodo(id);
};
const handleRemove = (id) => {
// remove the todo object from the cache after mutating
remove({ id }, { removeFromCache: () => ({ Todo: id }) });
};
const handleAdd = () => {
add({}, { update: () => $refetch() });
};
const handleSave = () => {
setEditingTodo(null);
update({
id: editingTodo,
title: titleInputRef.current.value,
description: descriptionInputRef.current.value,
});
};
useEffect(() => {
// adjust current page if it is greater than total pages
if (page * pageSize > todos.length) {
// move to last page
setPage(Math.ceil(todos.length / pageSize));
}
}, [page, pageSize, todos.length]);
return (
<>
<div>
<button disabled={loading} onClick={handleAdd}>
Add
</button>{" "}
({todos.length} todos){" "}
{new Array(Math.ceil(todos.length / pageSize))
.fill(null)
.map((_, index) => (
<button
key={index}
style={{ fontWeight: index === page ? "bold" : "normal" }}
onClick={() => setPage(index)}
>
{index + 1}
</button>
))}{" "}
{loading && <strong style={{ color: "red" }}>Updating... </strong>}
</div>
<div>
{todos.slice(page * pageSize, (page + 1) * pageSize).map((todo) => {
const isEditing = editingTodo === todo.id;
return (
<div key={todo.id} style={{ opacity: todo.completed ? 0.5 : 1 }}>
<hr />
<div>
{isEditing ? (
<>
<button onClick={() => handleSave()}>Save</button>
<button onClick={() => handleEdit(null)}>Cancel</button>
</>
) : (
<>
<button
disabled={loading}
onClick={() => handleEdit(todo.id)}
>
Edit
</button>
<button
disabled={loading}
onClick={() => toggle({ id: todo.id })}
>
Toggle
</button>
<button
disabled={loading}
onClick={() => handleRemove(todo.id)}
>
Remove
</button>
</>
)}{" "}
{isEditing ? (
<input ref={titleInputRef} defaultValue={todo.title} />
) : (
<strong>
{todo.title} ({todo.updatedOn})
</strong>
)}
</div>
<p>
{isEditing ? (
<textarea
ref={descriptionInputRef}
defaultValue={todo.description}
rows={5}
style={{ width: "100%" }}
/>
) : (
<span>{todo.description}</span>
)}
</p>
</div>
);
})}
</div>
</>
);
};
render(
<Provider store={store}>
<Suspense fallback="Loading...">
<App />
</Suspense>
</Provider>,
document.getElementById("root")
);