Adding object to nested array using firebase, nextjs - javascript

I'm using nextjs, firebase to build my project, the project idea is a hybrid project management, the user is able to create a project with many boards and save tasks inside it:
UI of the board
I want to save the board in this way:
screen shot from Firebase
The code:
Here is how board array and project states looks:
const [projectFields, setProjectFields] = useState({
title: '',
details: '',
date: '',
});
const [boardFields, setboardFields] = useState([
{
title: '',
type: '',
columns: [
{ name: "backlog" },
{ name: "In progress" },
{ name: "In review" }
]
},
]);
sendProject function:
const sendProject = async () => {
if (loading) return;
setLoading(true);
const docRef = await addDoc(collection(db, "projects"), {
id: session.user.uid,
projectDetails: projectFields,
boards: boardFields,
timestamp: serverTimestamp(),
});
setLoading(false);
setProjectFields({
title: '',
details: '',
date: '',
})
setboardFields({
title: '',
type: '',
columns: [{}]
})
};
sendTask function, which is update the "boards" in the doc in firebase wrongly:
const sendTask = async () => {
if (loading) return;
setLoading(true);
let object = {
id: '1',
priority: tasksFields.priority,
title: tasksFields.title,
date: tasksFields.date,
details: tasksFields.details,
chat: '0',
attachment: '0'
}
const taskRef = doc(db, "projects", projectId ?? "1");
const newBoards = [...project.boards];
newBoards[boardIndex] = {
...newBoards[boardIndex],
columns: {
...newBoards[boardIndex].columns[columnIndex],
tasks: [...newBoards[boardIndex].columns[columnIndex].tasks, object]
},
}
await updateDoc(taskRef, {
...project,
boards: newBoards,
});
setLoading(false);
setTasksFields(
{
id: '',
priority: '',
title: '',
details: '',
date: '',
attachment: '0',
}
)
};
here is the result after updating the doc:
wrong database schema picture
I don't know how to update it correctly, any help please? thank you all

I found the solution 👇:
Updated sendTask function:
const sendTask = async () => {
if (loading) return;
setLoading(true);
let object = {
id: '1',
priority: tasksFields.priority,
title: tasksFields.title,
date: tasksFields.date,
details: tasksFields.details,
chat: '0',
attachment: '0'
}
const taskRef = doc(db, "projects", projectId ?? "1");
const newBoard = {...project.boards};
const newColumn = [...project.boards[boardIndex].columns];
newColumn[columnIndex] = {
...newColumn[columnIndex],
tasks: [...newColumn[columnIndex].tasks, object]
};
newBoard[boardIndex] = {...newBoard[boardIndex], columns: newColumn};
await updateDoc(taskRef, {
...project,
boards: newBoard,
});
setLoading(false);
setTasksFields(
{
id: '',
priority: '',
title: '',
details: '',
date: '',
attachment: '0',
}
)
};
};

Related

Create new array of objects from two existing array of objects with differing lengths?

I know similar topics are discussed here ad nauseam, but I'm having issues figuring this out for my specific case.
I have a set of data coming into a graphql resolver, where I have to make 3 inserts.
The data coming in looks like this:
[
{
name: 'Item 1',
price: 0,
quantity: 1,
url: null,
user_id: 1,
category_name: 'Category 1',
qty: null
},
... other raw data
]
I'm then taking that data, and formatting it into two separate arrays of Categories and Items. Now, I'm trying to create an array of CategoryItems to be inserted into a join table.
Here are some examples as to what the arrays might look like:
items
[
{
id: 677,
name: 'Item 1',
price: 0,
quantity: 1,
url: null,
user_id: 1
created_at: '2022-07-28T18:00:22.810136+00:00',
updated_at: '2022-07-28T18:00:22.810136+00:00',
},
{
id: 678,
name: 'Item 2',
price: 0,
quantity: 1,
url: null,
user_id: 1
created_at: '2022-07-28T18:00:22.810136+00:00',
updated_at: '2022-07-28T18:00:22.810136+00:00',
},
{
id: 679,
name: 'Item 3',
price: 0,
quantity: 1,
url: null,
user_id: 1
created_at: '2022-07-28T18:00:22.810136+00:00',
updated_at: '2022-07-28T18:00:22.810136+00:00',
},
...other items
]
categories
[
{
id: 148,
name: 'Category 1',
list_id: 2,
updated_at: '2022-07-28T18:00:23.09351+00:00',
created_at: '2022-07-28T18:00:23.09351+00:00',
},
{
id: 149,
name: 'Category 2',
list_id: 2,
updated_at: '2022-07-28T18:00:23.09351+00:00',
created_at: '2022-07-28T18:00:23.09351+00:00',
},
... other categories
]
What I'm trying to do now is create an array of CategoryItems with a category_id, item_id, and position key. So the above data might be represented in a new array in this manner:
What I Want
[
{ category_id: 148, item_id: 677, position: 1 },
{ category_id: 148, item_id: 678, position: 2 },
{ category_id: 149, item_id: 679, position: 1 },
]
Notice that for each Category, there are multiple Items, so when looping through it needs to increment the position by 1 until it reaches the next Category.
Edit: Including the full resolver
import { Context } from '#/context';
import { CsvImportInput, CsvImportResponse } from '#/generated/types';
import { genericError } from '#/helpers/errors';
import { getUserId } from '#/helpers/utils';
import formatCsv from '#/helpers/formatCsv';
import { getUniqueListBy } from '#/helpers/functions';
import supabase from '#/lib/supabase';
const csvImport = async (_parent: any, { input }: { input: CsvImportInput }, context: Context): Promise<CsvImportResponse> => {
const userId = getUserId(context.token);
if (!userId) {
return {
success: false,
...genericError({ message: 'Must supply user id', path: 'Csv import resolver: try' }),
};
}
const { data, list_id } = input;
try {
const formattedData = formatCsv(data, userId);
// bulk insert items
const { data: bulkInsertData, error: bulkInsertError } = await supabase
.from('items')
.insert(formattedData);
console.log({ bulkInsertData });
console.log({ bulkInsertError });
if (!bulkInsertData || bulkInsertError) {
return {
success: false,
...genericError({ message: 'Error importing items', path: 'Csv import resolver: try' }),
};
}
// if not attaching to specific list
if (!list_id) {
return {
success: true,
errors: null,
};
} else {
// create categories
const newCategories = data.map(item => ({ name: item.category_name, list_id }));
const uniqueCategories = getUniqueListBy(newCategories, 'name');
console.log('uniqueCategories: ', uniqueCategories);
const { data: categoriesData, error: categoriesError } = await supabase
.from('categories')
.insert(uniqueCategories);
console.log({ categoriesData });
console.log({ categoriesError });
if (!categoriesData || categoriesError) {
return {
success: false,
...genericError({ message: 'Error importing categories', path: 'Csv import resolver: try' }),
};
}
// create category_items
// const newCategoryItems = bulkInsertData.map(( item, index ) => ({
// category_id: categoriesData.find(category => category.id === item.category.id)?.id,
// item_id: item.id,
// position: index,
// }));
}
return {
success: true,
errors: null,
};
} catch (err) {
console.log({ err });
return {
success: false,
...genericError({ message: 'Error importing items', path: 'Csv import resolver: catch' }),
};
}
};
export default csvImport;

Updating state when state is an array of objects

I have a form with 3 input fields as sender, receiver, and message, form is inside for loop with a unique id:
now I want to update the state when entering form fields , my expected state like below
[
{id: 1, sender: "", receiver: "", message: ""},
{id: 2, sender: "", receiver: "", message: ""},
{id: 3, sender: "", receiver: "", message: ""},
]
I tried the below code,
const [giftMessages, setGiftMessages] = useState([
{ id: '', sender: '' , receiver: '', giftmsg: '' }
]);
const handleGiftMessages = (e, orderItemId) => {
const updateMessages = [
...giftMessages,
{
id : orderItemId,
[e.target.name]: e.target.value
}
];
setGiftMessages(updateMessages);
}
but it's not coming my expected state format
Try below
const arr1 = [{ oid: 11 }, { oid: 12 }, { oid: 13 }];
const [giftMessages, setGiftMessages] = useState([]);
const handleGiftMessages = (e, orderItemId) => {
setGiftMessages(prevGiftMessages => {
const existingItem = prevGiftMessages.find(item => item.id === orderItemId);
return (!existingItem
? [
...prevGiftMessages,
{ id: orderItemId, sender: "", receiver: "", giftmsg: "" }
]
: prevGiftMessages
).map(giftMessage => {
if (giftMessage.id === orderItemId) {
return {
...giftMessage,
id: orderItemId,
[e.target.name]: e.target.value
};
}
return giftMessage;
});
});
};
Code sandbox => https://stackblitz.com/edit/react-val2gy?file=src%2FApp.js

