Reputation: 733
I'm making a web app with React, Relay (experimental), and Typescript and I'm having some issues re-using components with similar data/props across different fragments.
Let's say I have the following Relay query and main app component:
const query = graphql`
query AppQuery {
currentUser {
...HomePage_currentUser
...ProfilePage_currentUser
}
}
`;
export default function App(): JSX.Element {
const appData = useLazyLoadQuery<AppQuery>(query, {});
return (
<>
<HomePage currentUser={appData.currentUser}>
<ProfilePage currentUser={appData.currentUser}>
</>
);
}
And the following page components:
interface HomePageProps {
currentUser: HomePage_currentUser$key | null;
}
const currentUserFragment = graphql`
fragment HomePage_currentUser on User {
id
profileImageUrl
items {
id
name
}
}
`;
export default function HomePage(props: HomePageProps): JSX.Element {
const currentUser = useFragment(currentUserFragment, props.currentUser);
return (
<>
{/* ... */}
<ProfileIcon user={currentUser} >
{/* ... */}
</>
)
}
interface ProfilePageProps {
currentUser: ProfilePage_currentUser$key | null;
}
const currentUserFragment = graphql`
fragment ProfilePage_currentUser on User {
id
profileImageUrl
lastLoggedInTimestamp
}
`;
export default function ProfilePage(props: ProfilePageProps): JSX.Element {
const currentUser = useFragment(currentUserFragment, props.currentUser);
return (
<>
{/* ... */}
<ProfileIcon user={currentUser} >
{/* ... */}
</>
)
}
And the following ProfileIcon
component
interface ProfileIconProps {
currentUser: ???
}
export default function ProfileIcon(props: ProfileIconProps): JSX.Element {
return (
<div>
<img src={props.currentUser.profileImageUrl} />
</div>
)
}
My main question is regarding the type of the currentUser
prop in the ProfileIcon
component. It seems like there's no clean way to re-use the component for a ProfilePage_currentUser
type and a HomePage_currentUser
type despite the data being requested being incredibly similar and it clearly being compatible for the sake of this component.
Is there any recommended way to handle this other than something like this?
interface ProfileIconProps {
currentUser: Omit<ProfilePage_currentUser, ' $refType'>
}
Upvotes: 3
Views: 1613
Reputation: 3634
It's not a good practice in Relay to extract the data and pass it to a component as a prop manually. Instead you should have a fragment on the ProfileIcon
component and let Relay takes care of passing the same data down to your component.
So you need to create a fragment in ProfileIcon component and query for profileImageUrl
then add this fragment in both HomePage_currentUser
and ProfilePage_currentUser
fragments and relay will take care of the rest for you.
interface ProfileIconProps {
currentUser: ProfileIcon_currentUser$key | null;
}
const currentUserFragment = graphql`
fragment ProfileIcon_currentUser on User {
profileImageUrl
}
`;
export default function ProfileIcon(props: ProfileIconProps): JSX.Element {
const currentUser = useFragment(currentUserFragment, props.currentUser);
return (
<div>
<img src={props.currentUser.profileImageUrl} />
</div>
)
}
In HomePage component add ProfileIcon_currentUser
fragment
const currentUserFragment = graphql`
fragment HomePage_currentUser on User {
id
items {
id
name
}
...ProfileIcon_currentUser
}
`;
In ProfilePage component add ProfileIcon_currentUser
fragment
const currentUserFragment = graphql`
fragment ProfilePage_currentUser on User {
id
lastLoggedInTimestamp
...ProfileIcon_currentUser
}
`;
One of the advantage of using this pattern is that if you have a mutation on User somewhere else (even not within the same react node tree hierarchy) and let's say you
change the profileImageUrl
then the ProfileIcon will receive the new profileImageUrl
automatically without you passing around a new value of profileImageUrl. In fact, Relay store will be updated with the new value on mutation payload and as soon as as there is a new update in store, it will pass that data to any components that are using that data.
Upvotes: 5