I'm trying to execute a Mutation in a project, where I have Appollo GraphQL as middleware and reactJS as a front-end.
I have the following schema (the idea is to send a contact form):
input entryIntput {
url_title: String!
title: String!
channel_id: Int!
entry_date: String!
name: String!,
motivation: String!
mail: String!
job_position: String
}
...
type Mutation {
createEntry(input: entryIntput): postEntrySuccess
}
Resolvers:
Mutation: {
createEntry: (_, { input }) => channelEntriesService.postEntry(input)
}
ChannelEntriesService:
url_title: 'Application Form Entry-',
title: 'Application Form Entry #',
entry_date: Date.now(),
channel_id: 3
}) {
const auth = await authenticate.auth()
const nextEntry = await this.getNextEntry();
const patch = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
url: `${this._options.url}/create_channel_entry`,
data: `channel_id=${entry.channel_id}&url_title=${entry.url_title}${nextEntry}&title=${entry.title} ${nextEntry}&name=${entry.name}&motivation=${entry.motivation}&job_position=${entry.job_position}&mail=${entry.mail}&entry_date=${entry.entry_date}&session_id=${auth.session_id}`
}
const response = await axios(patch);
return response.data;
}
I have the Queue execution via useMutation and declaration like:
const APPLY = gql`
mutation PostEntry($input: entryIntput) {
createEntry(input: $input) {
entry_id
}
}
`;
....
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY);
On the front-end, where I'm using React-Hook-Form:
const onSubmit = (applicantData) =>
createEntry({
variables: {
url_title: applicantData?.name?.trim(),
entry_date: Date.now(),
title: applicantData?.name,
...applicantData,
},
});
When I execute the submit I've got undefined for the entry param in my channelEntries
.postEntry(),
While I execute it through a mutation from the apollo studio like:
mutation applicationEntryForm($entryInput: entryIntput) {
createEntry(input: $entryInput) {
entry_id
}
}
query_variables:
{
"entryInput": {
"channel_id": 3,
"url_title": "my First Entry input",
"entry_date": "1623829177995",
"title": "Voila intruder!",
"name": "Vlad",
"job_position": "cleaner",
"motivation": "My motivation notes",
"mail": "vlado#vlado1.com"
}
}
it has the correct parameters and an entry is created successfully.
What I'm doing wrong in the front-end "useMutation" and the submit function where the variables need to be passed?
I think you very much in advance for reading this and giving me some tips as I'm really stacked :(
After renaming some of the variables as #xadm mentioned I found the following:
there is a difference when I use:
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY);
and
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY, {});
e.g. passing an empty object for the options parameter actually makes things happening.
Related
I'm trying to setup a Apollo 3 local state with react. For an unknown reason my query does not work when I try to read a custom type (isAuthenticated query works well). Can you please help me to understand I literaly explored all the documentation and the link on internet about local state management.
Thank you
cache.js
import { InMemoryCache, makeVar, gql } from '#apollo/client'
export const isAuthenticatedVar = makeVar(!!localStorage.getItem('token'))
export const userVar = makeVar("")
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isAuthenticated: {
read() {
return isAuthenticatedVar()
}
},
user: {
read() {
return userVar()
},
__typename: 'User',
}
}
}
}
})
export const IS_AUTHENTICATED = gql`
query IS_AUTHENTICATED {
isAuthenticated #client
}
`
export const GET_LOCAL_USER = gql`
query GET_LOCAL_USER {
user #client
}
`
typedej.js
import { gql } from '#apollo/client'
export const typeDef = gql`
extend type User {
id: ID!
name: String!
surname: String!
email: String!
password: String!
phone: String!
adress: Adress!
isAdmin: Boolean!
isCoach: Boolean!
isManager: Boolean!
}
extend type Query {
isAuthenticated: Boolean!
user: User!
}
`
export default typeDef
test.js
const login = (email, password) => {
const emailLower = email.toLowerCase()
client.query({
query: LOGIN,
variables: { email: emailLower, password: password }
})
.then(result => {
console.log(result)
localStorage.setItem('token', result.data.login.token)
localStorage.setItem('userId', result.data.login.user.id)
isAuthenticatedVar(true)
userVar({ ...result.data.login.user})
console.log('USER VAR SETUP')
///Console.log show that userVar contains an object with all User data
console.log(userVar())
var test
try{
//test return NULL object
test = client.readQuery({ query: GET_LOCAL_USER}).user
}catch(error) {
console.log(error)
}
console.log('FETCH QUERY userVar')
console.log(test)
navigate("/")
})
.catch(error => {
console.log(error)
})
}
Tried several ways but can't find how to resolve this. I don't understand why documentation don't explain how to use with Object Type
I am trying to create a my-posts page which will consist of all the posts made by the user.
User Schema
const userSchema = new Mongoose.Schema({
email: { type: String},
password: { type: String},
name: { type: String},
createdAt: { type: String},
posts: [{ type: Mongoose.Schema.Types.ObjectId, ref: 'Post'}]
},{
timestamps: true,
});
Posts Schema
const postSchema = new Mongoose.Schema({
name: { type: String},
category: { type: String},
userId: {type: Mongoose.Schema.Types.ObjectId, ref: "User"},
},{
timestamps: true
});
Creating a new post
handler.post(async(req, res) => {
await db.connect();
const newPost = new Post({
name: req.body.name,
category: req.body.category,
userId: req.body.userId
})
const userBy = await User.findById(userId)
const thePost = await newPost.save();
userBy.posts = user.posts.concat(post._id)
await user.save()
await db.disconnect();
});
export default handler;
Retrieve 'My Posts'
export async function getServerSideProps( {query} ) {
await db.connect();
const data = await User.findById(req.query.id).populate("vehicles").lean
await database.disconnect();
const userPosts = data.map(database.convertObj)
return {
props: {
userPosts
}
}
}
I'm not too sure how to pass the current logged in users _id to the getServerSideProps to then query the database for all the posts attached to that user in the posts array. If there is a better way to approach this please let me know or if you know what I am currently doing wrong, thanks.
This is an area where you might want to use a request from the client side to get the data, as suggested by the Nextjs docs rather than using getServerSideProps.
But if you really want to do it with SSR, you can use getServerSideProps and pass in data (like the userId) to getServerSideProps through the context parameter as either a query or a parameter.
If you want to do it as a query, you can get query as a prop, as you are already doing, or you can do it using a param like in this codesandbox example
I'm having trouble accessing data from Amplify's API Graphql, and it keeps returning
Server Error
Error: No current user
I've been following this tutorial: https://youtu.be/13nYLmjZ0Ys?t=2292
I know I'm signed into Amplify because if I go into different pages, I can grab user Auth and I can even display the SignOut button. But for whatever reason, I'm not sure why I'm getting this error
import { API } from "aws-amplify";
import { useRouter } from "next/router";
import { listActivations, getActivation } from "../../graphql/queries";
const Activation = ({ activation }) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading</div>;
}
return <div>{activation.title}</div>;
};
export default Activation;
export async function getStaticPaths() {
const SSR = withSSRContext();
console.log("static paths");
const activationData = await SSR.API.graphql({
query: listActivations,
});
console.log("activationData", activationData);
const paths = activationData.data.listActivations.items.map((activation) => ({
params: { id: activation.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext(); // added SSR, but still getting error
console.log("static props");
const { id } = params;
const activationData = await SSR.API.graphql({
query: getActivation,
variables: { id },
});
return {
props: {
activation: activationData.data.getActivation,
},
};
}
The console log static paths appears, and then after that, I get errors.
Do you think it has anything to do with my GraphQL schema?
type User #model #auth(rules: [{ allow: owner, ownerField: "username" }]) {
id: ID!
username: String!
email: String!
userType: UserType
}
type Activation
#model
#key(
name: "activationsByStudentId"
fields: ["student"]
queryField: "activationsByStudentId"
)
#auth(
rules: [
{ allow: groups, groups: ["Admin"] }
{ allow: owner }
{
allow: owner
ownerField: "studentId"
operations: [create, update, delete]
}
{ allow: private, operations: [read] }
{ allow: public, operations: [read] }
]
) {
id: ID!
studentId: ID!
title: String!
student: Student #connection(fields: ["studentId"])
teachers: [TeachersActivations] #connection(name: "ActivationTeachers")
}
Edit: I've also added User model to see if this could be a cause too.
Since both getStaticProps and getStaticPaths are called during build time, and on the server when fallback is equal to true, you need to configure Amplify for SSR (Server-Side Rendering). Make sure to take a look at SSR Support for AWS Amplify JavaScript Libraries.
The solution: first, configure Amplify for SSR:
Amplify.configure({ ...awsExports, ssr: true });
Then you need to use withSSRContext, and add the the authMode parameter. As quoted from the link above:
For example, take an AppSync GraphQL API that is backed by an identity provider such as Amazon Cognito User pools, Okto, or Auth0. Some GraphQL types may require a user to be authenticated to perform certain requests. Using the API class, the user identity will now automatically be configured and passed into the API request headers:
const SSR = withSSRContext();
const activationData = await SSR.API.graphql({
query: listActivations,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
Still, I couldn't figure out the issue why this can't work, so I decided to move my query into client-side
const [activation, setActivation] = useState(null);
const router = useRouter();
const { aid } = router.query;
useEffect(() => {
if (!aid) return;
async function activationDataFromClient() {
try {
const getActivationData = await API.graphql({
query: getActivation,
variables: {
id: aid,
},
});
setActivation(getActivationData.data.getActivation);
} catch (err) {
console.log("error fetching activation data: ", err);
}
}
activationDataFromClient();
}, [aid]);
I had the same problem. Changing the authMode to 'API_KEY' enabled it to work for me. See example below:
export async function getStaticPaths(context) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: listArticles,
authMode: 'API_KEY'
});
const paths = data.listArticles.items.map((article) => ({
params: { id: article.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: getArticle,
variables: {
id: params.id,
},
authMode: 'API_KEY'
});
return {
props: {
article: data.getArticle
}
}
}
What I am trying to do: using useMutation to submit an array of array to mongoDB.
What happened: it did successfully save in mongoDb and in the correct format BUT the 'result.data' of useMutation is not as expected, it's too weired to be used.
This is my array of array (of course, it started as an empty array and was pushed in as user adds attraction to the days in their itinerary).
let itinerary = [{placeIds: ["ChIJafBhcoVPqEcRb7rHEy3G0L8", "ChIJBRk3gUlOqEcR9oEBV-dqK5M"]},
{placeIds: ["ChIJx8Iw5VFOqEcRXUgfWxkmAaA", "ChIJSdWeck5OqEcReSTr3YfoSuE"]},
{placeIds: ["ChIJ1WCXcFJOqEcRBR_ICa3TemU"]}]
This is the graphql mutation:
const SUBMIT_ITINERARY = gql`
mutation submitItinerary(
$dayPlans: [DayPlanInput]
){
submitItinerary(
dayPlans: $dayPlans
){
id
dayPlans{
placeIds
}
createdAt
}
}
`
This is the apollo react hook useMutation:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
in my graphql playground I got this
So I thought in my frontend I would get something similar but instead I got this:
Why is that the case? I want to get a structure similar to what I got in graphql playground.
More information that might be helpful:
Here is my itinerary schema:
const itinerarySchema = new Schema({
city: String,
username: String,
createdAt: String,
dayPlans: [],
user: {
type: Schema.Types.ObjectId,
ref: 'users',
}
});
my type definitions:
type DayPlan {
placeIds: [String]!
}
type Itinerary {
id: ID!
dayPlans: [DayPlan]!
username: String!
createdAt: String!
}
input DayPlanInput {
placeIds: [String]
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getUsers: [User]
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
submitItinerary(dayPlans: [DayPlanInput] ): Itinerary!
}
and my submitItinerary resolver:
Mutation: {
async submitItinerary(_, {dayPlans}, context) {
const user = checkAuth(context);
//console.log(user);
if (dayPlans.length === 0){
throw new Error('Itinerary should not be empty');
}
const newItinerary = new Itinerary({
dayPlans,
user: user.id,
username: user.username,
createdAt: new Date().toISOString()
})
const submitted = await newItinerary.save()
return submitted;
}
}
The first variable of the update option in useMutation is the Apollo Cache itself not the mutation result. The mutation result can be found in the second parameter. Here you can find the API docs.
This should log the correct data:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(cache, result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
Also another way to access the mutation result is using the second element from the hook return value:
const [submitItinerary, { data, loading, error }] = useMutation(SUBMIT_ITINERARY)
I hope my question title is comprehensible enough. I could't really think of a better way to phrase it and the character count restrictions busted my balls.
The problem I'm having happens in a project I'm working on that uses React, Redux and packages like react-router-redux and redux-auth-wrapper.
For testing I'm using Karma test runner with Enzyme, Sinon and Chai. Within the tests I'm using fetchMock to mock any requests to the API I'm using. I bundle everything using Webpack.
The issue I'm facing is that the code I wrote (modified this starter template rather rigorously) works, but the tests I wrote for them don't produce correct results. The bit that's acting weird is when I dispatch an action to fetch a token from our authentication API.
The Redux action I'm dispatching is the following one:
export function fetchToken(data) {
return function(dispatch) {
dispatch(requestTokenCreate(data))
// Let's fetch this user a token, shall we?
var headers = new Headers()
headers.append("Content-Type", "application/json")
headers.append("Accept", "application/json")
let requestParams = {
method: 'POST',
headers: headers,
mode: 'cors',
cache: 'default',
body: JSON.stringify({
subdomain: data.subdomain,
login: data.username,
password: data.password
})
}
data = {
subdomain: data.subdomain,
username: data.username
}
var serverUrl = (__DEV__ || __TEST__) ? "http://subdomain.easyposonline.dev:3000/api/v1" : "https://subdomain.easyposonline.nl/api/v1"
serverUrl = serverUrl.replace("subdomain", data.subdomain)
return fetch(serverUrl + "/auth_tokens/create", requestParams)
.then(response => response.json())
.then(json => {
return dispatch(receiveTokenCreate(json, data))
}
)
}
}
The bit relevant to my question is this:
return fetch(serverUrl + "/auth_tokens/create", requestParams)
.then(response => response.json())
.then(json => {
return dispatch(receiveTokenCreate(json, data))
}
)
When I dispatch this action in my development code, it works just fine! This returns the following response:
{success: true, token: "a3PC6y6nx1f63o2b13YWz7LYUHdidYui"}
When the action is dispatched from the test in question, though, I get back the following response:
{type: 'RECEIVE_USER_TOKEN_CREATE', json: Object{success: true, token: 'qicXyn6BhYhyoBuA_SFoZtTDN'}, payload: Object{subdomain: 'test-teun', username: 'teun#easypos.nl', token: 'qicXyn6BhYhyoBuA_SFoZtTDN'}}
So instead of returning the proper response, the code returns what seems to be an entire Redux action. This leads to my test failing because I end up with a bit of a recursion in the code I'm testing:
{type: 'RECEIVE_USER_TOKEN_CREATE', response: Object{type: 'RECEIVE_USER_TOKEN_CREATE', json: Object{success: ..., token: ...}, payload: Object{subdomain: ..., username: ..., token: ...}}, payload: Object{subdomain: 'test-teun', username: 'teun#easypos.nl'}}
I'm at a loss here. Probably I'm overlooking something astonishingly simple, but been looking at this for a few hours now and starting to lose my frickin' mind!
The test file I wrote is the following, maybe I'm setting up a test the wrong way:
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// import nock from 'nock'
import 'isomorphic-fetch'
import fetchMock from 'fetch-mock'
import SignInView from 'routes/SignIn'
import * as constants from 'constants'
import { fetchToken, destroyToken } from 'actions/user'
import _ from 'underscore'
describe('(Route) SignIn', () => {
let _route = SignInView({})
// Auth token request
const authTokenCreateSuccessfulRequest = {
subdomain: 'test-teun',
username: 'teun#easypos.nl',
password: 'psv4teun'
}
const authTokenCreateFailedRequest = {
subdomain: 'test-teun',
username: 'teun#easypos.nl',
password: 'teun4psv'
}
const authTokenCreateSuccessfulResponse = {
type: constants.RECEIVE_USER_TOKEN_CREATE,
json: {
success: true,
token: 'qicXyn6BhYhyoBuA_SFoZtTDN'
},
payload: {
subdomain: 'test-teun',
username: 'teun#easypos.nl',
token: 'qicXyn6BhYhyoBuA_SFoZtTDN'
}
}
const authTokenCreateFailedResponse = {
type: constants.RECEIVE_USER_TOKEN_CREATE,
json: {
success: false,
reason: "Ongeldige gebruikersnaam of wachtwoord!"
},
payload: {
subdomain: 'test-teun',
username: 'teun#easypos.nl'
}
}
beforeEach(() => {
fetchMock.mock('http://test-teun.easyposonline.dev:3000/api/v1/auth_tokens/create', authTokenCreateSuccessfulResponse)
})
afterEach(() => {
fetchMock.restore()
})
it('Should return a route configuration object', () => {
expect(typeof _route).to.equal('object')
})
it('Should define a route component', () => {
expect(_route.path).to.equal('sign_in')
})
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
it('Should trigger RECEIVE_USER_TOKEN_CREATE action containing a token on a successful login attempt', () => {
//fetchMock.mock('http://test-teun.easyposonline.dev:3000/api/v1/auth_tokens/create', authTokenCreateSuccessfulResponse)
const expectedActions = [
{
type: constants.REQUEST_USER_TOKEN_CREATE,
payload: authTokenCreateSuccessfulRequest
},
authTokenCreateSuccessfulResponse
]
const store = mockStore({ user: { isFetching: false } })
return store.dispatch(fetchToken(authTokenCreateSuccessfulRequest))
.then((data) => {
console.log(store.getActions()[1])
// console.log(expectedActions[1])
expect(_.isEqual(store.getActions(), expectedActions)).to.equal(true)
})
})
})
Another thing I might note is that I had this test working yesterday but I modified the structure of my Redux state since then and seem to have broken something along the way... :( Looking back in my Git history did not help me with this annoying thing.
I hope someone can point me in the right direction here, it'd really help me and my peace of mind. Thanks a bunch in advance!
Sigh... I discovered my dumb error.
fetchMock.mock('http://test-teun.easyposonline.dev:3000/api/v1/auth_tokens/create', authTokenCreateSuccessfulResponse)
should be
fetchMock.mock('http://test-teun.easyposonline.dev:3000/api/v1/auth_tokens/create', authTokenCreateSuccessfulResponse.payload)