README
A state management library which fetches, normalizes, and caches data for your React application.
Features
What makes react-kho different from other data fetching libraries is that it normalizes data in the cache. Which means when some objects change, all the related queries will be updated automatically without you having to do anything.
- Transport and protocol agnostic data fetching
- Data normalization
- Request deduplication
- Interval polling
- Paginated queries
- Infinite scroll queries
- Local state
- Mutations with query revalidation
- Local mutations (e.g. for use with WebSocket or SSE)
- Cache reset and query revalidation (e.g. upon user sign in/out)
- TypeScript support
- SSR support
- React Suspense support
- DevTools (coming soon)
- ...
Usage
Inside your React project directory, run npm install react-kho
or yarn add react-kho
.
In a Nutshell
Let's say you're working on a blog application that allows users to post articles and comments.
First, register these types like so:
// store/types.ts
import { NormalizedType as Type } from "react-kho"
export const UserType = Type.register("User", { keyFields: ["username"] })
export const CommentType = Type.register("Comment", {
shape: { author: UserType },
}) // key field is "id" by default
export const ArticleType = Type.register("Article", {
keyFields: ["slug"],
shape: {
author: UserType,
comments: [CommentType],
},
})
Next, define your query object:
// store/queries.ts
import { Query } from "react-kho"
import { ArticleType } from "./types"
import { getGlobalFeed } from "../api"
export const globalFeedQuery = new Query(
"GlobalFeed",
(args: { limit: number; offset: number }) =>
getGlobalFeed(args.limit, args.offset),
{
shape: { articles: [ArticleType] },
}
)
You can then use the query from your UI:
// views/Home/index.tsx
import { useQuery } from "react-kho"
import { globalFeedQuery } from "../../store"
function GlobalFeed() {
const { loading, data, error } = useQuery(globalFeedQuery, {
arguments: { limit: 10, offset: 0 },
})
if (loading) {
return <p>Loading...</p>
} else if (error) {
return <p>{error.message}</p>
} else if (data) {
const { articlesCount, articles } = data
return articlesCount === 0 ? (
<p>No articles found.</p>
) : (
<ArticleList articles={articles} />
)
}
return null
}
// index.tsx
import { Provider, createStore } from "react-kho"
ReactDOM.render(
<Provider store={createStore()}>
<App />
</Provider>
)
Now if you need to update an article, define a mutation object:
// store/mutations/articles.ts
import { Mutation } from "react-kho"
import { ArticleType } from "../types"
import { updateArticle } from "../../api"
export const updateArticleMutation = new Mutation(
"UpdateArticle",
(args: { slug: string; input: any }) => updateArticle(args.slug, args.input),
{
resultShape: ArticleType, // updateArticle() should return Promise<Article>
}
)
And use the mutation from the UI:
// views/EditArticle/index.tsx
import { useHistory, useParams } from "react-router-dom"
import { useMutation } from "react-kho"
import { updateArticleMutation } from "../../store"
function EditArticle() {
const { slug } = useParams<{ slug: string }>()
const browserHistory = useHistory()
const [updateArticle, { loading, error, data, called }] = useMutation(
updateArticleMutation
)
useEffect(() => {
if (called && !error) {
browserHistory.push(`/articles/${slug}`)
}
}, [slug, called, error])
if (loading) {
return <p>Loading...</p>
} else if (error) {
return <p>{error.message}</p>
} else {
return (
<ArticleForm
slug={slug}
onSubmit={(input: any) => updateArticle({ arguments: { slug, input } })}
/>
)
}
}
The good thing about react-kho
is that all the related queries will be updated automatically.
Learn More
- Concepts & Guides
- Examples
- Real world demo (Conduit frontend)
- DevTools (Coming soon)
License
The MIT License.