React js Tree View using ul and li - reactjs

I want to create a tree view with search nodes using ul/li from JSON data in react js without using any external library.
Here is the sample of my JSON data.
-
[ { value: "General", label: "General", count: "12 / 30", }, { value: "Accounting", label: "Accounting", count: "17 / 30", }, { value: "Labour Management", label: "Labour Management", count: "05 / 30", children: [ { value: "Labour ManagementChild", label: "Labour ManagementChild", children: [ { value: "Labour ManagementSub Child", label: "Labour ManagementSub Child", }, ], }, { value: "Labour ManagementChild2", label: "Labour ManagementChild2", }, ], }, ]

Suppose your JSON data is stored in a variable called tree
and you have some random format of your data in the li tags
then you can go with
const traverseNode = (node) => {
return (
<li key={node.label}>
{node.label}:{node.value} {node.count}
{node.children
? node.children.map((childNode) => (
<ul>{traverseNode(childNode)}</ul>
))
: null}
</li>
);
};
return <ul>{tree.map((node) => traverseNode(node))}</ul>;
Recursive solutions are helpful with such problems. I suggest taking a look again at data structures and algorithms for them (for example on programiz). Your structure is not exactly a tree, but a polytree.

Related

Mongoose | Find objects inside of an array, that each object has another array of objects to satisfy condition

I have a collection Shops. Each object in Shops collection has an array of Item objects called items.
{
_id: ObjectId(...),
shopName: 'Ice cream Shop',
items: [
<Item>{
itemName: 'Chocolate IC',
availabilities: [
{
city: 'NY',
arrivals: [
{
price: 3.99,
quantityLeft: 0,
date: 'yesterday'
},
{
price: 3.99,
quantityLeft: 40,
date: 'today'
}
]
},
{
city: 'LA',
arrivals: []
}
]
},
<Item>{
itemName: 'Strawberry IC',
availabilities: [
{
city: 'NY',
arrivals: [
{
price: 3.99,
quantityLeft: 0,
date: 'yesterday'
},
]
}
]
},
],
},
... anotherShops
I want to get list of Item objects which has overall quantityLeft more than 0 from a specific shop.
I tried this code to get all items with the name start with "Straw" from a Shop with shopName equal to 'Ice cream Shop':
const items = await Shop.aggregate()
.match({
shopName: 'Ice cream Shop',
})
.project({
items: {
$filter: {
input: "$items",
as: "item",
cond: {
$regexMatch: {
input: "$$item.itemName",
regex: `.*Straw.*`,
},
},
},
},
});
And it works. But I don't know how to sum up all quantityLeft values inside availabilities array of each item, and return only that items that has sum more than 0.
availabilities array can be an empty array [].
The city parameter also needs to be in condition. For example, only Items that are in stock in NY
I need this to get the list of items from a certain shop, and only the items that are still in stock.
Pretty hard.
I came up with this solution. If you have a better solution, please post it.
const shop = await GCShop.aggregate([
{
$match: {
shopName: 'Ice Cream Shop',
},
},
{
$unwind: "$items",
},
{
$unwind: "$items.availabilities",
},
{
$unwind: "$items.availabilities.arrivals",
},
{
$group: {
_id: "$items.id",
items_name: { $first: "$items.name" },
arrivals: {
$push: {
arrival_id: "$items.availabilities.arrivals.arrival_id",
price: "$items.availabilities.arrivals.price",
qtty: "$items.availabilities.arrivals.qtty",
},
},
totalQtty: { $sum: "$items.availabilities.arrivals.qtty" },
},
},
{
$project: {
offer_id: "$_id",
_id: 0,
offer_name: 1,
totalQtty: 1,
arrivals: 1,
},
},
{
$match: {
totalQtty: {
$gt: 0,
},
},
},
]).limit(20);

How do I extract the array in an array and structure it to be one whole array?

