Relay Modern isn't sending network request to GraphQL Server - javascript

I just started looking at Relay Modern recently and creating a simple app with a GraphQL backend (which works perfectly fine when testing with GraphIQL). However, I'm running into problems with Relay not sending network requests to retrieve any data. I'm not 100% confident about the below code but I definitely would expect it to at least send a network request to http://localhost:3000/graphql, but the devtools don't show any such request (or server logs).
environment.js
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
const store = new Store(new RecordSource());
const network = Network.create((operation, variables) =>
fetch('http://localhost:3000/graphql', {
method: 'POST',
headers: {
// Add authentication and other headers here
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables,
}).then(res => res.json()),
})
);
const environment = new Environment({
network,
store,
});
export default environment;
App.jsx
import React, { Component } from 'react';
import { graphql, QueryRenderer } from 'react-relay';
import environment from '#utilities/environment';
class App extends Component {
render() {
console.log(this.props); // Empty object {} here
return (
<div>
Hello World!
</div>
);
}
}
const Query = graphql`
query AppQuery {
user(id: "u01") {
id
username
}
}
`;
const AppQuery = () =>
(<QueryRenderer
environment={environment}
graphql={Query}
variables={{}}
render={({ error, props }) => {
console.log(error, props); // Get (undefined, {}) here
if (error) {
return <div>{error.message}</div>;
} else if (props) {
return <App {...props} />;
}
return <div>Loading!</div>;
}}
/>);
export default AppQuery;
Am I missing something obvious? There are no console/webpack errors and the app renders properly, such as it is, but simply no GraphQL/Relay data. Thanks!

