# react-hoc-query **Repository Path**: sethyuan/react-hoc-query ## Basic Information - **Project Name**: react-hoc-query - **Description**: HOC to help you query restful data - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-26 - **Last Updated**: 2021-09-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # react-hoc-query **NOTE: This project is OBSOLETE, most of its functionalities are better implemented in [reactutils](https://gitee.com/sethyuan/reactutils).** HOC to help you query restful data. It provides the following convenient features: - You can query whatever you like, but the primary use case is to query restful API services - Query results are stored in Redux' store - so the same query elsewhere will simply fetch from cache (the store) and, - any updates to the data in store will cause dependent components to refresh and, - you can use the redux devtool to debug data - A manual `refetch` is provided via props in case you need to ignore possible cache - Status changes of the query are provided via props (`loading`, `error`, `data`) - You can optionally setup a global query error handler to centralize error processing (`query.onError(e)`). E.g, display a dialog for network errors. - You can optionally setup `query.openLoading()`, `query.closeLoading()` and `query.loadingWait` to implement displaying a loading indicator UI should the query result delays for too long (longer than `query.loadingWait`) - You can setup an interval to poll newest data periodically, and control when to start/end polling via props (`startPolling`, `endPolling`) - Multiple queries and query dependencies are supported - You can create groups to control the overall cache size. Each group has a limit on how much query results it can store, and will discard excessive items using the LRU algorithm. ## Installation ```bash yarn add react-hoc-query ``` ## Basic Usage ### Setup the reducer ```js import query from "react-hoc-query/lib/reducers" const store = createStore( combineReducers({ // ..., // It has to be named `query`. query, }), // ... ) ``` ### Simple usage ```js import React from "react" import query from "react-hoc-query" @query({ key: "loginInfo", op: async props => { // Call whatever API you need here and return the result. // Note that this is an async function. // Anything you return, will be available under the `data` prop. return await api.isLoggedIn() }, }) class App from React.Component { render() { const { loading, error, data } = this.props.query if (loading) return if (!data || error) return if (data.isLoggedIn) { return } else { return } } } ``` With the above example, all components with a query key of **loginInfo** will share the same data. If a cache is available in store, that cache will be used. ### Use refetch to guarantee data freshness ```js import React from "react" import query from "react-hoc-query" import mutate from "react-hoc-mutate" @query({ key: "loginInfo", op: api.isLoggedIn, }) @mutate({ name: "login", op: api.login, }) class Login from React.Component { render() { return ( ) } login = async () => { try { await this.props.login() await this.props.query.refetch() } catch (e) { // handle it } } } ``` With the above example, `loginInfo` is refreshed after successful login, and every component that depends on `loginInfo` will get refreshed with the new login data. ### Use a custom name for prop ```js import React from "react" import query from "react-hoc-query" @query({ key: "loginInfo", name: "loginInfo" // defaults to "query", op: api.isLoggedIn, }) class App from React.Component { render() { const { loading, error, data } = this.props.loginInfo if (loading) return if (!data || error) return if (data.isLoggedIn) { return } else { return } } } ``` ### Polling interval ```js import React from "react" import query from "react-hoc-query" @query({ key: "serverTime", op: api.serverTime, pollInterval: 60, // 60sec }) class App from React.Component { render() { const { data } = this.props.query if (data) return } componentDidMount() { this.props.query.startPolling() } componentWillUnmount() { this.props.query.endPolling() } } ``` ### Query dependencies In the below example, 3 queries are used by App, but only `loginInfo` and `userProfile` are necessary for start displaying content to the users. `loginInfo` and `chatChannels` will be fetched concurrently while `userProfile` is only fetched after `loginInfo` is available and that the user is logged in. Each time one of the dependencies changes, the query gets refetched upon dependency availability given `shouldFetch` returns true. Also, note that the order in which you apply the queries is important, queries with `dependOn` should appear below their dependencies. `dependOn` is an array of both string and objects of shape `{ group, key }`. When an item is a string, it indicates a dependency for the specified key of the same group as the query itself; otherwise, a `{ group, key }` is needed to depend on a key of a different group. Please look further down for the introduction of groups. ```js import React from "react" import query from "react-hoc-query" import { propReduce } from "reactutils" @query({ key: "loginInfo", name: "loginInfo", op: api.loginInfo, }) @query({ key: "chatChannels", name: "chatChannels", op: api.chatChannels, }) @query({ key: props => props.loginInfo.data ? `userProfile:${props.loginInfo.data.uid}` : "", name: "userProfile", op: props => api.userProfile(props.loginInfo.data.userId), dependOn: ["loginInfo"], shouldFetch: props => props.loginInfo.data.isLoggedIn, }) @propReduce( { loading: (ret, item) => ret || item.loading }, ["loginInfo", "userProfile"], false, ) export default class App extends React.Component { render() { const { loading, loginInfo, chatChannels, userProfile } = this.props if (loading) { return
Loading
} else if (userProfile.data) { return
Hello {userProfile.data.name}
} else { return null } } } ``` ### Global onError handler on your app entry: ```js import query from "react-hoc-query" query.onError = err => { if (err.type === "your_error_type") { // dispatch action to open error dialog } } ``` ### Global loading indicator popup handler on your app entry: ```js import query from "react-hoc-query" // If a query finishes before this times out then `openLoading` // will not get called. query.loadingWait = 0.1 // defaults to 0.1sec query.openLoading = () => { // dispatch action to popup loading indicator } query.closeLoading = () => { // dispatch action to close loading indicator } ``` ### Use group to limit cache size on your app entry: ```js import { groups } from "react-hoc-query" // This defines the cache size (number of query results) for the group // `movieItems`. Default size for new groups is 10. Everything else goes // into the `DEFAULT` group, which is practically unlimited. groups.movieItems = 20 ``` usage: ```js import React from "react" import query from "react-hoc-query" @query({ group: "movieItems", // Note key could be a function taking `props` too. key: props => `movie-item-${props.id}` op: async props => { return await api.movieItem(props.id) }, }) class MovieDetail from React.Component { render() { const { loading, error, data } = this.props.query // ... } } ``` With the above setup, no matter how many movie items you visit, only the most recent 20 will be cached. This prevents possible overuse of memory. ## Dev setup 1. `yarn` ### npm run tasks - **yarn build**: Transpile source code