A simple, declarative, and composable way to fetch data for React components.
Requires React 0.14 or later.
npm install --save react-refetch
This assumes that you’re using npm package manager with a module bundler like Webpack or Browserify to consume CommonJS modules.
The following ES6 functions are required:
Check the compatibility tables (Object.assign
, Promise
, fetch
, Array.prototype.find
) to make sure all browsers and platforms you need to support have these, and include polyfills as necessary.
See Introducing React Refetch on the Heroku Engineering Blog for background and a quick introduction to this project.
This project was inspired by (and forked from) React Redux. Redux/Flux is a wonderful library/pattern for applications that need to maintain complicated client-side state; however, if your application is mostly fetching and rendering read-only data from a server, it can over-complicate the architecture to fetch data in actions, reduce it into the store, only to select it back out again. The other approach of fetching data inside the component and dumping it in local state is also messy and makes components smarter and more mutable than they need to be. This module allows you to wrap a component in a connect()
decorator like react-redux, but instead of mapping state to props, this lets you map props to URLs to props. This lets you keep your components completely stateless, describe data sources in a declarative manner, and delegate the complexities of data fetching to this module. Advanced options are also supported to lazy load data, poll for new data, and post data to the server.
If you have a component called Profile
that has a userId
prop, you can wrap it in connect()
to map userId
to one or more requests and assign them to new props called userFetch
and likesFetch
:
import React, { Component } from 'react' import { connect, PromiseState } from 'react-refetch' class Profile extends Component { render() { // see below } } export default connect(props => ({ userFetch: `/users/${props.userId}`, likesFetch: `/users/${props.userId}/likes` }))(Profile)
When the component mounts, the requests will be calculated, fetched, and the result will be passed into the component as the props specified. The result is represented as a PromiseState
, which is a synchronous representation of the fetch Promise
. It will either be pending
, fulfilled
, or rejected
. This makes it simple to reason about the fetch state at the point in time the component is rendered:
render() { const { userFetch, likesFetch } = this.props if (userFetch.pending) { return <LoadingAnimation/> } else if (userFetch.rejected) { return <Error error={userFetch.reason}/> } else if (userFetch.fulfilled) { return <User user={userFetch.value}/> } // similar for `likesFetch` }
See the composing responses to see how to handle userFetch
and likesFetch
together. Although not included in this library because of application-specific defaults, see an example PromiseStateContainer
and its example usage for a way to abstract and simplify the rendering of PromiseState
s.
When new props are received, the requests are re-calculated, and if they changed, the data is refetched and passed into the component as new PromiseState
s. Using something like React Router to derive the props from the URL in the browser, the application can control state changes just by changing the URL. When the URL changes, the props change, which recalculates the requests, new data is fetched, and it is reinjected into the components:
By default, the requests are compared using their URL, headers, and body; however, if you want to use a custom value for the comparison, set the comparison
attribute on the request. This can be helpful when the request should or should not be refetched in response to a prop change that is not in the request itself. A common situation where this occurs is when two different requests should be refetched together even though one of the requests does not actually include the prop. Note, this is using the request object syntax for userStatsFetch
instead of just a plain URL string. This syntax allows for more advanced options. See the API documentation for details:
connect(props => ({ usersFetch: `/users?status=${props.status}&page=${props.page}`, userStatsFetch: { url: `/users/stats`, comparison: `${props.status}:${props.page}` } }))(UsersList)
In this example, usersFetch
is refetched every time props.status
or props.page
changes because the URL is changed. However, userStatsFetch
does not contain these props in its URL, so would not normally be refetched, but because we added comparison: ${props.status}:${props.page}
, it will be refetched along with usersFetch
. In general, you should only rely on changes to the requests themselves to control when data is refetched, but this technique can be helpful when finer-grained control is needed.
If you always want data to be refetched when any new props are received, set the force: true
option on the request. This will take precedence over any custom comparison
and the default request comparison. For example:
connect(props => ({ usersFetch: `/users?status=${props.status}&page=${props.page}`, userStatsFetch: { url: `/users/stats`, force: true } }))(UsersList)
Setting force: true
should be avoided if at all possible because it could result in extraneous data fetching and rendering of the component. Try to use the default comparison or custom comparison
option instead.
If the refreshInterval
option is provided along with a URL, the data will be refreshed that many milliseconds after the last successful response. If a request was ever rejected, it will not be refreshed or otherwise retried. In this example, likesFetch
will be refreshed every minute. Note, this is using the request object syntax for likeFetch
instead of just a plain URL string. This syntax allows for more advanced options. See the API documentation for details.
connect(props => ({ userFetch:`/users/${props.userId}`, likesFetch: { url: `/users/${props.userId}/likes`, refreshInterval: 60000 } }))(Profile)
When refreshing, the PromiseState
will be the same as the previous fulfilled
state, but with the refreshing
attribute set. That is, pending
will remain unset and the existing value
will be left intact. When the refresh completes, refreshing
will be unset and the value
will be updated with the latest data. If the refresh is rejected, the PromiseState
will move into a rejected
and not attempt to refresh again.
Instead of mapping the props directly to a URL string or request object, you can also map the props to a function that returns a URL string or request object. When the component receives props, instead of the data being fetched immediately and injected as a PromiseState
, the function is bound to the props and injected into the component as functional prop to be called later (usually in response to a user action). This can be used to either lazy load data, post data to the server, or refresh data. These are best shown with examples:
Here is a simple example of lazy loading the likesFetch
with a function:
connect(props => ({ userFetch: `/users/${props.userId}`, lazyFetchLikes: max => ({ likesFetch: `/users/${props.userId}/likes?max=${max}` }) }))(Profile)
In this example, userFetch
is fetched normally when the component receives props, but lazyFetchLikes
is a function that returns likesFetch
, so nothing is fetched immediately. Instead lazyFetchLikes
is injected into the component as a function to be called later inside the component:
this.props.lazyFetchLikes(10)
When this function is called, the request is calculated using both the bound props and any passed in arguments, and the likesFetch
result is injected into the component normally as a PromiseState
.
Functions can also be used for post data to the server in response to a user action. For example:
connect(props => ({ postLike: subject => ({ postLikeResponse: { url: `/users/${props.userId}/likes`, method: 'POST', body: JSON.stringify({ subject }) } }) }))(Profile)
The postLike
function is injected in as a prop, which can then be tied to a button:
<button onClick={() => this.props.postLike(someSubject)}>Like!</button>
When the user clicks the button, someSubject
is posted to the URL and the response is injected as a new postLikeResponse
prop as a PromiseState
to show progress and feedback to the user.
Functions can also be used to manually refresh data by overwriting an existing PromiseState
:
connect(props => { const url = `/users/${props.userId}` return { userFetch: url, refreshUser: () => ({ userFetch: { url, force: true, refreshing: true } }) } })(Profile)
The userFetch
data is first loaded normally when the component receives props, but the refreshUser
function is also injected into the component. When this.props.refreshUser()
is called, the request is calculated, and compared with the existing userFetch
request. If the request changed (or force: true
), the data is refetched and the existing userFetch
PromiseState
is overwritten. This should generally only be used for user-invoked refreshes; see above for automatically refreshing on an interval.
Note, the example above sets force: true
and refreshing: true
on the request returned by the refreshUser()
function. These attributes are optional, but commonly used with manual refreshes. force: true
avoids the default request comparison (e.g. url
, method
, headers
, body
) with the existing userFetch
request so that every time this.props.refreshUser()
is called, a fetch is performed. Because the request would not have changed from the last prop change in the example above, force: true
is required in this case for the fetch to occur when this.props.refreshUser()
is called. refreshing: true
avoids the existing PromiseState
from being cleared while fetch is in progress.
The two examples above can be combined to post data to the server and refresh an existing PromiseState
. This is a common pattern when responding to a user action to update a resource and reflect that update in the component. For example, if PATCH /users/:user_id
responds with the updated user, it can be used to overwrite the existing userFetch
when the user updates her name:
connect(props => ({ userFetch: `/users/${props.userId}`, updateUser: (firstName, lastName) => ({ userFetch: { url: `/users/${props.userId}` method: 'PATCH' body: JSON.stringify({ firstName, lastName }) } }) }))(Profile)
If a component needs data from more than one URL, the PromiseState
s can be combined with PromiseState.all()
to be pending
until all the PromiseState
s have been fulfilled. For example:
render() { const { userFetch, likesFetch } = this.props // compose multiple PromiseStates together to wait on them as a whole const allFetches = PromiseState.all([userFetch, likesFetch]) // render the different promise states if (allFetches.pending) { return <LoadingAnimation/> } else if (allFetches.rejected) { return <Error error={allFetches.reason}/> } else if (allFetches.fulfilled) { // decompose the PromiseState back into individual const [user, likes] = allFetches.value return ( <div> <User data={user}/> <Likes data={likes}/> </div> ) } }
Similarly, PromiseState.race()
can be used to return the first settled PromiseState
. Like their asynchronous Promise
counterparts, PromiseStates
can be chained with then()
and catch()
; however, the handlers are run immediately to transform the existing state. This can be helpful to handle errors or transform values as part of a composition. For example, to provide a fallback value to likesFetch
in the case of failure:
PromiseState.all([userFetch, likesFetch.catch(reason => [])])
Inside of connect()
, requests can be chained using then()
, catch()
, andThen()
and andCatch()
to trigger additional requests after a previous request is fulfilled. These are not to be confused with the similar sounding functions on PromiseState
, which are on the response side, are synchronous, and are executed for every change of the PromiseState
.
then()
is helpful for cases where multiple requests are required to get the data needed by the component and the subsequent request relies on data from the previous request. For example, if you need to make a request to /foos/${name}
to look up foo.id
and then make a second request to /bar-for-foos-by-id/${foo.id}
and return the whole thing as barFetch
(the component will not have access to the intermediate foo
):
connect(({ name }) => ({ barFetch: { url: `/foos/${name}`, then: foo => `/bar-for-foos-by-id/${foo.id}` } }))
andThen()
is similar, but is intended for side effect requests where you still need access to the result of the first request and/or need to fanout to multiple requests:
connect(({ name }) => ({ fooFetch: { url: `/foos/${name}`, andThen: foo => ({ barFetch: `/bar-for-foos-by-id/${foo.id}` }) } }))
This is also helpful for cases where a fetch function is changing data that is in some other fetch that is a collection. For example, if you have a list of foo
s and you create a new foo
and the list needs to be refreshed:
connect(({ name }) => ({ foosFetch: '/foos', createFoo: name => ({ fooCreation: { method: 'POST', url: '/foos', andThen: () => ({ foosFetch: { url: '/foos', refreshing: true } }) } }) }))
catch
and andCatch
are similar, but for error cases.
To support static data and response transformations, there is a special kind of request called an "identity request" that has a value
instead of a url
. The value
is passed through directly to the PromiseState
without actually fetching anything. In its pure form, it looks like this:
connect(props => ({ usersFetch: { value: [ { id: 1, name: 'Jane Doe', verified: true }, { id: 2, name: 'John Doe', verified: false } ] } }))(Users)
In this case, the usersFetch
PromiseState
will be set to the provided list of users. The use case for identity requests by themselves is limited to mostly
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音 的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。
openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号