When creating listeners with graphql/subscriptions - they return errors - Variable 'owner' has coerced NULL value for NonNull Type String.
Its a simple schema along with simple code in Component did mount.
type Note @model @auth(rules: [{allow: owner}]) {
# type Note @model {
id: ID!
note: String!
}
` componentDidMount() {
this.getNotes();
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote)).subscribe({
next: noteData => {
console.log("Inside Create Note Listener");
console.log(noteData);
const newNote = noteData.value.data.onCreateNote;
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote];
this.setState({ notes: updatedNotes});
},
error:(error) => {console.log('MY subscribe error',error)}
});
this.deleteNoteListener = API.graphql(graphqlOperation(onDeleteNote)).subscribe({
next: noteData => {
console.log("Inside Delete Note Listener");
console.log(noteData);
const deletedNote = noteData.value.data.onDeleteNote;
const updatedNotes = this.state.notes.filter(note => note.id !== deletedNote.id);
this.setState({notes: updatedNotes});
},
error:(error) => {console.log('MY subscribe error',error)}
});
this.updateNoteListener = API.graphql(graphqlOperation(onUpdateNote)).subscribe({
next: noteData => {
console.log("Inside update Note Listener");
console.log(noteData);
const { notes } = this.state;
const updatedNote = noteData.value.data.onUpdateNote;
const index = notes.findIndex(note => note.id === updatedNote.id);
const updatedNotes = [
...notes.slice(0, index),
updatedNote,
...notes.slice(index+1)
];
this.setState({notes: updatedNotes, note: "", id: ""});
},
error:(error) => {console.log('MY subscribe error',error)}
});
};`
Console output shows:
MY subscribe error
{…}
​
data: null
​
errors: (1) […]
​​
0: Object { path: null, locations: (1) […], message: "Variable 'owner' has coerced Null value for NonNull type 'String!'" }
​​
length: 1
​​
<prototype>: Array []
​
<prototype>: Object { … }
App.js:29
MY subscribe error
Object { data: null, errors: (1) […] }
App.js:59
MY subscribe error
Object { data: null, errors: (1) […] }
App.js:40
GEThttp://localhost:3000/logo192.png
GEThttp://localhost:3000/favicon.ico
GEThttp://localhost:3000/sockjs-node/982/2krmre4f/websocket
[HTTP/1.1 101 Switching Protocols 4ms]
./src/App.js
Line 7:10: 'GRAPHQL_AUTH_MODE' is defined but never used no-unused-vars backend.js:6:2315
<!-- Please run the following command inside your project and copy/paste the output into the codeblock: -->
npx envinfo --system --binaries --browsers --npmPackages --npmGlobalPackages
```
System:
OS: macOS Mojave 10.14.6
CPU: (6) x64 Intel(R) Core(TM) i5-8500B CPU @ 3.00GHz
Memory: 78.31 MB / 8.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.13.0 - /usr/local/bin/node
npm: 6.13.1 - /usr/local/bin/npm
Browsers:
Chrome: 78.0.3904.108
Firefox: 71.0
Firefox Developer Edition: 71.0
Safari: 13.0.3
npmPackages:
aws-amplify: ^1.2.4 => 1.2.4
aws-amplify-react: ^2.5.4 => 2.5.4
react: ^16.12.0 => 16.12.0
react-dom: ^16.12.0 => 16.12.0
react-scripts: 3.2.0 => 3.2.0
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
Sample code
Include additional sample code or a sample repository to help us reproduce the issue. (Be sure to remove any sensitive data)
_You can turn on the debug mode to provide more info for us by setting window.LOG_LEVEL = 'DEBUG'; in your app._
Hi @cpalanzo
I found this excerpt in our docs for \@auth - Authorizing Subscriptions:
When @auth is used subscriptions have a few subtle behavior differences than queries and mutations based on their event based nature. When protecting a model using the owner auth strategy, each subscription request will require that the user is passed as an argument to the subscription request. If the user field is not passed, the subscription connection will fail. In the case where it is passed, the user will only get notified of updates to records for which they are the owner.
A couple of paragraphs below, you'll see this section:
Note that if your type doesn’t already have an owner field the Transformer will automatically add this for you. Passing in the current user can be done dynamically in your code by using
Auth.currentAuthenticatedUser()
in JavaScript, AWSMobileClient.default().username in iOS, or AWSMobileClient.getInstance().getUsername() in Android.
So, my suggestion would be to try your subscriptions like this:
API.graphql(
graphqlOperation(onCreateNote,
{
owner: await Auth.currentAuthenticatedUser()
}
)
).subscribe(...)
I hope this helps!
Thank you - I'm a newbie taking a course - which does not show the owner being added as you show - but logically it makes sense the subscription should require the owner. However - when I add the code to my code I get:
Line 21:17: Parsing error: Can not use keyword 'await' outside an async function
I'm sure my code is wrong - but not sure where.
Here is the whole Listener routine again - any ideas?
componentDidMount() {
this.getNotes();
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote,
{owner: await Auth.currentAuthenticatedUser()})).subscribe({
next: noteData => {
console.log("Inside Create Note Listener");
console.log(noteData);
const newNote = noteData.value.data.onCreateNote;
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote];
this.setState({ notes: updatedNotes});
},
error:(error) => {console.log('MY subscribe error',error)}
});
When you use await
the function that is encapsulating the code should be async
. More info here
async componentDidMount() {
this.getNotes();
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote,
{owner: await Auth.currentAuthenticatedUser()})).subscribe({
next: noteData => {
console.log("Inside Create Note Listener");
console.log(noteData);
const newNote = noteData.value.data.onCreateNote;
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote];
this.setState({ notes: updatedNotes});
},
error:(error) => {console.log('MY subscribe error',error)}
});
Hope that helps
This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.
@cpalanzo I also had same problem in the course.
I figured it out by creating getUser function and use promise for subscription
here is a code for you.
import { API, Auth, graphqlOperation } from "aws-amplify";
...
useEffect(() => {
...
const createNoteListener = getUser().then(user => {
return API.graphql(
graphqlOperation(onCreateNote, { owner: user.username })
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevState => {
const prevNotes = prevState.filter(item => item.id !== newNote.id);
return [...prevNotes, newNote];
});
}
});
});
...
}
const getUser = async () => {
const user = await Auth.currentUserInfo();
return user;
};
const getUser = async () => {
const user = await Auth.currentUserInfo();
return user;
};
Thanks guys for the answers. This helped me get past this error. Just to make this easier to find in Google, here is the name of the book / video series that this fix works with: _Serverless React with AWS Amplify - The Complete Guide_. It is for _Chapter 6: Real-time Data with GraphQL Subscriptions_.
Here is the code I used in each subscription listener. Once you add the @auth directive to your model, the subscription listeners all expect the username. You don't need to modify the list, add or update graphql, just the subscriptions.
Mine is slightly different than the solution from @genk1. His code is perfect for Chapter 7 when you refactor the entire app to React Hooks.
Below is how my code looked after Chapter 6. All of my calls to get the username are inlined. It requires you to wrap the await function call in parenthesis and then call the username attribute. Not wrapping the await Auth.currentUserInfo()
call in parenthesis will result in undefined
being returned when checking the username attribute because you have to wait for the function to return the user before you check the username attribute. (something that took me a few minutes to figure out). :-)
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote,
{
owner: (await Auth.currentUserInfo()).username
}
))
.subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote];
this.setState({ notes: updatedNotes, note: "" });
}
});
In the answer by @genk1, i.e. after chapter 7 when we have converted everything to hooks, how can we unsubscribe from the subscription? Simply doing createNoteListener.unsubscribe()
doesn't work, as createNoteListener is a Promise
now instead of an Observer
. Following is the code I have as of now, and it's not working properly.
useEffect(() => {
getNotes();
const createNoteListener = getUser().then(user => {
return API.graphql(graphqlOperation(onCreateNote, {owner: user.username })
).subscribe({
next: value => {
setNotes(prevNotes => [value.value.data.onCreateNote, ...prevNotes]);
}
})
})
const updateNoteListener = getUser().then(user => {
return API.graphql(graphqlOperation(onUpdateNote, {owner: user.username})
).subscribe({
next: value => {
const updatedNote = value.value.data.onUpdateNote;
setNotes(prevNotes => {
const index = prevNotes.findIndex(note => note.id === updatedNote.id)
return [...prevNotes.slice(0, index), updatedNote, ...prevNotes.slice(index + 1)]
})
}
})
})
const deleteNoteListener = getUser().then(user => {
return API.graphql(graphqlOperation(onDeleteNote, {owner: user.username})
).subscribe({
next: value => {
setNotes(prevNotes => {
return prevNotes.filter(note => note.id !== value.value.data.onDeleteNote.id)
})
}
})
})
return () => {
createNoteListener.unsubscribe();
updateNoteListener.unsubscribe()
deleteNoteListener.unsubscribe()
}
}, [])
I am pushing the subscriptions into an array and then resolving that in the return:
useEffect(() => {
const subs = [];
async function fetchData() {
try {
const result = await API.graphql(graphqlOperation(queries.listBuildDefinitions, {limit: 999}));
setBuildDefinitions(result.data.listBuildDefinitions.items)
} catch (error) {
console.error(error);
}
const user = await Auth.currentUserInfo();
console.info("User : "+ JSON.stringify(user));
const username = user.username;
const insertSubscription = await API.graphql(graphqlOperation(subscriptions.onCreateBuildDefinition, {owner: username})).subscribe({
next: (eventData) => {
const buildDefinition = eventData.value.data.onCreateBuildDefinition
setBuildDefinitions(buildDefinitions => [...buildDefinitions, buildDefinition])
}
})
subs.push(insertSubscription);
const deleteSubscription = await API.graphql(graphqlOperation(subscriptions.onDeleteBuildDefinition, {owner: username})).subscribe({
next: (eventData) => {
setBuildDefinitions(buildDefinitions => buildDefinitions.filter(item => item.id !== eventData.value.data.onDeleteBuildDefinition.id));
}
})
subs.push(deleteSubscription);
}
fetchData();
return () => {
subs.forEach(function(item, index, array){
item.unsubscribe();
})
}
}, [])
@kreuzhofer is setBuildDefinitions an apache function?
Anyone coming from _Serverless React with AWS Amplify - The Complete Guide_ and is stuck on _Chapter 15.4: Subscribing to Product Mutations_: The subscription expects a user ID, so you can fix each subscription in a similar vein to the solutions above, by setting _owner_ to user ID for each listener:
componentDidMount() {
const { user } = this.props;
...
this.createProductListener = API.graphql(
graphqlOperation(onCreateProduct, {
owner: user.attributes.sub,
})
).subscribe({
...
}
Most helpful comment
Thanks guys for the answers. This helped me get past this error. Just to make this easier to find in Google, here is the name of the book / video series that this fix works with: _Serverless React with AWS Amplify - The Complete Guide_. It is for _Chapter 6: Real-time Data with GraphQL Subscriptions_.
Here is the code I used in each subscription listener. Once you add the @auth directive to your model, the subscription listeners all expect the username. You don't need to modify the list, add or update graphql, just the subscriptions.
Mine is slightly different than the solution from @genk1. His code is perfect for Chapter 7 when you refactor the entire app to React Hooks.
Below is how my code looked after Chapter 6. All of my calls to get the username are inlined. It requires you to wrap the await function call in parenthesis and then call the username attribute. Not wrapping the
await Auth.currentUserInfo()
call in parenthesis will result inundefined
being returned when checking the username attribute because you have to wait for the function to return the user before you check the username attribute. (something that took me a few minutes to figure out). :-)