Circular Dependencies cannot be split into their respective files

There are two objects, Users and Company, each user works for a Company, thereby has a companyId which is referred to through GraphQL. Similarly, each Company has a list of users working for them.
Here's the code:
company.js
const UserType = require('./user')
const CompanyType = new GraphQLObjectType({
name: 'Company',
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
users: {
type: new GraphQLList(UserType), --------> Error:Expected {} to be a GraphQL type.
Expected UserType to be a GraphQL type.
async resolve(parentValue, args) {
return await axios(
`http://localhost:3001/company/${parentValue.id}/users`
).then(({ data }) => data)
},
},
}),
})
user.js
const CompanyType = require('./company')
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLString },
age: { type: GraphQLInt },
name: { type: GraphQLString },
company: {
type: CompanyType,
async resolve(parentValue, args) {
console.log(parentValue)
return await axios(
`http://www.localhost:3001/company/${parentValue.companyId}`
).then((response) => response.data)
},
},
}),
})
rootquery.js
const UserType = require('./user')
const CompanyType = require('./company')
const RootQuery = new GraphQLObjectType({
name: 'RootQueryObjectType',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLString },
},
resolve: async (parentValue, args) => {
return await axios(`http://localhost:3001/users/${args.id}`).then(
({ data }) => data
)
},
},
company: {
type: CompanyType,
args: {
id: { type: GraphQLString },
},
resolve: async (parentValue, args) => {
return await axios(`http://localhost:3001/company/${args.id}`).then(
({ data }) => data
)
},
},
},
})
The error is understandable due to Circular Dependencies.
In case I put the code of user.js and company.js into the rootquery.js, there's no error.
Is there a way to seperate out these files, without running into an empty object error?
After researching quite a bit, found out that wherever you'd need to use a circular dependency, you've got to require it at that point in your code, instead of requiring it as a variable.
E.g.
Previously:
const UserType = require('./user') ---> This was null/empty as it wasn't yet
created
const CompanyType = new GraphQLObjectType({
name: 'Company',
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
users: {
type: new GraphQLList(UserType), --------> Error:Expected {} to be a
GraphQL type.
Expected UserType to be a
GraphQL type.
async resolve(parentValue, args) {
return await axios(
`http://localhost:3001/company/${parentValue.id}/users`
).then(({ data }) => data)
},
},
}),
})
To overcome this issue, I required the neccessary module exactly where it is required
Solution:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
name: { type: GraphQLString },
id: { type: GraphQLString },
age: { type: GraphQLInt },
company: {
**type: require('./company'),** -----> This is where a dynamic
import
is being made
async resolve(parentValue, args) {
return await axios(
`http://localhost:3001/users/${parentValue.id}`
).then(({ data }) => data)
},
},
}),
})

Filter tree recursively based on a custom condition