I think your environnement is just fine.
Few things that might help : You might want to create a FragmentContainer and setup/run Relay Compiler to generate the needed graphql files in order to Relay run your queries.
You probably want declare and collocate the data requirements with App through a FragmentContainer. You need a Fragment Container because your data is masked in App hence not available through props (see more here why it's masked).
You'll need to use createFragmentContainer() like this :
App = createFragmentContainer(
App,
graphql`
fragment App_users on User { // The fragment name should follow the convention <FileName>_<propName>, so maybe you might the App component to an another file.
user(id: "u01") {
id
username
}
}
`,
);
Modify the Query to :
graphql`
viewer {
...App_users_viewer
}
`
When done, you should be able to run the Relay Compiler and have graphql generated files

Related

How to properly load global variable with async values(Reactjs)?

I'm trying to solve this problem that I can't seem to solve with stripe's API's
So when creating a charge with their new version API they say that in the front end we should call
loadStripe('publishable Key',{'Connected account ID'})
and set that to a const.
now I dont undestand how are we supposed to get the ID that is stored somewhere say a database?
As a reference please look at this and here (In Step 3 ...).
What I'm currently doing is something like this
import React from "react";
import ReactDOM from "react-dom";
import { Elements } from "#stripe/react-stripe-js";
import { loadStripe } from "#stripe/stripe-js";
import CheckoutForm from "./CheckoutForm";
//btw I have set this to const and to let and none work
const stripePromise = fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
anything: window.sessionStorage.getItem("Variable Account")
//here store something that will tell what account to pull ID data from
})
})
.then(data => data.json())
.then(result => {
return loadStripe("KEY", { stripeAccount: result });
});
class App extends React.Component {
render() {
return (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
}
}
export default App;
but the const seems to not load correctly if one goes with the regular flow of the app say from
myapp.com/home
-> click
myapp.com/home/something
-> then
myapp.com/home/something/payment
stripe is not loading but one refreshes the browser now works but that tells me I'm doing maybe something wrong or I have to make the app refresh in 'componentDidMount()' maybe?
One can set it to be static but connected accounts can be many so if anyone can help me with this I would appreciate it
Generally, you'd want to have this account ID available in your app. But if you need to retrieve it, that's fine, but make sure the stripePromise is what you think it is. For example, I can make this work here with a simulated fetch call here: https://codesandbox.io/s/stripe-connect-w-resolve-wts34
Note that I'm managing the Promise explicitly:
const stripePromise = new Promise((resolve, reject) => {
fetch(...)
.then(data => data.json())
.then(result => {
resolve(
loadStripe(STRIPE_PUBKEY, { stripeAccount: "acct_xxx" })
);
});
});
The fact that you describe this breaking with navigation suggests you might be routing incorrectly. If this is a single page app, the navigation shouldn't cause the App component to re-render.

Page in React renders before the clients permissions are checked

I have executed a query in Javascript to fetch the data of the current user in my application. This query returns the current user's (the clients) data which is used to check the clients access permissions for various pages in my React application using Apollo Client and GraphQL.
However, for a page that only the administrator should have access to, whilst this query is taking place the page renders so that a user without administrator permissions can temporarily view the contents of the page. Once the permissions have been checked and it is known that the user does not have access, an error page is produced.
I would like this error page to be produced immediately so that none of the content can be viewed at all by clients who don't have permission.
// This is a currentUser.js file that is imported by various React components
// which uses the query to check permissions
import gql from 'graphql-tag';
export default apolloClient => apolloClient
.query({
query: gql`
query CURRENT_USER {
name
age
gender
permissions
}
`,
})
.then(({ data }) => ({ currentUser: data }))
.catch(() =>
({ currentUser: {} }));
// This is a AdministratorPage.jsx file that shouldn't render whilst
// permissions are checked
import currentUser from '../lib/currentUser';
import React, { Component } from 'react';
import { ApolloConsumer } from 'react-apollo';
class AdministratorPage extends Component {
render() {
return (
<ApolloConsumer>
{(client) => {
currentUser(client).then((data) => { ...}
Any ideas?
You should render loading page or some sort of loading indicator until the current user data is fetched. Try something like this in your render method:
<Query query={GET_CURRENT_USER}>
{({ data, loading, error }) => {
if (loading) return <Loading />;
if (error) return <p>ERROR</p>;
return (
<AdministratorPage />
);
}}
</Query>

Next.js - understanding getInitialProps

I have an app that uses next.js along with Apollo/ Graphql and i'm trying to fully understand how the getInitialProps lifecycle hook works.
The lifecycle getInitialProps in my understanding is used to set some initial props that will render server side for when the app first loads which can be used prefetch data from a database in order to help SEO or simply to enhance page load time.
My question is this:
Every time I have a query component that fetches some data in my
components across my app, do I have to use getInitialProps to be
sure that data will be rendered server side?
My understanding is also that getInitialProps will only work in the page index components (as well as in _app.js), this would mean that any component lower down in the component tree would not have access to this lifecycle and would need to get some initial props from way up at the page level and then have them passed down the component tree. (would be great if someone could confirm this assumption)
Here is my code:
_app.js (in /pages folder)
import App, { Container } from 'next/app';
import { ApolloProvider } from 'react-apollo';
class AppComponent extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<Component client={client} {...pageProps} />
</ApolloProvider>
</Container>
);
}
}
export default AppComponent;
Index.js (in /pages/users folder)
import React, { PureComponent } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
const USERS_QUERY = gql`
query USERS_QUERY {
users {
id
firstName
}
}
`;
class Index extends PureComponent {
render() {
return (
<Query query={USERS_QUERY}>
{({data}) => {
return data.map(user => <div>{user.firstName}</div>);
}}
</Query>
);
}
}
export default Index;
The answer is NO
If you use Apollo with Next JS you will not have to use getInitialProps on each page to get some initial data rendered server side. The following configuration for getInitialProps is enough for all the components to render out with their respective queries if they have <Query> components in them
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
My issue and why I wasnt seeing any server side rendering is that Heroku or Now wouldnt perform SSR with a public URL ie my-app.heroku.com. To resolve this I purchased and applied a custom URL in Heroku and it worked. Along with a custom URL I had the following configuration in my Apollo config
const request = (operation) => {
operation.setContext({
fetchOptions: {
credentials: 'include'
},
headers: { cookie: headers.cookie }
});
};
This completely resolved it and now I have SSR without the pain of having to manually set getInitialProps on each page
Hope this helps someone

Unrecognized arguments in graphql mutation

I'm curretly following this tutorial on Meteor/Apollo/GraphQL, and having huge troubles to make a mutation with arguments/variables. Here is my code and some notes at the bottom !
The code
Schema
type Resolution {
_id: String!
name: String!
}
type Mutation {
createResolution(name: String!): Resolution
}
Resolver
import Resolutions from './resolutions'
export default {
Query: {
resolutions() {
return Resolutions.find({}).fetch()
}
},
Mutation: {
createResolution(obj, args, context) {
console.log('hey i get here')
}
}
}
The component using the mutation
import React, { Component } from 'react'
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'
const createResolutionQuery = gql`
mutation createResolution($name: String!) {
createResolution(name: $name) {
_id
}
}
`
class ResolutionForm extends Component {
submitForm = () => {
this.props
.createResolution({
variables: {
name: this.name.value
}
})
.then(d => console.log('data received'))
.catch(e => console.log(e))
}
render() {
return (
<div>
<input type="text" ref={input => (this.name = input)} />
<button onClick={this.submitForm}>Submit</button>
</div>
)
}
}
export default graphql(createResolutionQuery, {
name: 'createResolution'
})(ResolutionForm)
What i know
When i try to send my query to the server, i get an http 400 error, and i get the following graphql error : "Unknown argument "name" on field "createResolution" of type "Mutation"."
The createResolution is available in my graphiQL but does not show any arguments in the doc.
It's stipulated in the tutorial that changing the .graphql schema does not trigger meteor server reloading, to apply change i have to modify my "register-api" file which is responsible for making the executable schema and create the apollo server with it. I made fake change to trigger it but it did not changed anything.
I tried to relaunch the server after erasing my browser's cache with no result.
So I think my problem is with the mutation arguments (brilliant deduction I know), but I can't figure out where is the typo or where I'm missing something. Help from somebody with a fresh look is welcome, thanks :)
Edit
Reinstall npm packages solved the issue.
All looks good I made a small change and added it as a pull request to your github repo.
createResolution(obj, {name}, context) {
console.log('hey i get here')
const id = Resolutions.insert({
name,
})
return Resolutions.findOne(id)
}
Running on my machine I get no errors.

React Native: Why Does GraphQL Query With Apollo Return Undefined?

I'm currently trying to take a GraphQL query and using Apollo, display the result on my React Native app.
Here is the relevant code in App.js:
import {LoginScreen} from './src/Screens'
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const myLink = new HttpLink({
uri: 'http://localhost:5000/graphql',
});
const client = new ApolloClient({
link: myLink,
cache: new InMemoryCache()
});
export default class App extends React.Component{
render() {
return(
<ApolloProvider client={client}>
<LoginScreen/>
</ApolloProvider>
)}
}'
And here is the relevant code in LoginScreen.js
function ShowUser({data: { loading, otsUserByUserId }}) {
if (loading) {
return <Text>Loading</Text>;
} else {
console.log("Ots user is " + otsUserByUserId)
console.log("Data: " + data)
return (
<View>
{otsUserByUserId.map(user =>
<Text>The name of user is {user.firstName} {user.lastName}.</Text>
)}
</View>
);
}
}
export default graphql(gql`
query otsUser{
otsUserByUserId(userId:1) {
firstName
lastName
}
}
`)(ShowUser)
My query works in GraphiQL as you can see:
And just to show that I'm using the correct endpoint for my link:
When running this, in my debugger, I see
This shows that data is undefined and it's a networking error. So I must be doing something wrong on my setup on the front end. In some way, I am not using Apollo correctly. It seems pretty obvious that the error is in my App.js in how I define client, but I haven't been able to get anything to work for a while now, and it's driving me nuts. I can't find any SO posts on this.
I've gone through the Apollo docs multiple times and have been trying to just get something to work for a few days now. Any help is much appreciated. Thank you for your time.
The problem is localhost only means something to your computer, but means nothing to your react native app since the server is not running on it. Try changing localhost to your computer's IP address. That should hook you up.
const myLink = new HttpLink({
uri: 'http://{COMPUTER_IP_ADDRESS_HERE}:5000/graphql',
});
Update: 11/21
Below is to deal with your additional questions left in the comments.
If you have a better idea, please do not hesitate to tell me and I will try it.
Why can't I just do console.log(props.data)?
1. How I've done it
Here's a copy/paste of a working login page from one of my demo/toy apps. I know it works. It basically does the same thing you're attempting with a couple nice additions (e.g. managed form state, passing vars to the HOC, localstorage caching). You should be able to get what you need from it and adapt it to your use case easily.
// LoginScreen.js
import React from 'react';
import { gql, graphql } from 'react-apollo';
import { Button, Input } from 'rebass';
import { AUTH_TOKEN_KEY } from '../config';
class Login extends React.Component {
state = {
form: {
email: '',
password: ''
},
submitting: false
};
handleSubmit = evt => {
evt.preventDefault();
this.setState({
submitting: true
});
const { email, password } = this.state.form;
this.props
.signinUser({ variables: { email, password } })
.then(res => {
window.localStorage.setItem(AUTH_TOKEN_KEY, res.data.signinUser.token);
window.location = '/';
})
.catch(err => console.error(err));
};
handleChange = evt => {
const { name, value } = evt.target;
this.setState({
form: {
...this.state.form,
[name]: value
}
});
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<h1>Login</h1>
<Input
type="text"
name="email"
label="Email"
value={this.state.form.email}
// baseRef={ref => ref.focus()}
onChange={this.handleChange}
/>
<Input
type="password"
name="password"
label="Password"
value={this.state.form.password}
onChange={this.handleChange}
/>
<Button>Login</Button>
</form>
);
}
}
const signinUser = gql`
mutation($email: String!, $password: String!) {
signinUser(email: { email: $email, password: $password }) {
token
}
}
`;
export default graphql(signinUser, { name: 'signinUser' })(Login);
2. console.log(props.data)
You should be able to log this. Not sure what you're seeing, but I'm assuming it's something like [Object] from your description. If that's true, try this console.log('props.data %j', props.data) which will convert props.data to json if possible. You can also try console.log({props}) to see the entire props tree. If neither works the way you want, then you probably have something else going on here.
If you have more questions, you should probably open new stack overflow questions. These are really meant to just be one-to-one kinds of things, one question, one answer.

Categories