Pushing data to an array in already existing object with axios - reactjs

i have a object which looks like this:
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
}
to an characters array i want to add the id of characters.
I do it by form and then i handle submit like this:
const handleSubmit = (values) => {
console.log("dodano aktora do filmu!");
console.log(values);
addActorToMovie(values);
history.goBack();
};
the addActorToMovie action:
export const addActorToMovie = (resp) => ({
type: types.ADD_CHAR_TO_MOVIE,
payload: resp,
});
and the reducer:
case types.ADD_CHAR_TO_MOVIE:
console.log(action.payload);
return {
...state,
...state.episodes.map(function (item) {
return item.id === action.payload.episodeId
? {
id: item.id,
title: item.title,
release_date: item.release_date,
series: item.series,
img: item.img,
characters: [...item.characters, action.payload.actor],
}
: { ...item };
}),
};
It all works, but the problem is that i dont want to do it loccaly. Im using an database with json-server, and I want to do an Axios Request so that it would add a data to the database.
And i don't know how to do this, when i use axios.post it adds an object to my episodes array, if im using axios.put it changes an object. Is there any possibility to push the data to an array as i do it with the code above, but with axios so that it would be added to database?
My approach looked like this:
export const addActorToMovieAxios = (value) => {
console.log(value);
return async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:3000/episodes/`,
value
);
console.log(response);
dispatch(addActorToMovie(response.data));
} catch (ex) {
console.log(ex);
}
};
};
but as I said this does add a new object to an array.....
"episodes": [
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
},
{
"episodeId": 1,
"actor": "1",
"id": 2
}
]

So just to be clear I understand your question, you have an object that already exists in your DB, and you want to push something onto the 'characters' array in that existing object, without creating a new object, correct?
To do this, I would use Mongo for your DB and define two Mongoose Schemas, one for the existing object (let's call it TVShow) and one for the Characters within that object. Your two Schemas will look like this:
TVShowModel.js:
const mongoose = require('mongoose');
const CharacterModel = require('./CharacterModel')
const TVShowScheme = new mongoose.Schema({
title: {
type: String,
},
release_date: {
type: Date,
},
series: {
type: String,
},
img: {
type: String,
},
characters:[
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
},
],
examQuestions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'CharacterModel'
}
]
})
module.exports = mongoose.model('TVShowModel', TVShowScheme )
CharacterModel.js:
const mongoose = require('mongoose');
const CharacterModel= new mongoose.Schema({
characterName: {
type: String,
},
actorName: {
type: String,
},
}) // add any other fields you want here
module.exports = mongoose.model('CharacterModel', CharactModelScheme )
Then, create your Axios post request. Make sure you send when you send the 'value' variable to your server, it contains the id (or perhaps the unique title) of the object you'll be 'pushing' to. Push won't work in axios/react, so we'll use the 'spread' opperator instead.
Your router will look like this:
const CharacterModel= require ('../models/CharacterModel');
const TVShowModel= require ('../models/TVShowModel');
const router = express.Router();
router.post('/episodes', async function(req,res){
try{
const tvshow = await TVShowModel.find({title: req.body.title})
// edit as needed
console.log("FOUND TV Show: "+tvshow )
const characterName= req.body.characterName
const actorName = req.body.actorName
const newCharacter = new CharacterModel({
characterName,
actorName,
})
console.log("new character created: "+newCharacter)
tvshow[0].CharacterModel = [...tvshow[0].CharacterModel,newCharacter];
await tvshow[0].save()
.then(()=>res.json('New Character Added to DB'))
.catch(err=>res.status(400).json('Error: ' + err))
} catch(e){
console.log(e)
}
})
Hope this was clear!

Related

How to transform object to array before parsing in Zod

I do have an external URL endpoint that returns an array of field object when it is more than 2 and an object when there is only one, see the snippet below:
Return when the field count is one:
{
"fields": { "fullName": "fieldFullname", "type": "fieldType" }
}
Return when the field is more than one:
{
"fields": [
{ "fullName": "fieldFullname", "type": "fieldType" },
{ "fullName": "fieldFullname", "type": "fieldType" }
]
}
Currently, this is my schema using zod:
export const sObjectMetadataSchema = z.object({
fields: z.array(metadataFieldSchema).optional()
});
export const metadataFieldSchema = z.object({
fullName: z.string().optional(),
type: z.string().optional(),
});
It is configured that it will only accept an array of objects. When it returns only one field it throws an error:
{
"code": "invalid_type",
"expected": "array",
"received": "object",
"path": [],
"message": "Expected array, received object"
}
My goal is if it returns a single object it will convert it to an array of objects during runtime. Currently trying to implement using transform but still not working:
An initial implementation using transform:
export const sObjectMetadataSchema = z.object({
fields: z.unknown().transform((rel) => {
return Array.isArray(rel)
? z.array(metadataFieldSchema).optional()
: 'Convert the rel to Array?';
}),
});
I didn't test that, but seems like should work:
const FieldsSchema = z.object({
fullName: z.string(),
type: z.string()
});
export const sObjectMetadataSchema = z.object({
fields: z.union([FieldsSchema, FieldsSchema.array()]).transform((rel) => {
return Array.isArray(rel)
? rel
: [rel];
}),
});
Thanks for the answer #Konrad !
I improved it a little bit in typescript, so it is also typed correctly:
const arrayFromString = <T extends ZodTypeAny>(schema: T) => {
return z.preprocess((obj) => {
if (Array.isArray(obj)) {
return obj;
} else if (typeof obj === "string") {
return obj.split(",");
} else {
return [];
}
}, z.array(schema));
};

PUT/Update/Delete item in nested Appsync array. React, useReducer, Apollo

I have an object in my schema which has an array like so:
type Foo #aws_cognito_user_pool {
id: ID!
name: String
comments: [Comment]
createdAt: AWSDateTime
}
the input and Comment Type look like so:
input FooInput #aws_cognito_user_pool {
name: String
comments: [CommentInput]
}
type Comment #aws_cognito_user_pool {
id: String
body: String
parent: String
createdAt: String
}
input CommentInput {
id: String
body: String
parent: String
createdAt: String
}
In my app I am using useReducer to manage state and apollo.
My Foo reducer looks like so
const INITIAL_FOO_STATE = () => {
name: "",
comments: [],
}
export const FooReducer = (state, action) => {
switch(action.field) {
case "name":
case "comments":
return {
...state, [action.field]: action.payload,
}
default:
return
}
}
in my component i have the add, update and delete functions like so:
const addComment = (body, parent) => {
CreateComment(
body,
parent,
)
.then((comment) => {
setComments([comment, ...comments]);
setActiveComment(null);
});
};
const updateComment = (body, commentId) => {
UpdateComment(body)
.then(() => {
const updatedComments = comments.map((item) => {
if (item.id === commentId) {
return {
...item,
body: body,
};
}
return item;
})
setComments(updatedComments);
setActiveComments(null);
})
};
const deleteComment = (commentId) => {
if (window.confirm("Are you sure you want to remove this comment?")) {
DeleteComment()
.then(() => {
const updatedComments = comments.filter((comment) => comment.id !== commentId);
setComments(updatedComments)
})
}
};
This works just fine on the frontend, but now want to dispatch these actions to my Foo.comments array on the backend and looking for some help as to the best way without the need of another table.
I was thinking of placing the dispatch's within their respective .then()
For anyone interested, the solution was simple.
I had to modify the comment type in schema to:
type Comment #aws_cognito_user_pool {
id: ID!
sourceId: ID!
body: String
parent: String
createdAt: String
}
input CommentInput {
body: String
parent: String
createdAt: String
}
then add the mutations and resolvers for create, update and delete of the Comment.
In the Foo comment field, a query resolver was needed.
This also meant a new table was needed for comments.
The table needed to have the sourceId as primary key and ID as sort key, with a global index setup on the sourceId
I swapped out the CRUD functions in my component with the appropriate Apollo mutations and done.

Handling Graphql Mutation update, cache read and writeQuery, if the query is dynamic?

Doing nightlife app on freecodecamp https://learn.freecodecamp.org/coding-interview-prep/take-home-projects/build-a-nightlife-coordination-app/
I am trying to implement 'Go' button, similarly 'Like' button on Youtube or Instagram. Users click the button the number(counting how many users go) goes up meaning users will go there and click again, it revokes, the number decreases, users will not go there.
It seems like working well except the issue, I have to refresh the page and then, the number has increased or decreased and throws the error like below so:
Invariant Violation: Can't find field getBars({}) on object {
"getBars({\"location\":\"vancouver\"})": [
{
"type": "id",
"generated": false,
"id": "Bar:uNgTjA9ADe_6LWby20Af8g",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:CwL5jwXhImT_7K5IB7mOvA",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:mdt1tLbkZcOS2CsEbVF9Xg",
"typename": "Bar"
},
.
.
.
I am assuming handling update function will fix this issue but unlike the example from Apollo documentation:
// GET_TODOS is not dynamic query
// nothing to pass as variables to fetch TODO list
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
My query is dynamic:
// I have to pass location variable, otherwise it won't fetch anything.
const GET_BARS_QUERY = gql`
query getBars($location: String!) {
getBars(location: $location) {
id
name
url
rating
price
image_url
goings {
username
}
goingCount
}
}
`;
I believe I might need to handle to provide location using readQuery and writeQury but not too sure what I should do.
Here's my code:
const GoButton = ({ user, bar }) => {
const { token } = user;
const { id, goings, goingCount } = bar;
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data });
}
});
return (
<Button onClick={userGoes}>
Go {goingCount}
</Button>
);
};
const GO_MUTATION = gql`
mutation go($yelp_id: String!) {
go(yelp_id: $yelp_id) {
id
goings {
id
username
}
goingCount
}
}
`;
export default GoButton;
Full code here https://github.com/footlessbird/Nightlife-Coordination-App
when you read/write the getBars query, you need to pass the location as a variable
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY,
variables: {
location: 'New York'
}
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data,
variables: {
location: 'New York'
}
});
}
});

Problem with adding an item to an array of mongoose reference objects

In my NodeJS and MongoDB app, I have 2 mongoose schemas:
companySchema:
const companySchema = new Schema({
name: {
type: String,
required: true
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product',
required: false
}]
});
companySchema.statics.addProduct = function (productId) {
let updatedProducts = [...this.products];
updatedProducts.push(productId);
this.products = updatedProducts;
return this.save();
}
module.exports = mongoose.model(‘Company’, companySchema);
productSchema:
const productSchema = new Schema({
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
}
});
module.exports = mongoose.model('Product', productSchema);
Every time I add a new product to productSchema, I would like to add the _id of the newly created product to products array in companySchema in order to easily access products later on.
To accomplish this, I wrote:
const Company = require('../models/company');
const Product = require('../models/product ');
exports.postAddProduct = (req, res, next) => {
const name = req.body.name;
const quantity = req.body.quantity;
const product = new Product({
name: name,
quantity: quantity
});
product.save()
.then(product => {
return Company.addProduct(product._id);
})
.then(result => {
res.redirect('/');
})
.catch(err => console.log(err));
}
I am getting an error: TypeError: this.products is not iterable.
You are setting a static method, which is a method on the model rather than the document instance.
Therefore, this refers to the model itself, not the individual document.
Unlike the document, the model doesn’t have an array (iterable) called products so it can’t be spread into a new array.
Try using methods instead of statics:
companySchema.methods.addProduct = function (productId) {
...
}
I hope this helps.

Mongoose/Mongo: Update Not Saving

I'm extremely perplexed by this issue that I'm having with mongo/mongoose. I'm essentially trying to get an array of products, delete a certain product from the array, and then update the shopping chart with the new array that omits the selected product. Here's the snippet of code I'm dealing with:
const remove = (req, res, next) => {
console.log('here is the product id ' + req.body.cart.product)
delete req.body._owner // disallow owner reassignment.
Cart.find({_id: req.user.cartId})
.then((products1) => {
console.log("array of products: " + products1[0].product)
const index = products1[0].product.indexOf(req.body.cart.product)
console.log("index valeu: " + index)
if (index > -1) {
products1[0].product.splice(index, 1)
return products1[0].product
}
return products1[0].product
})
.then((products2) => {
console.log('Second Promise Input: ' + products2)
Cart.update({_id: req.user.cartId}, {$set: {product: products2}})
})
.then(() => res.sendStatus(204))
.catch(next)
}
And here's the output from my server:
Server listening on port 4741
here is the product id 5952b57ea52d092b8d34c6b0
array of products: 5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0
index valeu: 0
Second Promise Input: 5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0
PATCH /carts-decrease/595b037e128cfd37e0c864d7 204 38.773 ms
According to my console.logs, I'm getting the array just the way I want it but it simply does not update the shopping cart with the new array. I've been staring at this code for far too long and I'd appreciate a second set of eyes on this. Thanks.
P.S. Ignore the fact that the product ids are all the same, its just a testing variable
Cart Schema:
'use strict'
const mongoose = require('mongoose')
const cartSchema = new mongoose.Schema({
product: {
type: Array,
required: false
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: false
}
}, {
timestamps: true,
toJSON: {
virtuals: true,
transform: function (doc, ret, options) {
const userId = (options.user && options.user._id) || false
ret.editable = userId && userId.equals(doc._owner)
return ret
}
}
})
const Cart = mongoose.model('Cart', cartSchema)
module.exports = Cart
Product Schema:
'use strict'
const mongoose = require('mongoose')
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
description: {
type: String,
required: true
}
}, {
toJSON: {
virtuals: true
}
})
const Product = mongoose.model('Product', productSchema)
module.exports = Product
Show request:
const show = (req, res) => {
const product = {}
product.array = []
// console.log(req.cart.product)
const promises = []
Promise.all(req.cart.product.map(function (id) {
return Product.find({_id: ObjectId(id)})
})).then(function (products) {
console.log(products)
req.cart.product = products
return res.json({
cart: req.cart.toJSON({virtuals: true, user: req.user})
})
}).catch(function (err) {
console.log(err)
return res.sendStatus(500)
})
}
I would recommend you to slightly modify your cartSchema and store products in the form of an array of embedded documents:
const cartSchema = new mongoose.Schema({
products: [{
name: { type: String },
price: { type: Number }
...
}]
...
});
If you do this you can simply use the $pull update operator to remove products from your cart:
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
In your case the query should then look like this:
Cart.update(
{ _id: req.user.cartId },
{ $pull: { products: { '_id': req.body.cart.product } }}
);
As the embedded documents will have their own ObjectId there will only be one document matching the query.

Resources