Relay NewsConcurrent, Suspense and HooksDanilo Assis
Danilo Assis
@daniloab
@daniloab_
Fullstack Developer

Why Relay

  • declarative data
  • data fetching
  • colocation

Case

Case

Case

Benefits and Caveats

  • Simple Code
  • Better UI Experience
  • Opposed to HOCs
  • React Concurrent Mode and Suspense

React Concurrent Mode

  • What is
  • Interruptible Rendering
  • Intentional Loading
  • Still using react normally: state, props, hooks

Suspense

  • What is
  • Mechanism, not library
  • What not to do
  • What Suspense do

Suspense

// Show a spinner while the post list is loading
<ErrorBoundary>
<Suspense fallback={<Spinner />}>
<PostList />
</Suspense>
</ErrorBoundary>

component that lets you “wait” for some code to load

Error Boundary

export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error) {
return {
error,
};
}
render() {
if (this.state.error != null) {
return (
<div>
<div>Error: {this.state.error.message}</div>
<div>
<pre>{JSON.stringify(this.state.error.source, null, 2)}</pre>
</div>
</div>
);
}
return this.props.children;
}
}

React component to catch javascript errors anywhere

Relay Hooks

  • RelayEnvironmentProvider
  • useRelayEnvironment
  • usePreloadedQuery
  • useLazyLoadQuery
  • useFragment
  • useRefetchableFragment
  • usePaginationFragment

RelayEnvironmentProvider

const React = require('React');
const {RelayEnvironmentProvider} = require('react-relay/hooks');
function Root() {
return (
<RelayEnvironmentProvider environment={environment}>
<App />
</RelayEnvironmentProvider>
);
}
module.exports = Root;

RelayEnvironmentProvider will wrap our app setting the environment on React Context

useRelayEnvironment

const React = require('React');
const {useRelayEnvironment} = require('react-relay/hooks');
function MyComponent() {
const environment = useRelayEnvironment();
const handler = useCallback(() => {
// For example, can be used to pass the environment to functions
// that require a Relay environment.
commitMutation(environment, ...);
}, [environment])
return (...);
}
module.exports = Root;

Hook used to access a Relay environment that was set by a RelayEnvironmentProvider

usePreloadedQuery

const AppEnvironment = require('./AppEnvironment'); // user-defined
const query = graphql`
query PostQuery($id: ID!) {
post(id: $id) {
id
title
image
description
}
}
`;
// Note: call in an event-handler or similar, not during render
const result = preloadQuery(
AppEnvironment,
query,
{id: '4'},
{fetchPolicy: 'store-or-network'},
);
function App() {
const data = usePreloadedQuery<PostQuery>(query, result);
return (
<>
<p>{data.post.title}</p>
<div>
<img src={data.post.image}/>
<span>{data.post.description}</span>
</div>
</>
);
}

Hook used to access data fetched by an earlier call to preloadQuery().

useLazyLoadQuery

function App() {
const data = useLazyLoadQuery<PostQuery>(
graphql`
query PostQuery($id: ID!) {
post(id: $id) {
id
title
image
description
}
}
`,
{id: 4},
{fetchPolicy: 'store-or-network'},
);
return (
<>
<p>{data.post.title}</p>
<div>
<img src={data.post.image}/>
<span>{data.post.description}</span>
</div>
</>
);
}

Hook used to fetch a GraphQL query during render.

useFragment

type Props = {
post: Post_post,
};
function Post(props: Props) {
const data = useFragment(
graphql`
fragment Post_post on Post {
id
title
image
description
}
`,
props.post,
);
return (
<>
<p>{data.post.title}</p>
<div>
<img src={data.post.image}/>
<span>{data.post.description}</span>
</div>
</>
);
}

Hook used to fetch a GraphQL query during render.

useRefetchableFragment

type Props = {
post: PostList_post,
};
function PostList(props: Props) {
const [startTransition] = useTransition();
const [data, refetch] =
useRefetchableFragment<PostListRefetchQuery, _>(
graphql`
fragment PostList_post on Post
@refetchable(queryName: "PostListRefetchQuery") {
id
name
image
descriptiion
}
`,
props.post,
);
return (
<>
<p>{data.post.title}</p>
<div>
<img src={data.post.image}/>
<span>{data.post.description}</span>
</div>
<Button
onClick={() => {
startTransition(() => {
refetch({postId: data.post.id},
{fetchPolicy: 'store-or-network'})}
});
}>
Like
</Button>
</>
);
}
module.exports = PostList;

You can use useRefetchableFragment when you want to fetch and re-render a fragment with different data

Real Application

References

  • https://relay.dev/
  • https://reactjs.org/
  • https://reactjs.org/docs/concurrent-mode-intro.html
  • https://reactjs.org/docs/concurrent-mode-suspense.html
  • https://github.com/relayjs/relay-examples
  • https://relay-modern-course.now.sh/packages/
Thanks!
We are hiring!
Join Us
Give me a Feedback:
https://entria.feedback.house/danilo