-
Notifications
You must be signed in to change notification settings - Fork 786
Dynamic queries passed into graphql() #330
Comments
You could use that JSON graphql type and then https://github.com/hapijs/joi to verify the schema of the incoming data. GraphQL is leaning towards static queries, so that they can be fetched efficiently… |
Thanks for the response @wmertens! Adding a separate JSON schema on top of GraphQL sounds a bit like reinventing the wheel :) I'm having a play with |
Hmm, I do like that the current API heavily recommends using static queries - after all, we think it's the right approach for most apps: https://dev-blog.apollodata.com/5-benefits-of-static-graphql-queries-b7fa90b0b69a#.axv02k49m It sounds like you just want to have dynamic queries on app initialization time, rather than on each render of a component? I think that could be somewhat complex because it makes the loading state of a component much harder to determine. First we have to wait for something to decide what the query should be, then send that to the server and wait for results. Can you give me an example of the transformation you would want to do? |
That's the largest use case for us. For example, we might have a We also have a "form builder" component which retrieves a "form schema" on first render of the component, which determines the rendered fields. Forms can be loaded by the user navigating around the UI. In an ideal world we could use this schema to construct a form submission query at render rather than initialisation time. We're working around this through a generic |
I've discovered that |
My proposal for a "fragment registry" in our CMS UI might clarify our use case a bit: silverstripe/silverstripe-graphql#13 |
@stubailo Would you be open to adding the |
@chillu what's your use case? |
Oh - I see there are a lot of comments above. I'll defer to @helfer. |
Can you build a custom version of |
Thanks for your feedback @stubailo! I've solved part of our problem now by creating a "lazy HOC" around register = new GraphQLRegister();
export function graphqlWithRegister(document, operationOptions) {
return function wrappedGraphQLWithRegister(WrappedComponent) {
let ComponentWithGraphQL; // Closed-over value is scoped to component
class WrappedGraphQLWithRegister extends Component {
constructor() {
super();
// Lazy initialising of HOC to give customisations an opportunity to be registered
if (!ComponentWithGraphQL) {
ComponentWithGraphQL = graphql(
// Adds customisations which have been registered by third party code
register.transformDocument(document, operationOptions),
register.transformOptions(document, operationOptions)
)(WrappedComponent);
}
}
render() {
return <ComponentWithGraphQL {...this.props} />;
}
}
WrappedGraphQLWithRegister.displayName = `WrappedGraphQLWithRegister(${getDisplayName(WrappedComponent)})`;
return WrappedGraphQLWithRegister;
};
} Check GraphQLRegister.js for a really rough but working version of this (and more context on our issue tracker). The other part (dynamic queries at runtime) would need to be solved by a variation of the Anyway, I'm closing this since you made the intentions of the library clear - thanks again for responding. |
hi @chillu , I found that I have same demands like yours. Is there any official way in react-apollo supporting dynamic graphql query at run time? Please help and give me some advise. Happy Christmas!! Thanks. :) |
@bbbbighead I can't comment on the roadmap for react-apollo, but from what the maintainers have said before I'm not getting my hopes up for dynamic query support there. We might end up creating our own variation of react-apollo, for now we've parked the issue since we've derisked the issue for our use case. |
Does anyone have a suggested technique for passing dynamic queries into |
From a discussion on Slack, if you need to do a dynamic query, you can use another higher-order component, like this:
|
@dahjelle as far as I can see this method could bring really bad problems. New instanc of Or am I missing something? |
Thanks for the feedback, @nosovsh! I appreciate hearing from folks with more expertise than myself. :-D I don't disagree…I frankly don't know enough about that part of React.
Makes sense. I suppose the usual way of dealing with that is putting all of one's state in Redux or such like?
This, too, makes sense. Do you have any data as to the performance impact this would have for stateless components? Are you aware of an approach for dynamic queries that you'd recommend? Perhaps @chillu's approach above? |
Hey everyone, I have a use case I'd like to support with apollo/graphql: Basically the idea is to allow users to define which columns (from a set list) they would like to view on a data table. Querying for all of the columns could mean a lot of unnecessary data would be grabbed, thus, I'd like to only send a graphql query with the user-requested columns. I haven't been able to figure out how to do this yet, but I might be missing something. Any help would be greatly appreciated! Also, this seems like a pretty common use case. I understand that apollo/relay are moving toward static queries (which might make sense for 90% of use cases), but it seems unwise to completely drop all dynamic query functionality (sometimes you just need to do it). |
@dahjelle Regarding performance I don't know what React will do exactly but my guess it will run the full life cycle for new component on each rerender (constructor, onComponentDidMount etc). And you will not have much profit from functional components. But I could be wrong here please check it yourself. I'm writing this because we were doing the same and finally faced bug with loosing local state that was reeeally hard to catch :) After that we just switched from fully dynamic queries (sent by props) to hoc generators. Our queries are static but you can use the same component with different queries. |
@nosovsh , could you point to a repo or share code for your dynamic query solution using HOC generators? I'd be curious to see how you're doing it. Thanks! |
For whatever it's worth, I'm trying a technique like @chillu's above. In my case, the
The main limitation I can see is that, while the query is dynamic on instantiation, changing the Other pros/cons that anyone sees? I think that, if you need your queries to change in response to props without re-instantiating all child components, one would have to wrap using |
I just found a note in official docs about loosing state if using hoc inside render: https://facebook.github.io/react/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method |
@tnrich please open a new issue for this! By the way Apollo supports any kind of queries, not just static ones. So it should be easy to do what you're looking for. |
@dahjelle , what do you use for the |
@tnrich Something like the following (simplified a lot from our real code, but hopefully better gets the point across):
You can pass any of the regular |
In my setup, my component will no longer rerender when the loading state changes, only when new results are rendered. |
I ran into this same issue today. My solve was to set the Example:
Now all instances of |
@mattyfresh This is not the problem.
|
Ah I see, didn't understand what you meant by 'dynamic'. Would be great if you could just pass a function that returns a gql doc as the first argument of the |
@EyMaddis So how can I do the dynamic query? I have tried but an error is thrown. |
I made a short try on this the other day. This is completely possible, and could easily replace some directives, like to only request some fields if a condition is match. import React from 'react'
import { compose, withState } from 'recompose'
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import withData from 'app/lib/withData'
const gqlDynamic = (strings, ...expressions) => ({ strings, expressions })
const dynQuery = gqlDynamic`
query {
currentUserContext {
${({ withId }) => withId ? 'uid' : ''}
entityLabel
name
}
}
`
const graphqlDynamic = ({ strings, expressions }, ...args) => ComposedCmoponent => class GraphQLProps extends React.Component {
resolveExpression = expression => typeof expression === 'function'
? this.resolveExpression(expression(this.props))
: expression
render () {
const query = gql(strings, expressions.map(this.resolveExpression))
const ConnectedComposedCmoponent = compose(
graphql(query)
)(ComposedCmoponent)
return (
<ConnectedComposedCmoponent { ...this.props } />
)
}
}
// eslint-disable-next-line react/prop-types
const Page = ({ setWithId, withId, ...props }) => (
<div>
<button onClick={ () => setWithId(!withId) }>Toggle user id!</button>
<pre>{ JSON.stringify(props, null, 2) }</pre>
</div>
)
export default compose(
withData,
withState('withId', 'setWithId', false),
graphqlDynamic(dynQuery),
)(Page)
|
I wanted to share my take on this to help others and to get feedback. It's based on the proposed Query component for version 2.1 from the roadmap which looks like this: <Query
skip={boolean}
options={QueryOptions}
loading={(result?) => Component || null}
error={(result?) => Component || null}
render={(result) => Component}
/> So this is what I came up with and hopefully when version 2.1 comes refactoring to react-apollo's Query component will be really easy. import React from "react";
import { bool, func, number, object, oneOf, oneOfType, string } from "prop-types";
import gql from "graphql-tag";
import { graphql } from "react-apollo";
// Cache of QueryContainer components based on parsed query or query string
const cache = new Map();
const QueryComponent = props => {
const { data, loading, error, render } = props;
if (data.networkStatus === 1) {
// Query has never been run before
return loading(data);
}
if (data.networkStatus === 8) {
// Error, with no request in flight
return error(data);
}
if (data.error) {
// Error, but there is a request in flight...
// ...let's hope for the best!
return loading(data);
}
// data.loading might be true, but we can still show cached data
return render(data);
};
QueryComponent.displayName = "QueryComponent";
const Query = props => {
const { query } = props;
if (!cache.has(query)) {
const parsedQuery = typeof query === "object" ? query : gql(query);
cache.set(query, graphql(parsedQuery, {
skip: ({ skip }) => skip,
options: ({ options }) => options,
})(QueryComponent));
}
const QueryContainer = cache.get(query);
return <QueryContainer {...props} />;
};
Query.displayName = "Query";
Query.propTypes = {
query: oneOfType([string, object]).isRequired,
skip: bool,
options: object,
loading: func,
error: func,
render: func.isRequired,
};
Query.defaultProps = {
loading: () => null,
error: () => null,
};
export default Query; |
I wanted to do the same thing @EyMaddis mentioned in #330 (comment). I ended up publishing a simple wrapper around react-apollo that allows for a function that generates a query from your props. If you don't want to include another dependency, you can just copy the function out of the source code. |
We're converting data access for our PHP-based CMS UI into GraphQL. Naturally, the "active record" style data types managed by the CMS are user defineable (via PHP), so every installation of the CMS will have a different UI. We have a dynamic "form schema" which determines the form fields in a particular installation, and are hoping to use a GraphQL mutation for the form submission - with a 1:1 mapping between form fields and GraphQL fields. At the moment, we're not requiring PHP developers to install a JS build toolchain to achieve this level of customisation.
The graphql() HOC expects a
gql
document passed in when the script is first evaluated. We're looking for a way to allow dynamic definition of fields at this point, rather than bundling them into the JS build.From what I can tell, the HOC doesn't actually use the query for anything before the component is constructed for the first time, the only query-related "initialisation" behaviour is
const operation = parser(document)
. Would it be possible to pass in aPromise
instead ofDocument
intographql()
, which lazily returns a GraphQL query and defers any logic in the HOC until an instance of it is created? The same would be required for query-related options likefragments
. I'm aware that this usage would bypass some of the static analysis benefits which GraphQL clients like Relay or Apollo have now, or build out in the future. In our case, we'd use this Promise to inspect a "fragment registry" which could be populated with server-generated JSON with varying fields.If you don't think this is appropriate for the
graphql()
HOC, can you think of a "middle path" where we can still take advantage of components being aware of queries, without dropping straight down to aclient.watchQuery()
level?There's been a similar discussion on Relay, around the need for
babel-relay-plugin
to access the schema at JS build time (rather than early runtime in Apollo). Note that the "JSON GraphQL Type" mentioned there won't cut it for us, since it negates a main benefit of GraphQL (strong typing).Overall, I think a lot of CMS devs are getting excited about the expressive possibilities of GraphQL, but the lacking support for dynamic schemas will be a stumbling block for any customiseable UI that's not just building out single-deployment web applications.
Thanks for Apollo, it's been a great experience for us so far - we've actively evaluated it against Relay and found it to be a lot more friendly to use!
The text was updated successfully, but these errors were encountered: