Updating model with GraphQL Mutation - reactjs

I have been having trouble figuring out how to update a User with graphQL. The functionality I'm currently aiming for is for the user to be able to update their account/profile information. I have some things set up for the user like a bio field for their profile, and a profile picture field that's set up to take a URL and display that as their profile picture.
I have no problems when it comes to creating using graphQL. A user can sign up, log in, make posts, etc without issue. I can also update the User in regards to other models, for example, a new post pushes to the users Post data just fine.
I have not been able to figure out how to update a user directly though. Essentially I can get around this by creating a new model for "profile pic" and pushing that to the User, but that seems like it's just extra steps that might slow things down, as well as shortchanging myself being able to learn something new.
This is the User model. I have omitted a few fields due to the exact block of code being large, but this includes the "image" and "bio" fields (the fields I would like to update) as well as the reference to the Post model which I mentioned above that functions appropriately.
User.js
const userSchema = new Schema(
{
username: {
type: String,
required: true,
unique: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
match: [/.+#.+\..+/, 'Must match an email address!']
},
password: {
type: String,
required: true,
minlength: 8
},
image: {
type: String
},
bio: {
type: String,
maxLength: 500
},
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
],
},
Below is the mutation in Explorer, including the variables and the result.
Profile Pic Resolver
addProfilePic: async (parent, { image }, context) => {
if (context.user) {
const updatedUser = await User.findOneAndUpdate(
{ _id: context.user._id },
{ image: image },
{ new: true, runValidators: true }
);
return updatedUser;
}
throw new AuthenticationError('You need to be logged in!');
},
typeDefs.js (relevant only)
type Mutation {
addProfilePic(_id: ID!, image: String!): Auth
}
I notice that in the Explorer page it returns "null" for user with a 200 status. I am led to believe that means that it's not able to even access the "image" field on the user to be able to update it. When compared to my other mutations in regards to users, this is set up very similarly and I'm not sure what the difference is.
I feel like I am missing something very basic here in regards to being able to update. I haven't been able to find an update mutation example that works. Could anyone assist? My main questions would be:
Why does the mutation return "null" for user?
How can I set up my resolver to appropriately update information on an already-created object?
Thank you to anyone who is able to take a look and assist, I will be closely watching this post for replies and will update any other code someone may need to be able to assist. I've been stuck in regards to updating information for a long time, but my site is getting to the point where it's nearly ready and I need to tackle this updating issue in order to progress. Thank you!
Quick Edit: I want to add that "Auth" is referenced. The appropriate authorization headers are in place to retrieve the data. Just wanted to add that in as I highly doubt authorization has anything to do with this!

I have solved this issue and would like to leave the answer here for anyone who may find it useful.
In the mutation typeDefs, I changed the "Auth" to "User",
type Mutation {
addProfilePic(_id: ID!, image: String!): User
}
and then in the mutation itself, took away the user field like such:
mutation addProfilePic($_id: ID!, $image: String!) {
addProfilePic(_id: $_id, image: $image) {
_id
username
image
}
}
This has allowed the user to update their profile photo information. Hope this helps!

Related

Upload react-pdf dynamically generated file to Sanity using NextJS

I'm working on an e-commerce app built on NextJS and Sanity, so far I've made some mock products with all the necessary requirements, a user login system and checkout. I've been trying to make an invoice system so that when the user confirms an order 3 things must happen:
send all the order data to a react-pdf component and generate the invoice(working)
post the invoice file to the sanity schema so that the user has access to it when he goes to his order history page(not working)
email both the company and the client about the order(not implemented yet but I can do it)
ReactPDF allows me to access the pdf through a hook that returns me the blob of the file and the URL. I've tried to POST both of them but the url returned 404 and the blob didn't upload at all.
Searched the docs of both ReactPDF and Sanity and I couldn't find anything, although I think it has to do something with this endpoint from Sanity:
myProjectId.api.sanity.io/v2021-06-07/assets/files/myDataset
This is how I POST the order to my sanity studio
const { data } = await axios.post(
'/api/orders',
{
user: userInfo,
invoice_id: orders.length + 1,
orderItems: cartItems.map((item) => ({
...item,
slug: undefined
})),
billingData,
paymentMethod,
itemsPrice,
taxPrice,
totalPrice
},
{
headers: {
authorization: `Bearer ${userInfo.token}`
}
}
);
I've tried making 2 POST requests, one for the invoice_file alone, trying to post the blob or the url but none did work. The schema for invoice file was updated for the type of post each time so I'm 99% sure that wasn't the issue, anyway here's how the schema for invoice_file looks as for file:
{
name: 'invoice_file',
title: 'Invoice',
type: 'file',
options: {
storeOriginalFilename: true
}
},
If there would be any other code snippets relevant please let me know.
I really don't know how to find the solution for this as it's the first time trying to do such thing, so help would be much appreciated.
Thanks in advance!
I apologies as I'm not really active here but it's hard to pass on your question especially as I'm working on something similar. There's probably other ways to do this but I suggest you work use the official Sanity client. There's a specific section in the README that tells us how to do the file uploads or here.
So here's kinda the very small snippet:
import {
Document,
pdf,
} from "#react-pdf/renderer";
const doc = <Document />;
const asPdf = pdf([]); // {} is important, throws without an argument
asPdf.updateContainer(doc);
const blob = await asPdf.toBlob();
// `blob` here is coming from your react-pdf blob
const fileName = "customfilename.pdf";
client.assets.upload("file", blob, { filename: fileName }).then((fileAsset) => {
console.log(fileAsset", fileAsset);
// you can then use the fileAsset to set and reference the file that we just uploaded to our document
client.patch("document-id-here").set({
invoice_file: {
_type: "file",
asset: {
_type: "reference",
_ref: fileAsset._id,
},
},
}).commit();
});

Attaching data from model to mutation with GraphQL

I'm currently working on a social media site using react, graphQL and Apollo.
I have an issue retaining a field from a User when making a post. With this being graphQL, there comes a host of areas that issues can arise from (typedefs, resolvers, mutations, queries, how they're called in the component, etc) and when trying to make this post I have had it become about 3 miles long as I cut and paste all of the various code from all of those files to try and get as much detail as I can. However, that ends up being an incredible amount of code to dig through to try to find the exact error, so I'm going to skip all of that and ask a simple question in the hopes that an answer can help me crack this without someone having to read a novels worth of text.
When a User makes a post, the username is saved in the post through the resolver like such:
addPost resolver
addPost: async (parent, args, context) => {
if (context.user) {
const post = await Post.create({ ...args, username: context.user.username });
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { posts: post._id } },
{ new: true }
);
return post;
}
throw new AuthenticationError('You need to be logged in!');
},
I recently finished up adding profile picture functionality to the site. A users profile image is saved to their model. I have updated the Post model to include a userImage field alongside the username field.
I attempted to retain the users profile image to attach to the post like so:
addPost resolver including image
addPost: async (parent, args, context) => {
if (context.user) {
const post = await Post.create({ ...args, username: context.user.username, userImage: context.user.image });
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { posts: post._id } },
{ new: true }
);
return post;
}
throw new AuthenticationError('You need to be logged in!');
},
However, when making a post, the userImage field comes back as null. Is there somewhere else to call that value in the resolver to retain that? If I drop the "username: context.user.username" from the resolver, the post returns an error saying the username is required. In testing, I made the userImage field a required field so that a post would not post without a userImage, and I receive the same error saying the userImage field is required, even with that updated resolver including the "userImage: context.user.image."
The image field on the Post model is "userImage" and on the user model it is just "image", so I believe the way it is set up in the resolver should work, but for some reason it will not take.
Main question: How can I retain this field?
Questions based on this not working: Am I only able to call one field in the resolver (that field being username)?
When I list the userImage field as not required, the post will post, and the userImage field in the response from graphQL just says "null".
I hope this is enough for someone to point me in the right direction. I will keep an eye on this post and would be happy to add any relevant code that anyone may need to assist. I have updated everything on the typedefs, mutations.js, and queries.js files to include images and userImages respectively.
Thank you to anyone who takes a look, I'm really trying to understand what may need to be done here!

How to design schema for nested data

I created a post Schema and I have trouble implementing the comment and comment reply schema since you can not predict how often one comment reply has it own reply.
I am using mongoose and express.
So how can I implement this type of schema design?
I think you're looking for something like this where you are referencing comments from within your comment schema.
I added a middleware to pre-populate the replies array when you call .find(). You can add more middleware for other calls like .findOne() etc.
const mongoose = require("mongoose");
const commentSchema = mongoose.Schema(
{
comment: {
type: String,
required: true
},
author: { // To reference the user that left the comment. If needed
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
replies:[{type: Schema.Types.ObjectId, ref: "Comment"}] // Array of comment replies
},
{
timestamps: true,
}
);
// Middleware to populate the replies when you call `find()`
commentSchema.pre('find', function() {
this.populate('replies');
});
module.exports = mongoose.model('Comment', commentSchema);
You can do more in-depth on this post which will show you how to pre-populate the replies field when returning comments etc.
https://www.makeschool.com/academy/track/standalone/reddit-clone-in-node-js/comments-on-comments

Posting to mongoDb with ObjectId Many to one relationship

Mongoose/MongoDB Question
I have an Owners model containing basic profile data.
I have a secondary model: OwnersImages
e.g
{
owner: {
type: Schema.Types.ObjectId,
ref: 'Owners'
},
name: String,
imageUrl: String,
},
);
From the client I want to post the imageUrl and the name to the OwnersImages table.
e.g
let values = {
owner: this.state.user._id,
name: this.state.field,
imageUrl: this.state.url
}
axios.post(`${serverPath}/api/addFieldImage`, values)
However Im unsure how best to go about this, link it etc.
I can do a GET request on the Owners table to get the Owner data, but then posting this as part of the values to OwnerImages doesn't successfully link the two tables.
Do i need to just store a string reference to the Owner id in OwnerImages or is there a smarter way of doing this?
Or should I just post the string of the user Id to mongoose and then do a map to the Owner table from within there?
Tried to explain this best way I could but the eyes are tired so please ask if any confusion!
Many thanks
Without seeing your exact setup, I think you could modify this to fit your needs:
// In the Schema/Model files
const ownersSchema = Schema({
// other fields above...
images: [{ type: Schema.Types.ObjectId, ref: 'OwnersImages' }]
});
const ownersImagesSchema = Schema({
// other fields above...
owner: { type: Schema.Types.ObjectId, ref: 'Owners' },
});
// in the route-handler
Owners.findById(req.body.owner, async (err, owner) => {
const ownersImage = new OwnersImages(req.body);
owner.images.push(ownersImage._id);
await ownersImage.save();
await owner.save();
});
As a side-note, I think the Models generally have singular names, so Owner and OwnerImage. The collection will then automatically take on the plural form. Just food for thought.
When you want to load these, you can link them with populate(). Consider loading all of the OwnersImages associated with an Owners in some route-handler where the /:id param is the Owners id:
Owners
.findOne({ _id: req.params.id })
.populate('images')
.exec(function (err, images) {
if (err) return handleError(err);
// do something with the images...
});

react-hooks query and setState causes 500 error

I think this could be a bug or a misconfiguration.
I get this error:
networkError: ServerError: Response not successful: Received status code 500
Submitting the form the first time I would get the desired result, but if I hit the submit button again, I get the networkError message above.
const client = useApolloClient();
const [val, setValu] = useState({
email: 'example#example.com',
password: 'password',
texterror: {
status: false,
text: ''
},
})
//the submit function below:
const handleLogin = async (e) => {
e.preventDefault();
await client.query({
query: Handle_Login,
variables: {
email: val.email,
password: val.password
}
}).then((e) => {
console.log(e)
}).catch((e) => {
setValu({
texterror: {
status: true,
text: 'it be broke yo'
}
})
})
}
However, if I remove setValu({texterror: {status: true, text: 'it be broke yo'}}) in the catch, The 500 error goes away. I could spam the submit button and I wouldn't receive the 500 error.
For right now I'm going to not have a setState inside just to avoid this problem, but I would like to know if there is a solution to this issue.
Any help would be appreciated. Thank you in advance!
At the top level, it's clear that you're causing some kind of error on the server side. A 500 code means there is an "internal server error". So you should really look at your server code and figure out how to safeguard against unexpected input so that it won't error and/or crash but instead return code 400 which is "bad request".
So the question becomes, what about your frontend code is causing a malformed server request?
I suspect that you're trying to use setValu() (which is a React hook) the same way one might use a traditional setState call in a class component. However, they behave quite differently.
In a class component, setState performs a "shallow merge" of the old state and the new state. So if you did:
setState({
texterror: {
status: true,
text: 'it be broke yo'
}
});
If would find only the field texterror on the state object and update that.
However, React hooks work differently. useState() creates a single atomic value, and when you call setValu() it complete replaces the previous value stored in val.
So:
const [val, setValu] = useState({
email: 'example#example.com',
password: 'password',
texterror: {
status: false,
text: ''
},
});
// val is now an object with 'email', 'password', and 'texterror' fields
setValu({
texterror: {
status: true,
text: 'it be broke yo'
}
});
// val is now an object with only a 'texterror' field
In your code when you are using setValu you are wholly replacing the value of val to something that doesn't have an email or password field, which when sent to the server causes an internal server error. A simple fix would be to simply merge the old value of val with the desired new object when you update it:
setValu({
...val,
texterror: {
status: true,
text: 'it be broke yo'
}
});
Remember that with the useState hook you are overwriting the entire state val. This means that you have no longer got a email and password. It is a common oversight when moving from class based components to hooks. Add the parts of the state that didn't change too, and it should all work again.

Resources