I am building a dashboard that contains many different widgets. User can add and remove any widget and place them in any order they prefer to. Each widget has its own data requirement. What is the correct Relay way to construct the container hierarchy?
To provide some context this is the architecture so far:
Widget
is a component that takes in a config object and render the corresponding component accordingly.
class Widget extends React.Component {
render() {
const {widget} = this.props;
// widgetMap is a map that maps string to React component
const ActualWidget = widgetMap.get(widget.component);
return (
<ActualWidget data={widget.data} />
);
}
}
export default Relay.createContainer(Widget, {
fragments: {
data: () => Relay.QL`
fragment on ??? {
# What should be here since we don't know what will be rendered?
}
`
}
});
Dashboard
component holds a number of widgets added by user.
class Dashboard extends React.Component {
renderWidgets = () => {
return this.props.widgets.map(widget => {
return <Widget widget={widget}/>;
});
};
render() {
return (
<div>
<span>Hello, {this.props.user.name}</span>
{this.renderWidgets()}
</div>
);
}
}
export default Relay.createContainer(Dashboard, {
fragments: {
// `user` fragment is used by Dashboard
user: () => Relay.QL`
fragment on User {
name
}
`,
// Each widget have different fragment,
// So what should be here?
}
});
Update
I have tried to make each ActualWidget
to be a field of viewer. So the schema is sort of like this:
type Viewer {
widget1: Widget1
widget2: Widget2
}
type Widget1 {
name,
fieldOnlyForWidget1
}
type Widget2 {
name,
fieldOnlyForWidget2
}
Then for my Widget
container, I try to insert the fragment dynamically.
export default Relay.createContainer(Widget, {
initialVariables: {
component: 'Widget1' // Trying to set the string here
}
fragments: {
data: (variables) => Relay.QL`
fragment on Viewer { # We now know it is Viewer type
# This didn't work because `variables.component` is not string! :(
${widgetMap.get(variables.component).getFragment('viewer')}
}
`
}
});
That does not work. I believe Relay parsed the QL statically so it is unable to compose dynamic fragments. However that's just my guess.
I am in the process of testing the feasibility of using RootContainer
for each widget and will update this soon.
I think the root issue here is you're trying to have the client decide, at runtime, the types of data to request based on some data given to it by the server (the widget names). That's always going to be a two-step process so you cannot nest the child fragments in the parent to do a single fetch; therefore, you'll need to set up a new RootContainer
or something to kick off a new set of queries once you know what you need.
However, I think you might be able pull this off as a single fetch using regular nesting by encoding the widget information in the graph itself. The trick here is to use GraphQL Union types: docs
Union types should allow you to describe your "Dashboard" type as having a list of "Widgets". If you define "Widget" as a union type, you could have many different types of Widgets with their own unique fields and data.
Once your server is serving up union types, you can then write a Dashboard relay container that looks something like this:
var Dashboard = Relay.createContainer(DashboardComponent, {
fragments: {
dashboard: () => { console.log("dashboard query"); return Relay.QL`
fragment on Dashboard {
widgets {
_typename
${FooWidget.getFragment("widget")}
${BarWidget.getFragment("widget")}
}
}
`},
}
});
Note the special "__typename" field that is defined on union types (see this question for some details). Your dashboard component can then use this.props.dashboard.widgets
to iterate through all the widgets and create the appropriate widget based on the __typename
, something like this:
var widgets = this.props.dashboard.widgets.map((widget) => {
if (widget.__typename == "FooWidget") {
return <FooWidget widget={widget} />
} else if (widget.__typename == "BarWidget") {
return <CountdownWidget widget={widget} />
}
})
I'm pretty sure this will work, but haven't tried it at all myself. From what I understand of Relay (which isn't that much), this would probably be the most "Relay-ish" way to model your problem.
Does that make sense?