How do I extract the array in an array and structure it to be one whole array?
Input:
const input = [{
categoryName: "Chinese food",
tabs: [{
header: "Chicken Rice",
content: "Hainanese chicken rice is a dish of poached chicken and seasoned rice"
},
{
header: "Dim sum",
content: "large range of small Chinese dishes that are traditionally enjoyed in restaurants"
}
]
},
{
categoryName: "Italian food",
tabs: [{
header: "Pizza",
content: "Dish of Italian origin consisting of a usually round"
}]
},
]
Output (Need to extract all the headers out to become this array)
const output = [
{
"categoryName": "Chinese food",
"header": "Chicken Rice",
},
{
"categoryName": "Chinese food",
"header": "Dim sum",
},
{
"categoryName": "Italian food",
"header": "Pizza"
},
]
The below solution achieves the assumed target/objective below:
The header always be nested in the same, uniform way. i.e, it will
always be in the tabs prop array & the tabs prop will always be
present in each object in the input array.
Code Snippet
// extract only 'header', and included 'id'
const getHeadersWithId = arr => (
arr.flatMap( // iterate over 'input' array and 'flat'-ten the result
({tabs}) => (tabs.map( // de-structure 'tabs' & iterate over it
({header}) => ({header}) // de-structure 'header' & retain it to intermediate-result array
))
).map(
({header}, idx) => ({ // iterate with index 'idx'
id: idx+1, // generate the 'id' and store
header // header from intermediate-result array element
})
)
);
const input = [{
categoryName: "Chinese food",
tabs: [{
header: "Chicken Rice",
content: "Hainanese chicken rice is a dish of poached chicken and seasoned rice"
},
{
header: "Dim sum",
content: "large range of small Chinese dishes that are traditionally enjoyed in restaurants"
}
]
},
{
categoryName: "Italian food",
tabs: [{
header: "Pizza",
content: "Dish of Italian origin consisting of a usually round"
}]
},
];
console.log(getHeadersWithId(input));
Explanation
Inline comments added to the snippet above.

NextJs / React: Organizing Array

I'm having issues understanding how to best manipulate an array to get the data I want. From the research I've done, there's multiple ways, but I'm unclear on which is most optimized.
I want to display a simple list, with the items broken down by country, then state, then organized alphabetically by city. The array is formatted as follows:
[
{
id: 1,
name: "Place 1",
state: "Florida",
city: "Boca Raton",
country: "US",
},
{
id: 2,
name: "Place 2",
state: "Florida",
city: "Daytona Beach",
country: "US",
},
{
id: 3,
name: "Place 3",
state: "Kansas",
city: "Lenexa",
country: "US",
},
{
id: 4,
name: "Place 4",
state: "Harju",
city: "Tallinn",
country: "EE",
},
]
An example of the desired outcome is:
US
Florida
Place 1
Place 2
Kansas
Place 3
EE
Harju
Place 4
I see a lot of people saying to utilize ES6 for this, but I'm not sure the best way to approach it. Manipulate the original array response? Is there some way I can loop through them?
Here's an approach that only requires a single loop.
const data = [];
let result = {};
data.forEach(({ name, state, country }) => {
if (!result[country]) {
result[country] = {};
}
if (!result[country][state]) {
result[country][state] = [name];
}
else {
result[country] = {
...result[country],
[state]: [
...result[country][state],
name
]
};
}
});
console.log(result);
Output
{
US: { Florida: [ 'Place 1', 'Place 2' ], Kansas: [ 'Place 3' ] },
EE: { Harju: [ 'Place 4' ] }
}
I'm sure the if-else part can be removed by using spread operator and operator chaining, but I wasn't able to figure that out.
If your environment supports operator chaining, here's a smaller solution
const data = [];
let result = {};
data.forEach(({ name, state, country }) => {
result[country] = {
...result[country],
[state]: [
...(result?.[country]?.[state] || []),
name
]
};
});
console.log(result);

why react-select default value doesn't show

