Im using Apollo Client 2 with React. Is there a callback for when a query has finished loading?
Im making a user's account page. The email field should display the users email. I can get this value with my graphQL query but the loading only finishes after the component has already mounted. I therefore cant use componentDidMount. Is there callback or event I use to setState and populate the email field that way?
import React from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
class AccountPage extends React.Component {
render() {
if (this.props.data.loading) return <p>Loading...</p>;
return(
<form>
<input type="email" />
</form>
)
}
}
const UserQuery = gql`
query UserQuery {
user {
_id
email
}
}
`;
export default graphql(UserQuery)(AccountPage);
There's a neat way to get around that with recompose:
const LoadingComponent = () => <p>Loading...</p>
compose(
// with compose, order matters -- we want our apollo HOC up top
// so that the HOCs below it will have access to the data prop
graphql(UserQuery),
branch(
({ data }) => {
return !data.user && data.loading
},
renderComponent(LoadingComponent)
),
// BONUS: You can define componentDidMount like normal, or you can use
// lifecycle HOC and make AccountPage a functional component :)
lifecycle({
componentDidMount() {
// set your state here
},
}),
)(AccountPage)
branch will conditionally render whatever component you pass to it instead of the wrapped component. In this case, the wrapped component is everything below branch inside of compose. That means AccountPage is not mounted until data.loading is false and data.user is truthy, which lets us use componentDidMount to set the state based on the query result.
Related
The Problem
When navigating away from query components that use the state of the app route as required variables, I get GraphQL errors of the sort:
Variable "$analysisId" of required type "ID!" was not provided.
"Navigating away" means, for example, going
from: /analysis/analysis-1/analyse/
to: /user-profile/
Background
I am building an SPA using Apollo GraphQL, and I have some queries which follow this pattern:
query Analyse($analysisId: ID!) {
location #client {
params {
analysisId #export(as: "analysisId")
}
}
analysis(analysisId: $analysisId) {
id
# ... etc
}
}
The location field gets a representation of the SPA router's state. That state is held in an Apollo client "reactive variable". Query components are programmed to not begin subscribing to the query unless that reactive variable exists and has the required content.
shouldSubscribe(): boolean {
return !!(locationVar()?.params?.analysisId);
}
Params represents express-style URL params, so the route path is /analysis/:analysisId/analyse.
If the user navigates to /analysis/analysis-1/analyse, the query component's variables become: { analysisId: "analysis-1" }`. This works fine when loading the component.
What I Think is Happening
When the component connects to the DOM, it checks to see if it's required variables are present in the router state, and if they are, it creates an ObservableQuery and subscribes.
Later, when the user navigates away, the ObservableQuery is still subscribed to updates when suddenly the required analysisId variable, exported by the client field location.params.analysisId is nullified.
I think that since the ObservableQuery is still subscribed, it sends off the query with null analysisId variable, even though it's required.
What I've Tried
By breaking on every method in my query component base class, I'm reasonably sure that the component base class is not at fault - there's no evidence that it is refetching the component when the route changes. Instead, I think this is happening inside the apollo client.
I could perhaps change the schema for the query from analysis(analysisId: ID!): Analysis to analysis(analysisId: ID): Analysis, but that seems roundabout, as I might not have control over the server.
How do I prevent apollo client from trying to fetch a query when it has required variables and they are not present?
This seems to be working fine so far, in my HttpLink, src/apollo/link/http.ts:
import { ApolloLink, from } from '#apollo/client/link/core';
import { HttpLink } from '#apollo/client/link/http';
import { hasAllVariables } from '#apollo-elements/lib/has-all-variables';
const uri =
'GRAPHQL_HOST/graphql';
export const httpLink = from([
new ApolloLink((operation, forward) => {
if (!hasAllVariables(operation))
return;
else
return forward(operation);
}),
new HttpLink({ uri }),
]);
Many of you might have heard of GraphQL. It provides QUERY and MUTATION. Also it supports SUBSCRIPTION as 100% replacement of web socket. I'm a big fan of GraphQL and hooks. Today I faced an issue with useSubscription which is a hook version of SUBSCRIPTION. When this subscription is fired, a React component is re-rendered. I'm not sure why it causes re-rendering.
import React from 'react'
import { useSubscription } from 'react-apollo'
const Dashboard = () => {
...
useSubscription(query, {
onSubscriptionData: data => {
...
}
})
render (
<>
Dashboard
</>
)
}
Actually useSubscription's API doc doesn't say anything about this re-rendering now. It would be really appreciated if you provide me a good solution to preventing the re-rendering.
Thanks!
Just put your subscription in separate component as this guy did, and return null, now your root component won't rerender
function Subscription () {
const onSubscriptionData = React.useCallback(
handleSubscription, // your handler function
[])
useSubscription(CHAT_MESSAGE_SUBSCRIPTION, {
shouldResubscribe: true,
onSubscriptionData
})
return null
}
// then use this component
<Subscription />
In my experience, there is no way to prevent re-render when receiving new data in onSubscriptionData. If your global data is used for calculations, you should use useMemo for you global variable. On the other hand, you should consider do you need put your variable in onSubscriptionData? Are there other ways? Did you use useSubscription in right component? If your have to do that, you have to accept extra rendering.
Hopefully, my answer is helpful for your situation.
When the user picks a value from a select dropdown, the onChange calls a function in the parent container. There I need to check which fields are filled.
But it appears that the value hasn't been set in the form yet.
I have tried to use the selectors valid and getFormvalues, but they are not updated with the latest form value change.
See this codesandbox.
If I print getFormvalues in the render of the form component, it is updated, but I can't handle the form there.
So how can I handle the form after the user picks a value, where the form is updated?
You are already receiving values in the SimpleFormContainer due to connect. You don't need the callback from SimpleFormContainer to SimpleForm. You can use the componentWillReceiveProps in the SimpleFormContainer to check the values update and do whatever you want.
The final code might look like:-
class SimpleFormContainer extends React.Component {
componentWillReceiveProps(nextValues) {
if (this.props.formValues !== nextValues.formValues){
console.log(nextValues.formValues)
}
}
render() {
return (
<SimpleForm onSubmit={showResults}/>
)
}
}
SimpleFormContainer = connect(
state => ({
formValues: getFormValues('simple')(state)
}),
{ getFormValues }
)(SimpleFormContainer);
Working example
I can't understand the actual use of mapDispatchToProps, mapStateToProps so please explain with example.
mapDispatchToProps and mapStateToProps, are two API's.
mapStateToProps provides the store's current state object, using this we can filter and use the required part of state. We can also provide ownProps parameter which contains Component's parent supplied arguments. e.g:
const mapStateToProps = (state, ownProps) => {
return state;
}
mapDispatchToProps lets functions available inside our component's. So with actionCreators we can supply functions that could be used directly without dispatching them inside our Component e.g:
const mapDispatchToProps = dispatch => {
return {
login1: bindActionCreators(login, dispatch)
}
}
here login supplied to bind function is imported as * from an actions fil. e.g import login as * from 'filename'
Now Connect is an another API which is given by react-redux which provides the real glue and make things working e.g:
export default connect(mapStateToProps,null,mapDispatchToProps)(Login)
here Login is the Component(Class) to which we need to map to.
Hope this might clear, the difference feel free to ask if any more dobuts
After I send fetch query to the graphql server, server side returns errors like this graphql response
But React component doesn't show errors in their props.
component props
I want to control how component run actions based on error code.
For example If component doesn't fetch any data, mustation action will run. And then I want to control it again based on fail or success.
class Room extends React.PureComponent {
render() {
if (this.props.data.loading) {
return (<div>Loading</div>)
}
if (this.props.errors.code == "bad_request") {
//redirect to form
}
if (this.props.errors.code == "not_found") {
// run mutaion query
}
return (
<div>
Room name is {room.length}
</div>
)
}
}
Does Anybody know how to access response value or any other best error handling?
You can access error in data.error object see this link for details
http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error