PageSourceSearch

https://relay.dev/assets/js/97cb6bc5.ba83f24a.js

js relay.dev collected 2026-10-02 03:12:28 UTC 18,939 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk=self.webpackChunk||[]).push([["16785"],{28630(e,n,t){t.r(n),t.d(n,{metadata:()=>r,default:()=>l,frontMatter:()=>i,contentTitle:()=>o,toc:()=>c,assets:()=>h});var r=JSON.parse('{"id":"guided-tour/refetching/OssAvoidSuspenseNote","title":"OssAvoidSuspenseNote","description":"In future versions of React when concurrent rendering is supported, React will provide an option to support this case and avoid hiding already rendered content with a Suspense fallback when suspending.","source":"@site/versioned_docs/version-v14.0.0/guided-tour/refetching/OssAvoidSuspenseNote.md","sourceDirName":"guided-tour/refetching","slug":"/guided-tour/refetching/OssAvoidSuspenseNote","permalink":"/docs/v14.0.0/guided-tour/refetching/OssAvoidSuspenseNote","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/relay/tree/main/website/versioned_docs/version-v14.0.0/guided-tour/refetching/OssAvoidSuspenseNote.md","tags":[],"version":"v14.0.0","frontMatter":{}}'),s=t(74848),a=t(28453);let i={},o,h={},c=[];function d(e){let n={admonition:"admonition",p:"p",...(0,a.R)(),...e.components};return(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"In future versions of React when concurrent rendering is supported, React will provide an option to support this case and avoid hiding already rendered content with a Suspense fallback when suspending."})})}function l(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},62660(e,n,t){t.r(n),t.d(n,{metadata:()=>r,default:()=>g,frontMatter:()=>c,contentTitle:()=>d,toc:()=>u,assets:()=>l});var r=JSON.parse('{"id":"guided-tour/refetching/refreshing-fragments","title":"Refreshing Fragments","description":"Relay guide to refreshing fragments","source":"@site/versioned_docs/version-v14.0.0/guided-tour/refetching/refreshing-fragments.md","sourceDirName":"guided-tour/refetching","slug":"/guided-tour/refetching/refreshing-fragments/","permalink":"/docs/v14.0.0/guided-tour/refetching/refreshing-fragments/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/relay/tree/main/website/versioned_docs/version-v14.0.0/guided-tour/refetching/refreshing-fragments.md","tags":[],"version":"v14.0.0","frontMatter":{"id":"refreshing-fragments","title":"Refreshing Fragments","slug":"/guided-tour/refetching/refreshing-fragments/","description":"Relay guide to refreshing fragments","keywords":["refreshing","fragment"]}}'),s=t(74848),a=t(28453),i=t(86898),o=t(17352),h=t(28630);let c={id:"refreshing-fragments",title:"Refreshing Fragments",slug:"/guided-tour/refetching/refreshing-fragments/",description:"Relay guide to refreshing fragments",keywords:["refreshing","fragment"]},d,l={},u=[{value:"Using real-time features",id:"using-real-time-features",level:2},{value:"Using <code>useRefetchableFragment</code>",id:"using-userefetchablefragment",level:2},{value:"If you need to avoid Suspense",id:"if-you-need-to-avoid-suspense",level:3},...h.toc];function f(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.p,{children:["When referring to ",(0,s.jsx)(n.strong,{children:'"refreshing a fragment"'}),", we mean fetching the ",(0,s.jsx)(n.em,{children:"exact"})," same data that was originally rendered by the fragment, in order to get the most up-to-date version of that data from the server."]}),"\n",(0,s.jsx)(n.h2,{id:"using-real-time-features",children:"Using real-time features"}),"\n",(0,s.jsxs)(o.OssOnly,{children:[(0,s.jsx)(n.p,{children:"If we want to keep our data up to date with the latest version from the server, the first thing to consider is if it appropriate to use any real-time features, which can make it easier to automatically keep the data up to date without manually refreshing the data periodically."}),(0,s.jsxs)(n.p,{children:["One example of this is using ",(0,s.jsx)(n.a,{href:"https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/",children:"GraphQL Subscriptions"}),", which will require additional configuration on your server and ",(0,s.jsx)(n.a,{href:"https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/#configuring-the-network-layer",children:"network layer"}),"."]})]}),"\n",(0,s.jsxs)(n.h2,{id:"using-userefetchablefragment",children:["Using ",(0,s.jsx)(n.code,{children:"useRefetchableFragment"})]}),"\n",(0,s.jsxs)(n.p,{children:["In order to manually refresh the data for a fragment, we need a query to refetch the fragment under; remember, ",(0,s.jsx)(n.em,{children:"fragments can't be fetched by themselves: they need to be part of a query,"}),' so we can\'t just "fetch" the fragment again by itself.']}),"\n",(0,s.jsxs)(n.p,{children:["To do so, we can also use the ",(0,s.jsx)(n.a,{href:"../../../api-reference/use-refetchable-fragment/",children:(0,s.jsx)(n.code,{children:"useRefetchableFragment"})})," Hook in combination with the ",(0,s.jsx)(n.code,{children:"@refetchable"})," directive, which will automatically generate a query to refetch the fragment under, and which we can fetch using the ",(0,s.jsx)(n.code,{children:"refetch"})," function:"]}),"\n",(0,s.jsxs)(o.OssOnly,{children:[(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"import type {UserComponent_user$key} from 'UserComponent_user.graphql';\n// This type is autogenerated by Relay given @refetchable used below\nimport type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';\n\ntype Props = {\n  user: UserComponent_user$key,\n};
1\n\nfunction UserComponent(props: Props) {\n  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(\n    graphql`\n      fragment UserComponent_user on User\n      # @refetchable makes it so Relay autogenerates a query for\n      # fetching this fragment\n      @refetchable(queryName: \"UserComponentRefreshQuery\") {\n        id\n        name\n        friends {\n          count\n        }\n      }\n    `,\n    props.user,\n  );\n\n  const refresh = useCallback(() => {\n    // We call refetch with empty variables: `{}`,\n    // which will refetch the @refetchable query with the same\n    // original variables the fragment was fetched with, and update\n    // this component with the latest fetched data.\n    // The fetchPolicy ensures we always fetch from the server and skip\n    // the local data cache.\n    refetch({}, {fetchPolicy: 'network-only'})\n  }), [/* ... */];\n\n  return (\n    <>\n      <h1>{data.name}</h1>\n      <div>Friends count: {data.friends?.count}</div>\n      <Button\n        onClick={() => refresh()}>\n        Fetch latest count\n      </Button>\n    </>\n  );\n}\n"})}),(0,s.jsx)(n.p,{children:"Let's distill what's happening in this example:"}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"useRefetchableFragment"})," behaves similarly to ",(0,s.jsx)(n.a,{href:"../../../api-reference/use-fragment/",children:(0,s.jsx)(n.code,{children:"useFragment"})})," (see the ",(0,s.jsx)(n.a,{href:"../../rendering/fragments/",children:"Fragments"})," section), but with a few additions:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["It expects a fragment that is annotated with the ",(0,s.jsx)(n.code,{children:"@refetchable"})," directive. Note that  ",(0,s.jsx)(n.code,{children:"@refetchable"}),' directive can only be added to fragments that are "refetchable", that is, on fragments that are on ',(0,s.jsx)(n.code,{children:"Viewer"}),", on ",(0,s.jsx)(n.code,{children:"Query"}),", on any type that implements ",(0,s.jsx)(n.code,{children:"Node"})," (i.e. a type that has an ",(0,s.jsx)(n.code,{children:"id"})," field)."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["It returns a ",(0,s.jsx)(n.code,{children:"refetch"})," function, which is already Flow-typed to expect the query variables that the generated query expects"]}),"\n",(0,s.jsxs)(n.li,{children:["It takes two Flow type parameters: the type of the generated query (in our case  ",(0,s.jsx)(n.code,{children:"UserComponentRefreshQuery"}),"), and a second type which can always be inferred, so you only need to pass underscore (",(0,s.jsx)(n.code,{children:"_"}),")."]}),"\n",(0,s.jsxs)(n.li,{children:["We're calling the ",(0,s.jsx)(n.code,{children:"refetch"})," function with 2 main inputs:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The first argument is the set of variables to fetch the fragment with. In this case, calling ",(0,s.jsx)(n.code,{children:"refetch"})," and passing an empty set of variables will fetch the fragment again ",(0,s.jsx)(n.em,{children:"with the exact same variables the fragment was originally fetched with,"})," which is what we want for a refresh."]}),"\n",(0,s.jsxs)(n.li,{children:["In the second argument we are passing a ",(0,s.jsx)(n.code,{children:"fetchPolicy"})," of ",(0,s.jsx)(n.code,{children:"'network-only'"})," to ensure that we always fetch from the network and skip the local data cache."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["Calling ",(0,s.jsx)(n.code,{children:"refetch"})," will re-render the component and cause ",(0,s.jsx)(n.code,{children:"useRefetchableFragment"})," to suspend (as explained in ",(0,s.jsx)(n.a,{href:"../../rendering/loading-states/",children:"Loading States with Suspense"}),"), since a network request will be required due to the ",(0,s.jsx)(n.code,{children:"fetchPolicy"})," we are using. This means that you'll need to make sure that there's a ",(0,s.jsx)(n.code,{children:"Suspense"})," boundary wrapping this component from above in order to show a fallback loading state."]}),"\n"]})]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Note that this same behavior also applies to using the ",(0,s.jsx)(n.code,{children:"refetch"})," function from ",(0,s.jsx)(n.a,{href:"../../../api-reference/use-pagination-fragment",children:(0,s.jsx)(n.code,{children:"usePaginationFragment"})}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"if-you-need-to-avoid-suspense",children:"If you need to avoid Suspense"}),"\n",(0,s.jsxs)(n.p,{children:["In some cases, you might want to avoid showing a Suspense fallback, which would hide the already rendered content. For these cases, you can use ",(0,s.jsx)(n.a,{href:"../../../api-reference/fetch-query/",children:(0,s.jsx)(n.code,{children:"fetchQuery"})})," instead, and manually keep track of a loading state:"]}),"\n",(0,s.jsx)(o.OssOnly,{children:(0,s.jsx)(h.default,{})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"import type {UserComponent_user$key} from 'UserComponent_user.graphql';\n// This type is autogenerated by Relay given @refetchable used below\nimport type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';\n\ntype Props = {\n  user: UserComponent_user$key,\n};
1\n\nfunction UserComponent(props: Props) {\n  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(\n    graphql`\n      fragment UserComponent_user on User\n      # @refetchable makes it so Relay autogenerates a query for\n      # fetching this fragment\n      @refetchable(queryName: \"UserComponentRefreshQuery\") {\n        id\n        name\n        friends {\n          count\n        }\n      }\n    `,\n    props.user,\n  );\n\n  const [isRefreshing, setIsRefreshing] = useState(false);\n  const refresh = useCallback(() => {\n    if (isRefreshing) { return; }\n    setIsRefreshing(true);\n\n    // fetchQuery will fetch the query and write\n    // the data to the Relay store. This will ensure\n    // that when we re-render, the data is already\n    // cached and we don't suspend\n    fetchQuery(environment, AppQuery, variables)\n      .subscribe({\n        complete: () => {\n          setIsRefreshing(false);\n\n          // *After* the query has been fetched, we call\n          // refetch again to re-render with the updated data.\n          // At this point the data for the query should\n          // be cached, so we use the 'store-only'\n          // fetchPolicy to avoid suspending.\n          refetch({}, {fetchPolicy: 'store-only'});\n        },\n        error: () => {\n          setIsRefreshing(false);\n        }\n      });\n  }, [/* ... */]);\n\n  return (\n    <>\n      <h1>{data.name}</h1>\n      <div>Friends count: {data.friends?.count}</div>\n      <Button\n        disabled={isRefreshing}\n        onClick={() => refresh()}>\n        Fetch latest count {isRefreshing ? <LoadingSpinner /> : null}\n      </Button>\n    </>\n  );\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Let's distill what's going on here:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["When refreshing, we now keep track of our own ",(0,s.jsx)(n.code,{children:"isRefreshing"})," loading state, since we are avoiding suspending. We can use this state to render a busy spinner or similar loading UI in our component, ",(0,s.jsx)(n.em,{children:"without"})," hiding the content."]}),"\n",(0,s.jsxs)(n.li,{children:["In the event handler, we first call ",(0,s.jsx)(n.code,{children:"fetchQuery"}),", which will fetch the query and write the data to the local Relay store. When the ",(0,s.jsx)(n.code,{children:"fetchQuery"})," network request completes, we call ",(0,s.jsx)(n.code,{children:"refetch"})," so that we render the updated data, similar to the previous example."]}),"\n",(0,s.jsxs)(n.li,{children:["At this point, when ",(0,s.jsx)(n.code,{children:"refetch"})," is called, the data for the fragment should already be cached in the local Relay store, so we use ",(0,s.jsx)(n.code,{children:"fetchPolicy"})," of ",(0,s.jsx)(n.code,{children:"'store-only'"})," to avoid suspending and only read the already cached data."]}),"\n"]}),"\n",(0,s.jsx)(i.A,{})]})}function g(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(f,{...e})}):f(e)}},86898(e,n,t){t.d(n,{A:()=>g});var r=t(74848),s=t(95310),a=t(17352),i=t(96540);function o(){let e=window.encodeURI(JSON.stringify({title:"Feedback about "+window.location.pathname,description:"**!!! Required !!!**\n\nPlease modify the task description to let us know how the docs can be improved.\n\n**Please do not ask support questions via this form! Instead, ask in fburl.com/relay_support**",tag_ids:{add:[0xac96423e5b680,0x64079768ac750]}}));window.open("https://www.internalfb.com/tasks/?n="+e)}function h({children:e}){return(0,r.jsxs)("div",{className:"docsRating",id:"docsRating",children:[(0,r.jsx)("hr",{}),e]})}let c=()=>{let[e,n]=i.useState(!1),t=e=>{n(!0),window.ga&&window.ga("send",{hitType:"event",eventCategory:"button",eventAction:"feedback",eventValue:e})};return e?"Thank you for letting us know!":(0,r.jsxs)(r.Fragment,{children:["Is this page useful?",(0,r.jsx)("svg",{className:"i_thumbsup",alt:"Like",id:"docsRating-like",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 81.13 89.76",onClick:()=>t(1),children:(0,r.jsx)("path",{d:"M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z"})}),(0,r.jsx)("svg",{className:"i_thumbsdown",alt:"Dislike",id:"docsRating-dislike",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 81.13 89.76",onClick:()=>t(0),children:(0,r.jsx)("path",{d:"M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z"})})]})},d=()=>(0,r.jsxs)("p",{children:["Let us know how these docs can be improved by",(0,r.jsx)("a",{className:"button",role:"button",tabIndex:0,onClick:o,children:"Filing a task"})]}),l=()=>(0,r.jsxs)("p",{children:["Help us make the site even better by"," ",(0,r.jsx)(s.default,{to:"https://www.surveymonkey.com/r/FYC9TCJ",children:"answering a few quick questions"}),"."]}),u=()=>(0,r.jsxs)(h,{children:[(0,r.jsx)(d,{}),(0,r.jsx)(c,{}),(0,r.jsx)(l,{})]}),f=()=>(0,r.jsxs)(h,{children:[(0,r.jsx)(c,{}),(0,r.jsx)(l,{})]}),g=()=>(0,a.fbContent)({internal:(0,r.jsx)(u,{}),external:(0,r.jsx)(f,{})})},28453(e,n,t){t.d(n,{R:()=>i,x:()=>o});var r=t(96540);let s={},a=r.createContext(s);function i(e){let n=r.useContext(a);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),r.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.