I using react select like following code1. But In react-select doesnt show default value.
Two console.log are show me same value correctly all time .
(defaultArray is just a variable)
And when I use code2. Default value does not show too. When I use code3 . Default value showed.
That is too curious to me.
Please give me advice.
edit1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I found new one. useEffect may be caused it's part of problem.
when I remove [useEffect] and set Array. work well.
But Fundamental problem is invisible yet.
const [defaultArray, setDefaultArray] = useState([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
code1
import React from "react";
import { useState, useEffect } from "react";
const SelectPersonality: React.FC<RouteComponentProps> = props => {
const [defaultArray, setDefaultArray] = useState(Array());
useEffect (() => {
setDefaultArray([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]);
}, []);
return (
<div>
{console.log(defaultArray)}
{console.log([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
])}
<Select
isMulti
defaultValue={defaultArray}
options={character_options}
onChange={value => Change(value)}
className="select_personality_character"
/>
</div>
);
};
export default SelectPersonality;
code2
{console.log(defaultArray)}
{console.log([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
])}
<Select
isMulti
defaultValue={
defaultArray
? defaultArray
: [
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]
}
options={character_options}
onChange={value => Change(value)}
className="select_personality_character"
/>
code3
<Select
isMulti
defaultValue={
defaultArray
? [
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]
: defaultArray
}
options={character_options}
onChange={value => Change(value)}
className="select_personality_character"
/>
it seems that defaultArray is not equal to the array ( read the code bellow, is much easier to understand than to explain )
defaultArray != [
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]
defaultArray it may be an anidated array
[
[
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]
]
please check and let me know
edit:
the code2 and code3 gives us the hint that defaultArray:
is not null
is not undefined
it may be empty
like you said into the edit, it may be useEffect fault
basically you initialize the array, render the component, populate the array with the values
try this :
const [defaultArray, setDefaultArray] = useState([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]);
you should populate the array before rendering
also, keep this in mind
const dependenciesArray = []
useEffect(()=>{}, dependenciesArray) // this will block the rerender of the component at state update
useEffect(()=>{}) // not passing an array will force a rerender of the component at each state update
so if you want dinamic select values, don't pass the second argument into the useEffect()
see docs: useEffect docs
Try this,
import React from "react";
import "./styles.css";
import Select from "react-select";
export default function App() {
return (
<div className="App">
<h1>React-select example</h1>
<SelectComponent />
</div>
);
}
const SelectComponent = () => {
const options = [
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
{ value: "35", label: "F" },
{ value: "36", label: "G" },
{ value: "37", label: "H" },
{ value: "38", label: "I" }
];
const defaults = [options[0], options[1], options[2], options[3]];
return (
<div>
<Select
isMulti
defaultValue={defaults}
options={options}
/>
</div>
);
};
I think the problem is that your default values dont match the options values, you are feeding the select component 2 different data arrays and it gets confused
Hi I resolved this problem but I still don't understand what caused it.
I had to add {defaultArray !== undefined && (~)} . This code doesn't work without it.
I can't understand.
why....
import React from "react";
import { useState, useEffect } from "react";
const SelectPersonality: React.FC<RouteComponentProps> = props => {
const [defaultArray, setDefaultArray] = useState<Array<{ [key: string]: string }>>();
useEffect (() => {
setDefaultArray([
{ value: "30", label: "A" },
{ value: "31", label: "B" },
{ value: "32", label: "C" },
{ value: "33", label: "D" },
{ value: "34", label: "E" },
]);
}, []);
return (
<div>
     {defaultArray !== undefined && (
<Select
isMulti
defaultValue={defaultArray}
options={character_options}
onChange={value => change(value)}
className="select_personality_character"
/>
)}
</div>
);
};
export default SelectPersonality;

Update nested subdocuments in MongoDB with arrayFilters

I need to modify a document inside an array that is inside another array.
I know MongoDB doesn't support multiple '$' to iterate on multiple arrays at the same time, but they introduced arrayFilters for that.
See: https://jira.mongodb.org/browse/SERVER-831
MongoDB's sample code:
db.coll.update({}, {$set: {“a.$[i].c.$[j].d”: 2}}, {arrayFilters: [{“i.b”: 0}, {“j.d”: 0}]})
Input: {a: [{b: 0, c: [{d: 0}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Output: {a: [{b: 0, c: [{d: 2}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Here's how the documents are set:
{
"_id" : ObjectId("5a05a8b7e0ce3444f8ec5bd7"),
"name" : "support",
"contactTypes" : {
"nonWorkingHours" : [],
"workingHours" : []
},
"workingDays" : [],
"people" : [
{
"enabled" : true,
"level" : "1",
"name" : "Someone",
"_id" : ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"contacts" : [
{
"_id" : ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries" : "1",
"priority" : "1",
"type" : "email",
"data" : "some.email#email.com"
}
]
}
],
"__v" : 0
}
Here's the schema:
const ContactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const PersonSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [ContactSchema]
});
const GroupSchema = new Schema({
name: String,
people: [PersonSchema],
workingHours: { start: String, end: String },
workingDays: [Number],
contactTypes: { workingHours: [String], nonWorkingHours: [String] }
});
I need to update a contact. This is what I tried using arrayFilters:
Group.update(
{},
{'$set': {'people.$[i].contacts.$[j].data': 'new data'}},
{arrayFilters: [
{'i._id': mongoose.Types.ObjectId(req.params.personId)},
{'j._id': mongoose.Types.ObjectId(req.params.contactId)}]},
function(err, doc) {
if (err) {
res.status(500).send(err);
}
res.send(doc);
}
);
The document is never updated and I get this response:
{
"ok": 0,
"n": 0,
"nModified": 0
}
What am I doing wrong?
So the arrayFilters option with positional filtered $[<identifier>] does actually work properly with the development release series since MongoDB 3.5.12 and also in the current release candidates for the MongoDB 3.6 series, where this will actually be officially released. The only problem is of course is that the "drivers" in use have not actually caught up to this yet.
Re-iterating the same content I have already placed on Updating a Nested Array with MongoDB:
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
All this means is that the current "driver" implementation of .update() actually "removes" the necessary arguments with the definition of arrayFilters. For NodeJS this will be addressed in the 3.x release series of the driver, and of course "mongoose" will then likely take some time after that release to implement it's own dependencies on the updated driver, which would then no longer "strip" such actions.
You can however still run this on a supported server instance, by dropping back to the basic "update command" syntax usage, since this bypassed the implemented driver method:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = mongoose.Types.ObjectId;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const contactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const personSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [contactSchema]
});
const groupSchema = new Schema({
name: String,
people: [personSchema],
workingHours: { start: String, end: String },
workingDays: { type: [Number], default: undefined },
contactTypes: {
workingHours: { type: [String], default: undefined },
contactTypes: { type: [String], default: undefined }
}
});
const Group = mongoose.model('Group', groupSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.remove() )
);
// Create sample
await Group.create({
name: "support",
people: [
{
"_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com"
},
{
"_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries": "1",
"priority": "1",
"type": "email",
"data": "some.email#example.com"
}
]
}
]
});
let result = await conn.db.command({
"update": Group.collection.name,
"updates": [
{
"q": {},
"u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
"multi": true,
"arrayFilters": [
{ "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
{ "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
]
}
]
});
log(result);
let group = await Group.findOne();
log(group);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Since that sends the "command" directly through to the server, we see the expected update does in fact take place:
Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: 'adifferent.email#example.com', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: 'some.email#example.com' } ] } ], __v: 0 })
{ n: 1,
nModified: 1,
opTime:
{ ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
t: 24 },
electionId: 7fffffff0000000000000018,
ok: 1,
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
'$clusterTime':
{ clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
"_id": "5a06557fb568aa0ad793c5e4",
"name": "support",
"__v": 0,
"people": [
{
"_id": "5a05a8c3e0ce3444f8ec5bd8",
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com",
"_id": "5a06557fb568aa0ad793c5e5"
},
{
"_id": "5a05a8dee0ce3444f8ec5bda",
"retries": "1",
"priority": "1",
"type": "email",
"data": "new data" // <-- updated here
}
]
}
]
}
So right "now"[1] the drivers available "off the shelf" don't actually implement .update() or it's other implementing counterparts in a way that is compatible with actually passing through the necessary arrayFilters argument. So if you are "playing with" a development series or release candiate server, then you really should be prepared to be working with the "bleeding edge" and unreleased drivers as well.
But you can actually do this as demonstrated in any driver, in the correct form where the command being issued is not going to be altered.
[1] As of writing on November 11th 2017 there is no "official" release of MongoDB or the supported drivers that actually implement this. Production usage should be based on official releases of the server and supported drivers only.
I had a similar use case. But my second level nested array doesn't have a key. While most examples out there showcase an example with arrays having a key like this:
{
"id": 1,
"items": [
{
"name": "Product 1",
"colors": ["yellow", "blue", "black"]
}
]
}
My use case is like this, without the key:
{
"colors": [
["yellow"],
["blue"],
["black"]
]
}
I managed to use the arrayfilters by ommiting the label of the first level of the array nest. Example document:
db.createCollection('ProductFlow')
db.ProductFlow.insertOne(
{
"steps": [
[
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Livestock Truck",
"type": "road",
"thirdParty": true
}
}
},
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Airplane",
"type": "air",
"thirdParty": true
}
}
}
],
[
{
"actionType": "store",
"payload": {
"company": "Company A",
"is_supplier": false
}
}
],
[
{
"actionType": "sell",
"payload": {
"reseller": "Company B",
"is_supplier": false
}
}
]
]
}
)
In my case, I want to:
Find all documents that have any steps with payload.vehicle.thirdParty=true and actionType=dispatch
Update the actions set payload.vehicle.thirdParty=true only for the actions that have actionType=dispatch.
My first approach was withour arrayfilters. But it would create the property payload.vehicle.thirdParty=true inside the steps with actionType store and sell.
The final query that updated the properties only inside the steps with actionType=dispatch:
Mongo Shell:
db.ProductFlow.updateMany(
{"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": true, "actionType": "dispatch"}}}},
{"$set": {"steps.$[].$[i].payload.vehicle.thirdParty": false}},
{"arrayFilters": [ { "i.actionType": "dispatch" } ], multi: true}
)
PyMongo:
query = {
"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": True, "actionType": "dispatch"}}}
}
update_statement = {
"$set": {
"steps.$[].$[i].payload.vehicle.thirdParty": False
}
}
array_filters = [
{ "i.actionType": "dispatch" }
]
NOTE that I'm omitting the label on the first array at the update statement steps.$[].$[i].payload.vehicle.thirdParty. Most examples out there will use both labels because their objects have a key for the array. I took me some time to figure that out.

Resources