{"id":17911123,"url":"https://github.com/dprokop/querier","last_synced_at":"2025-03-23T22:33:29.806Z","repository":{"id":65478863,"uuid":"122116267","full_name":"dprokop/querier","owner":"dprokop","description":"Simple declarative data layer for React apps","archived":false,"fork":false,"pushed_at":"2018-08-17T08:05:50.000Z","size":739,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-18T12:51:18.152Z","etag":null,"topics":["data","declarative","react","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dprokop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"code-of-conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-19T20:24:41.000Z","updated_at":"2024-08-25T00:50:58.000Z","dependencies_parsed_at":"2023-01-25T15:16:18.148Z","dependency_job_id":null,"html_url":"https://github.com/dprokop/querier","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dprokop%2Fquerier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dprokop%2Fquerier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dprokop%2Fquerier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dprokop%2Fquerier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dprokop","download_url":"https://codeload.github.com/dprokop/querier/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245179537,"owners_count":20573484,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data","declarative","react","typescript"],"created_at":"2024-10-28T19:36:52.347Z","updated_at":"2025-03-23T22:33:29.409Z","avatar_url":"https://github.com/dprokop.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Querier\nSimple declarative data layer for React applications\n\n[![Build Status](https://travis-ci.org/dprokop/querier.svg?branch=master)](https://travis-ci.org/dprokop/querier)\n\n# Table of Content\n  * [What is Querier?](#what-is-querier)\n  * [Querier gist](#querier-gist)\n  * [Getting data in and out from component](#queries)\n    * [Input queries](#input-queries)\n    * [Action queries](#action-queries)\n    * [Data dependencies definition](#data-dependencies-definition)\n  * [Handling query statuses](#handling-query-statuses)\n  * [Caching](#caching)\n  * [Server-side rendering](#server-side-rendering)\n  * [API](#server-side-rendering)\n  * [Known limitations](#known-limitations)\n\n## What is Querier?\nQuerier is a tool that helps you get remote and async data into React app effortlessly.\n\nWith simple, declarative API, you don't have to worry about handling async state or errors. All you have to do is decorate component that requires async data using [`withData`](#withdata) higher-order component. Results, states and possible errors will be injected into the component as props.\n\nQuerier is implemented and tested in TypeScript.\n\n## What is a declarative data layer?\nSimply, it is a way to supply data to your components. You declare component's data requirements, and the whole logic for fetching it, supplying status and results is handled by Querier itself. To achieve that, you implement `queries`. Queries are simply **async functions**. They can either communicate with remote source or resolve async data in whatever way you imagine.\n\n## Querier Gist\n\n**First**, define your queries:\n```js\n// queries.js\n\nexport const getRepository = async ({id}) =\u003e {\n  const results = await octokit.repos.getById({\n    id\n  });\n  return results.data;\n};\n\nexport const starRepository = async (owner, repo) =\u003e {\n  const results = await octokit.activity.starRepo({\n    owner,\n    repo,\n  });\n  return results.data;\n};\n```\n\n**Second**, make your component use the queries:\n```jsx\nimport { getRepository, starRepository } from 'queries.js';\nimport {  withData, QuerierState } from 'querier';\n\n// Define component that will require async data\nclass RepositoryDetails extends React.Component {\n  handleRepositoryStar() {\n    const { repository } = this.props.results;\n\n    if(repository) {\n      // action queries are available via actionQueries prop\n      this.props.actionQueries.starRepository(\n        repository.owner.login,\n        repository.name\n      );\n    }\n  }\n\n  render () {\n    const { name } = this.props.results.respository;\n    const { states: { repository: { state } } } = this.props;\n\n    return state === QuerierState.Success ? (\n      \u003cdiv\u003e\n        \u003ch1\u003eRespository name: {name}\u003c/h1\u003e\n        \u003cbutton onClick={this.handleRepositoryStar}\u003eStar repo\u003c/button\u003e\n      \u003c/div\u003e\n    ) : null;\n  }\n}\n\n// Define RepositoryDetails data requirements (vide queries)\nconst repositoryDetailsQueries = {\n  inputQueries: {\n    repository: {\n      query: getRepository\n    }\n  },\n  actionQueries: {\n    starRepository: {\n      query: starRepository\n    }\n  }\n};\n\n// Decorate your component using withData HOC\nexport default withData(repositoryDetailsQueries)(RepositoryDetails);\n```\n\nFor examples take a look into `examples` directory.\n## Queries\n---\nQueries gives you ability to get data *in* and *out* of React component. They are the most important part of Querier.\n\nTo supply data into component you use [`inputQueries`](#input-queries). To perform async operations (e.g. submittng a form) you use [`actionQueries`](#action-queries).\n\n### Input queries\nHere's an example of a simple input query:\n\n```ts\n// queries.ts\n\nexport const getRepository = async ({ id }: { id: number }): Promise\u003cRepositoryType\u003e =\u003e {\n    return await octokit.repos.getById({\n      id: id.toString()\n    }).data\n  };\n```\n\nTo make `getRepository` query supply data to your component, you need to decorate your component using `withData` HOC:\n```ts\n// YourComponent.tsx\n\n// First, define data dependencies definition\nconst repositoryDetailsQueries = {\n  inputQueries: {\n    /**\n     * You declare that your component will use getRepository query.\n     * Query result will be available in props as this.props.results.repository\n     * Query status will be available in props as this.props.states.repository\n     **/\n    repository: {\n      query: getRepository\n    }\n  }\n}\n\n// Second, use withData HOC\nexport default withData(repositoryDetailsQueries)(YourComponent);\n```\n\n#### Input queries executors - manually invoking input queries\n\nInput queries are automatically invoked when component's props change. However, in some cases there is a need to re-invoke input query for the same set of props. To do so, each query is passed to the component in `inputQueries` property, that contain each query's executor. In the case of the example above, the executor  for `repository` input query is accessible by:\n\n```js\nthis.props.inputQueries.repository.fire()\n```\n\n#### Input queries key notes\n- Input queries are fired when component mounts\n- **Input queries always receive component's full set of props** as argument\n- Input queries are being reinvoked when component's props change\n- Input query results are **cached** by default. See [Caching](#caching) for more details.\n\n### Action queries\nAction queries are similiar to input queries. The main difference is that thay are not being invoked automatically.\n\nThe purpose of action query is to perform async operation as a result of user interaction, e.g. submitting a form.\n\nHere's an example of action query usage:\n```tsx\n// queries.ts\n\nexport const findUser = async (userName: string): Promise\u003cSearchOrgResults\u003e =\u003e {\n    const results = await octokit.search.users({\n      q: userName,\n    });\n\n    return results.data;\n  };\n```\n```ts\n// SearchComponent.tsx\n\ntype SearchComponentActionQueries = {\n  findUser: SearchResults;\n}\n\nclass SearchComponent extends React.Component\u003cWithDataProps\u003c{}, {} , SearchComponentActionQueries\u003e\u003e {\n  handleSubmit(userName: string) {\n    this.props.actionQueries.findUser(userName);\n  }\n\n  render () {\n    const {\n      results: { findUser },\n      states\n    } = this.props\n\n    // Use findUser and states.findUser to render ...\n  }\n}\n\nconst searchComponentQueries = {\n  actionQueries: {\n    findUser: {\n      query: searchQuery\n    }\n  }\n}\nexport default withData(searchComponentQueries)(SearchComponent);\n```\n\n#### Action queries key notes\n- Results of the action query **will be passed back to the component**. Thanks to that you can declaratively perform e.g. navigation using React Router.\n- Action queries results are **cached** by default. See [Caching]() for more details.\n\n### Data dependencies definition\nIn order to perform queries you need to define them and pass to `withData` HOC. Data dependency definition is a simple JSON object describing queries your component will use:\n\n```ts\nimport { getRepository } from 'app/queries';\n\ntype RepositoryDetailsOwnProps = {  id: number }\ntype RepositorDetailsInputQueries = {  repository: RepositoryType }\ntype RepositorDetailsActionQueries = {  starRepository: RepositoryType }\ntype RepositoryDetailsProps = WithDataProps\u003c\n  RepositoryDetailsOwnProps,\n  RepositoryDetailsInputQueries,\n  RepositorDetailsActionQueries\n\u003e;\n\n\nconst repositoryDetailsQueries: DataDependencyDefinition\u003c\n  RepositoryDetailsProps,\n  RepositoryDetailsInputQueries,\n  RepositoryDetailsActionQueries\n\u003e = {\n  inputQueries: {\n    repository: {\n      query: getRepository\n    }\n  },\n  actionQueries: {\n    starRepository: starRepository,\n    hot: true\n  }\n};\n```\n\nData dependency definition is described by type `DataDependencyDefinition\u003cTProps, TInputQueries, TActionQueries\u003e`:\n\n```ts\ntype DataDependencyDefinition\u003cTProps, TInputQueries, TActionQueries\u003e = {\n  inputQueries?: InputQueriesDefinition\u003cTProps, TInputQueries\u003e;\n  actionQueries?: ActionQueriesDefinition\u003cTActionQueries\u003e;\n};\n```\n\nInput and action queries are defined separately, and each query definition accepts following options:\n\n```ts\n{\n  query: InputQuery\u003cTProps, TQueryResult\u003e | ActionQuery\u003cTQueryResult\u003e;\n  hot?: boolean;\n  lazy?: boolean; // input queries only\n  resultActions?: ResultActions\u003cTQueryResult\u003e;\n}\n```\n\n#### Query defintion options\n- `hot?: boolean` - when set to `true` makes the query not cacheable. See [Caching](#caching) for more information\n- `lazy?: boolean` - *Input queries only*. When set to `true`, input query will not be fired on mount\n- `resultActions?: Array\u003cActionFunction1\u003cTQueryResult, Action\u003cTQueryResult\u003e\u003e\u003e` - array of Redux action creators. When defined, these actions will be performed when query succeedes. Use it e.g. when you want to store your query result in Redux store.\n\n\n## Handling query statuses\n  When decorating your component using `withData` HOC all your queries statuses are passed to the component in `states` prop. Each query state is represented by `QueryStateType`:\n```ts\nenum QuerierState {\n  Pending = 0,\n  Active,\n  Success,\n  Error\n}\n\ntype QueryStateType = {\n  state: QuerierState;\n  error?: {};\n};\n```\nLet's assume you declared, that your component will use `getUsers` input query result as `users` prop. This means, that the `states` prop will look like this:\n```ts\n{\n  users: {\n    state: // QuerierState ...\n    error: // when state === QuerierState.Error\n  }\n}\n```\n\nHaving this knowledge you can start building UI abstractions over this API to display e.g. feedback indicators to your users. Or, in case of action queries, you can for example redirect user to a new page when action query suceedes.\n\nAlso, there is a [`combineStates(states: StatesType): QueryStateType`](./src/utils/combineStates.ts) utility that will calculate derived status of multiple query states.\n\n## Caching\nBy default, all **input** queries executed by Querier are cached. However, there are cases, when you want your queries to be executed every time instead of served from cache. To do so you need to declare your query as `hot` in your data dependencies definition passed to `withData` HOC:\n\n```ts\nconst searchComponentQueries = {\n  actionQueries: {\n    findUser: {\n      query: searchQuery,\n      hot: true\n    }\n  }\n}\n```\n\nFor action queriers set `hot: false` to make them cacheable.\n\nQuerier cache is not persistent - you need to take care of this by yourself. `QuerierProvider` component accepts `querier: QuerierType` property that can be initialised using querier store that you have cached in e.g. local storage.\n\n## Server-side rendering\nFor SSR example please take a look at [example repository](https://github.com/dprokop/querier-ssr-example)\n\n## API\n\n### `Querier`\n`Querier(store?: QuerierStoreType, dispatch?: Dispatch\u003c{}\u003e)` is the core part of the tool. It's responsibility is to perform queries, cache and pass them back to components.\n\n#### Arguments\n- `store` is an object, when performed queries are stored. It keeps queries ids, states, results, and reasons(components that caused the query invocation). You can initialise Querier using store you have e.g. build during server-side rendering or store in local storage.\n- `dispatch` is Redux's dispatch function. Initialise Querier with `dispatch` if you want to perform `resultActions` on your queries. See [Query defintion options](#query-defintion-options) for more details\n\n### `QuerierProvider`\n`\u003cQuerierProvider querier\u003e`\nMakes querier available to components decorated with `withData` HOC. `QuerierProvider` should be the topmost component in your React tree.\n\n#### Props\n- `querier?: QuerierType` - pass `querier` prop if you have e.g. built your Querier store during server-side rendering.\n\n### `withData`\n`withData(dependencies)` return HOC that connects your component to Querier.\n\n#### Arguments\n- `dependencies: DataDependencyDefinition\u003cTProps, TInputQueries, TActionQueries\u003e` - please refer to [Data dependencies definition](#data-dependencies-definition) for details\n\n\n ## Known limitations\n### Action queries arguments typings\nAction queries arguments are not typed at the moment\n\n### Anonymous query function exports in TypeScript when `module: 'commonjs'` set is problematic\n\nQuery keys in Querier cache are based on query function name and params passed to the query. When using `module: 'commonjs'` Typescript turns this :\n\n`export const someQuery = () =\u003e 1`\n\ninto this:\n\n```js\nexports.someQuery = function (_ref) { ...\n```\n\n`someQuery.name` will return empty string for such export, meaning, that if your component rely on multiple input queries, or multiple queries rely on the same set or props, then all off them will resolve with data stored in cache for **first query that finished execution**. This of course leads to invalid data :(\n\nTo avoid this situation you can either set `module` to `esnext` or define your queries as non-anonymous functions or functions assigned to a `const` end exported later:\n\n```ts\n// This will work:\nexport async function someQuery() { ... }\n\n// This will work as well:\nconst someQuery = async () =\u003e ...\nexport someQuery;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdprokop%2Fquerier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdprokop%2Fquerier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdprokop%2Fquerier/lists"}