I have below tree structure. I want to filter those items which release = 0.0.1 since I need them to build my navigation according to a specific release number.
{
title: '',
items: [
{
title: '',
path: '',
release: '0.0.1'
},
{
title: '',
path: '',
release: '0.0.2'
}
]
},
{
title: '',
items: [
{
title: '',
items: [
{
title: '',
path: '',
release: '0.0.2'
},
{
title: '',
path: '',
items: [
{
title: '',
path: '',
release: '0.0.1'
},
{
title: '',
path: '',
release: '0.0.2'
}
]
}
]
}
]
}
The point is that I need to have the same tree structure because the structure should be consistent when building my navigation.
{
title: '',
items: [
{
title: '',
path: '',
release: '0.0.1'
},
]
},
{
title: '',
items: [
{
title: '',
items: [
{
title: '',
path: '',
items: [
{
title: '',
path: '',
release: '0.0.1'
},
]
}
]
}
]
}
I can do this for two levels nested tree as below, but when it comes to more nested levels of items it seems that it does not work.
const menuToggle = (condition) => (menus) => menus
.map(menu => ({
...menu,
items: menu.items.filter(item => condition(item))
}))
.filter(menu => !isEmpty(menu.items));
You can use .reduce() and a recursive function to recursively build your new array. If the current object has an items property, then you can recursively call menuToggle to take care of the child items array by filtering it and any of its children. Once the recursive function has returned, you can push an updated version of the filtered items onto the current object using the spread syntax (...), which you can then push into your resulting array (acc). If the current object doesn't have an items property, you can check to see if it passes the condition, and if it does, you can add it to the accumulated array.
See example below:
const arr = [{ title: '', items: [{ title: '', path: '', release: '0.0.1' }, { title: '', path: '', release: '0.0.2' } ] }, { title: '', items: [{ title: '', items: [{ title: '', path: '', release: '0.0.2' }, { title: '', path: '', items: [{ title: '', path: '', release: '0.0.1' }, { title: '', path: '', release: '0.0.2' } ] } ] }] } ];
const menuToggle = (condition) => (menus) => {
return menus.reduce((acc, obj) => {
if(obj.items)
return [...acc, {...obj, items: menuToggle(condition)(obj.items)}];
else if(condition(obj))
return [...acc, obj];
return acc;
}, []);
}
const res = menuToggle(({release}) => release === "0.0.1")(arr);
console.log(res);
If you also want to remove objects which produce an empty items array, then you can do an additional check for this:
const arr = [{ title: '', items: [{ title: '', path: '', release: '0.0.1' }, { title: '', path: '', release: '0.0.2' } ] }, { title: '', items: [{ title: '', items: [{ title: '', path: '', release: '0.0.2' }, { title: '', path: '', items: [{ title: '', path: '', release: '0.0.1' }, { title: '', path: '', release: '0.0.2' } ] } ] }] } ];
const menuToggle = (condition) => (menus) => {
return menus.reduce((acc, obj) => {
if(obj.items) {
const items = menuToggle(condition)(obj.items);
return items.length ? [...acc, {...obj, items }] : acc;
} else if(condition(obj)) {
return [...acc, obj];
}
return acc;
}, []);
}
const res = menuToggle(({release}) => release === "0.0.1")(arr);
console.log(res);

Using immutability-helper in React to set a value of a (maybe nested) object

I have those code:
var update=require('immutability-helper');
var state = {
step: 1,
fields: {
type: '',
name: '',
subtype: '',
team: '',
agreement: ''
}
};
function assignAttribute(attr, value) {
var temp = update(state, {
[attr]: {$set: value}
});
state = temp;
};
console.log(state); // -> { step: 1, fields: { type: '', name: '', subtype: '', team: '', agreement: '' } }
assignAttribute('step', 3);
console.log(state); // -> { step: 3, fields: { type: '', name: '', subtype: '', team: '', agreement: '' } }
assignAttribute('fields.type','superType');
console.log(state); // -> { step: 3, fields: { type: '', name: '', subtype: '', team: '', agreement: '' }, 'fields.type': 'superType' }
What I want is that the last line is the following:
{ step: '3', fields: { type: 'superType', name: '', subtype: '', team: '', agreement: '' } }
The main problem seems to be that in the assignAttribute function [attr] is no longer used as dot-notation
What can I do to handle this?
I found a workaround (but not the right solution)
Using lodash/fp:
var _=require('lodash/fp');
var state = {
step: 1,
fields: {
type: '',
name: '',
subtype: '',
team: '',
agreement: ''
}
};
function assignAttribute(attr, value) {
var temp = _.set(attr, value, state);
state = temp;
};
Due to the fact that _.set is immutable I now got what I looked for :-)

Categories