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.
Related
I'm working on a next JS, React, Apollo, graphQL, faunaDB App. I am trying to architect how a form builder would work with it's mutations to fauna via graphQL. I am able to run mutations from the playground, and can query from the front end and build my form. Interaction seen here https://www.loom.com/share/7f7d1e1231d445f2be6b5db2c81239b6
Now I am pretty sure I can figure out how to run a mutation on the front end, my concern is when to run it? See I have the following code. Which queries faunaDB, and outputs form input elements from the state (which is populated by the query) (currently just ones of the type text) and allows you to add new form input types to the state causing a rerender and displaying a new form input element. This is all well and good.
import { useQuery, gql } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const Home = () => {
const { loading, error, data } = useQuery(INPUT_VALUES);
const [formState, setFormState] = useState(undefined);
useEffect(() => {
setFormState(data?.allFormInputVals?.data);
}, [data]);
const addInput = () => {
const blanktext = {
__typename: "FormInputType",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
console.log(formState);
setFormState([...formState, { ...blanktext }]);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<form>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name-${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`Name #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
/>
<label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select>
</>
)}
</div>
);
})}
</form>
</>
);
};
export default Home;
However my mutation options in the playground seem to be limited to only adding one what is called a document at a time, which is one object. If I console log my state that I want to add to the db it looks as follows. Now I can do a mutation in the playground where I can add one of these objects. But I want to be able to add all of them at once. On a save operation. I want to do this because I dont want to run a request of every addition to the form, I want to use react state to handle the form until the very end and then do my db request.
[
{
"__typename":"FormInputVal",
"name":"name",
"_id":"291541872966369805",
"type":"text"
},
{
"__typename":"FormInputVal",
"name":"name",
"_id":"291541888089981453",
"type":"text"
},
{
"__typename":"FormInputVal",
"name":"Product Image",
"_id":"255f95e0-bff1-4e75-81fc-d6f3f9a72446",
"type":"text"
}
]
Now I have created a graphQL schema the has Users, which can have many Forms, and Forms which can have many inputs. It looks like so. The #relation directive is specifice to faunaDB. It works how I expect it to, beyond this mutation issue I have been mentioning.
type Form {
name: String!
index: Int!
user: User
formInputVals: [FormInputVal!] #relation
}
type FormInputVal {
name: String!
index: Int!
type: String!
formRoot: Form!
}
type User {
name: String!
email: String!
password: String!
forms: [Form] #relation
}
type Query {
allForms: [Form!]
allUsers: [User!]
allFormInputVals: [FormInputVal!]
}
See I can mutate the DB with the following. Where I select a specific form and add a input, thus causing a rerender of the frontend and the form input shows up. This is all well and great. This is an example muation of that type.
mutation{
createFormInputVal(data:
{formRoot:{connect:"291541554941657608"},name:"name",type:"text",index:0}){
name
type
index
formRoot{
name
}
}
}
But here is where the root of the problem lies.
I want to take that state created by react and add it to a faunaDB collection which is called formInputVal the graphql schema is mapped to the db collections.
I talked to Fauna support and they mentioned a #resolver directive where I can run a DB function and add multiple documents(objects) at once so far the lambda function syntax for faunaDB is above my understanding. They mentioned this article for the function https://docs.fauna.com/fauna/current/tutorials/ecommerce#function and this one for the resolver https://forums.fauna.com/t/placing-an-index-on-a-field-in-an-embedded-type/778/4
Let's clarify,
Am I approaching this right? I am open to changing the schema. What would you do if you wanted to solve this problem with alternative approches or the same approach, but with a missing piece I don't understand.
Why can't I just pass an array of objects to the mutation for the correct formID, and it add's that many document's to the collection in one query. Is there any sort of general pratice for creating a generative form like this.
Ok thanks any help ahead of time.
UPDATE:
I have tried the following mutation but it does not work. It haults with the following error Unknown argument 'formInputVal' on field 'createFormInputVal' of type 'Mutation'
const ADD_INPUT_VALUES = gql`
mutation AddInputValues($formInputVal: FormInputValInput!) {
createFormInputVal(formInputVal: $formInputVal) {
name
}
}
`;
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
...
<form
onSubmit={async (e) => {
e.preventDefault();
const res = await createFormInputVal({
variables: formState,
}).catch(console.error);
console.log(res);
}}
>
...
If the root of the issue here is mutating or creating multiple documents at once, I think it got duplicated here:
https://stackoverflow.com/a/68930202/534056
A couple of things to watch out for:
Choose a name for your custom inputs that does not equal [type name] + Input. This is what Fauna uses for the auto-generated CRUD operations, and if you define one yourself the it will override the generated one. For example, given the type Form, Fauna will generate an input type called FormInput, so don't use that for a custom one.
If you want to pass an array of data as input, specify the argument as a List type (placed in square brackets).
If you are updating some documents, you will need to pass in the ID. The GraphQL API hides some details around Refs, so in your UDF, you will need to reconstruct the ref from an ID.
Todo never gets added. Visibility Filter works & remote Coinbase fetch works.
Here's my repo 👇
https://github.com/deadcoder0904/apollo-coinbase
& here's the same code sandbox 👇
https://codesandbox.io/s/github/apollographql/apollo-link-state/tree/master/examples/todo
The example is similar to the codesandbox one with just Remote URI for a fetch from coinbase APIs for some coins
I’ve copied the same example
Just 2 changes
1st change is I added a HTTPLink for fetching Remote Data using Coinbase API
2nd change is I didn’t use Apollo-Boost & explicitly imported everything else
I don't understand the difference bcz the code is literally similar (welp they are using apollo-boost & i have used every single chunk differently & gobbled together)
The addTodo mutation is not working in TodoForm.js personally :)
TodoForm.js
import React from "react";
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
const ADD_TODO = gql`
mutation addTodo($text: String!) {
addTodo(text: $text) #client {
id
}
}
`;
const TodoForm = () => (
<Mutation mutation={ADD_TODO}>
{addTodo => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) return;
addTodo({ variables: { text: input.value } });
input.value = "";
}}
>
<input
type="text"
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
);
}}
</Mutation>
);
export { TodoForm };
The error is -
[Network error]: TypeError:
Object(WEBPACK_IMPORTED_MODULE_0_graphql_tag["gql"]) is not a
function. (In
'Object(WEBPACK_IMPORTED_MODULE_0_graphql_tag["gql"])(_templateObject)',
'Object(WEBPACK_IMPORTED_MODULE_0_graphql_tag["gql"])' is an
instance of Object) on App.js:46
It thinks gql is something other than a function (probably undefined). Should be easy enough to print it out and see. I wouldn't be surprised if you have a bad version of graphql-tag. Set it to a fixed version in package.json
Found the solution. I guess I have to get used to reading the logs properly.
I made one small mistake in ./resolvers/todos file & everywhere else it was fine
I actually imported gql as a named export like -
import { gql } from 'graphql-tag';
when I was supposed to use default export like -
import gql from 'graphql-tag';
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.
Im developing a React Native app using Relay modern, GraphQL, and Graphcool. I'm trying to fetch the posts from the DB, and I have 3 files, Post.js, PostList, and the index.js.
PostList.js:
export default createFragmentContainer(PostList, graphql`
fragment PostList_viewer on Viewer {
allPosts(last: 100, orderBy: createdAt_ASC) #connection(key: "PostList_allPosts", filters: []) {
edges {
node {
...Post_post
}
}
}
}
`)
Post.js
export default createFragmentContainer(Post, graphql`
fragment Post_post on Post {
id
content
createdAt
author {
id
username
}
}
`)
index.js
const Feed = () => (
<QueryRenderer
environment={environment }
query={AllPostQuery}
render={({ error, props }) => {
if (error) {
return <div>{error.message}</div>
} else if (props) {
return <PostList viewer={props.viewer} />
}
return <Text>Loading</Text>
}}
/>
)
When I console log this.props.viewer.allPosts inside PostList.js I get { edges: [ null, null, null ], .... I have 3 posts in the DB so it's finding the posts, but why are they null? Pl
first of all if you run into problems try to log the "root" object which is props not a branch inside of it - this will make easier for you to undestrand data structure.
Also, as far as I know Relay does some magic which makes data available for you only in files where you defined a fragment, so in PostList.js you will not see details of the post. Try printing data from Post.js. That's possibly the reason why you see null values printed.
Also data from GraphQL be will be available for you under variable name derived from fragment name after _ (underscore), so in your example it will be post not allPosts.
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