React object, display key names - reactjs

I have a demo here
It's a simple React TypeScript app where I am displaying an object
The object is like
{
"default": {
"ProductSize": {
"min": 32
},
"ProductHeight": {
"min": 15
},
"ProductWeight": 50
},
"ProductOne": {
"ProductSize": {
"min": 20,
"max": 15
},
"ProductHeight": {
"min": 50,
"max": 87
},
"ProductSpacing": 90,
"ProductWeight": 100
},
"ProductTwo": {
"ProductSize": {
"min": 43,
"max": 67
},
"ProductHeight": {
"min": 12,
"max": 78
},
"ProductSpacing": 34,
"ProductWeight": 56
}
}
I can pull out parts of the jsons like
<ul>
{Object.keys(ProductData).map(key => (
<li key={key}>{ProductData[key].ProductSize.min}</li>
))}
</ul>
but how can I get the key name for each block
So I want to list
default
ProductOne
ProductTwo
ProductThree
I have tried
<li key={key}>{ProductData[key].key}</li>
but I get an empty list item

Answer
You already have keys array through Object.keys(ProductData)
So it will be simple
<ul>
{Object.keys(ProductData).map(key => (
<li key={key}>{key}</li>
))}
</ul>
Theory
Object.keys(obj) returns array of obj keys, MDN docs
const obj = {
foo: 12
bar: 'bar'
}
// will return ['foo', 'bar']
const keys = Object.keys(obj);
// will return <li key="foo"}>foo</li><li key="bar">bar</li>
const items = keys.map((key) => <li key={key}>{key}</li>)
If you will need a key and a value together see Object.entries() docs
// will return [['foo', 12], ['bar', 'bar']]
const entries = Object.entries(obj);
// will return <li key="foo"}>foo: 12</li><li key="bar">bar: bar</li>
const items = entries.map(([key, value]) => <li key={key}>{key}: {value}</li>);
For [key, value] syntax see destructuring docs

Related

How to get an object inside another object using with useState - Next.js

I have this Object on get:
[
{
"id": {
"M49": 20,
"ISO-3166-1-ALPHA-2": "AD",
"ISO-3166-1-ALPHA-3": "AND"
},
"nome": {
"abreviado": "Andorra",
"abreviado-EN": "Andorra",
"abreviado-ES": "Andorra"
},
"area": {
"total": "468",
"unidade": {
"nome": "quilômetros quadrados",
"símbolo": "km2",
"multiplicador": 1
}
},
"localizacao": {
"regiao": {
"id": {
"M49": 150
},
"nome": "Europa"
},
"sub-regiao": {
"id": {
"M49": 39
},
"nome": "Europa meridional (Sul da Europa)"
},
"regiao-intermediaria": null
},
"linguas": [
{
"id": {
"ISO-639-1": "ca",
"ISO-639-2": "cat"
},
"nome": "catalão"
}
],
"governo": {
"capital": {
"nome": "Andorra-a-Velha"
}
},
"unidades-monetarias": [
{
"id": {
"ISO-4217-ALPHA": "EUR",
"ISO-4217-NUMERICO": "978"
},
"nome": "Euro"
}
],
"historico": "O Principado de Andorra é um dos menores Estados da Europa, situado no alto dos Pireneus, entre as... "
}
]
I can't return every "nome": {"abreviado":"Andorra"}
import styles from "../styles/Home.module.css";
import { useEffect, useState } from "react";
let url = "https://servicodados.ibge.gov.br/api/v1/paises";
export default function Home() {
let [countryfact, setCountryfact] = useState([null]);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((result) => setCountryfact(result));
}, []);
console.log(countryfact)
return (
<div style={{ color: "blue" }}>
<ul>
{countryfact.map((country, name) => (
<li key={country.name}>
<span>name: {countryfact.name}</span> <span>age: {countryfact.id}</span>
</li>
))}
</ul>
</div>
);
}
I want to return the object inside another object but i can't do it with my code
My return on screen, is empty, but there a lot empty lines returning.
Maybe this is a simple, but i try with another ways without results
Return on screen
There is no property called name in your json data. Also I don't see any reason you print age as {countryfact.id} which by the way is an object.
<li key={country.name}>
<span>name: {countryfact.name}</span> <span>age: {countryfact.id}</span>
</li>
You can try like this:
<div style={{ color: "blue" }}>
<ul>
{countryfact.map((country) => (
<li key={country.nome.abreviado}>
<span>name:{country.nome.abreviado}</span>
{" "}
<span>area:{country.area.total}</span>
</li>
))}
</ul>
</div>
The problem is that you are passing the wrong variable inside tag li.
you called<span>name: {countryfact.name} observe that you used "countryfact"
the correctly is like <span>name: {country.name}.

Getting MUI's Autocomplete to correctly display categories and subcategories

I'm trying to essentially achieve the following image which is found here:
In that thread, they talk about the best way to display categories and subcategories and the consensus is an MUI Autocomplete.
I'm not however sure how I would achieve something like that at all and would like some help with how I could achieve it.
What I need is for the user to only be able to select one category, whether it be a "root category" or a sub-category. So in the example above, either the "Boysenberry" or the "Brulee Berry".
I also want to try and have the id of said category so I can apply it on my back end (which I'm sure I can do.
My fetched json structure looks like the below:
[
{
"id": 1,
"name": "Audio Visual Equipment",
"parent": null,
"stockItems": [],
"childCategories": [
{
"id": 2,
"name": "Projectors",
"stockItems": [],
"childCategories": [
{
"id": 3,
"name": "Lenses",
"stockItems": [],
"childCategories": []
}
]
}
]
},
{
"id": 4,
"name": "Lighting Equipment",
"parent": null,
"stockItems": [],
"childCategories": [
{
"id": 5,
"name": "Intelligent",
"stockItems": [],
"childCategories": []
},
{
"id": 6,
"name": "Generic",
"stockItems": [],
"childCategories": []
},
{
"id": 7,
"name": "Control",
"stockItems": [],
"childCategories": []
}
]
},
{
"id": 8,
"name": "Sound Equipment",
"parent": null,
"stockItems": [],
"childCategories": [
{
"id": 9,
"name": "Mixing Desk",
"stockItems": [],
"childCategories": []
}
]
},
{
"id": 10,
"name": "Cables",
"parent": null,
"stockItems": [],
"childCategories": [
{
"id": 11,
"name": "Multicore",
"stockItems": [],
"childCategories": []
},
{
"id": 12,
"name": "Lighting",
"stockItems": [],
"childCategories": []
},
{
"id": 13,
"name": "Audio",
"stockItems": [],
"childCategories": []
},
{
"id": 14,
"name": "Video",
"stockItems": [],
"childCategories": []
},
{
"id": 15,
"name": "Power",
"stockItems": [],
"childCategories": []
}
]
}
]
EDIT:-
I get the following warning when I refresh the page:
MUI: The value provided to Autocomplete is invalid.None of the options match with `-1`.You can use the `isOptionEqualToValue` prop to customize the equality test.
When I then click on the Autocomplete, I get the "root" categories only. When I then click on one, the name is not shown and I get the following error:
MUI: The value provided to Autocomplete is invalid.None of the options match with `1`.You can use the `isOptionEqualToValue` prop to customize the equality test.
1. Flattening the List
My approach is to "flatten" the list of categories into a single array so that MUI can evaluate each sub-category. Each of my flat options has a depth property so that I can display it with the correct level of indentation.
We can use the code from the Checkboxes example and add an indentation with the MUI sx prop:
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox checked={selected} sx={{ ml: 2 * option.depth }} />
{option.name}
</li>
)}
2. Filtering Matches
I'm assuming that we want to display the top-level category above a sub-category which matches on the sub-category term only. Like in your linked "ber" example, if the category was "Fall Gold" and the subcategory was "Fall Gold Berry". This means that we should consider the child terms when deciding if a term is a match.
To achieve this, I am including a matchTerms property on all option objects and using a custom filterOptions function on the Autocomplete which looks at this property. With the createFilterOptions utility, we just need to determine what texts to examine:
filterOptions={(createFilterOptions({
// join with some arbitrary separator to prevent matches across adjacent terms
stringify: (option) => option.matchTerms.join("//")
}))}
3. Highlighting
The last piece of this is the highlighting, which is not included in MUI. The MUI docs recommend the autosuggest-highlight package and include an example of how to use it. We can copy that, changing option.title to option.name.
Complete Code
JavaScript
import {
Autocomplete,
TextField,
Checkbox,
createFilterOptions
} from "#mui/material";
import { data } from "./data";
import parse from "autosuggest-highlight/parse";
import match from "autosuggest-highlight/match";
const toOptions = (category, depth = 0, parentId = null) => {
const { id, name, childCategories = [] } = category;
const children = childCategories.flatMap((child) =>
toOptions(child, depth + 1, id)
);
const option = {
id,
name,
depth,
parentId,
matchTerms: [name].concat(children.map((obj) => obj.name))
};
return [option].concat(children);
};
const optionsList = data.flatMap((category) => toOptions(category));
export default () => {
return (
<Autocomplete
options={optionsList}
getOptionLabel={(option) => option.name}
renderOption={(props, option, { selected, inputValue }) => {
const matches = match(option.name, inputValue);
const parts = parse(option.name, matches);
return (
<li {...props}>
<Checkbox checked={selected} sx={{ ml: 2 * option.depth }} />
<div>
{parts.map((part, index) => (
<span
key={index}
style={{
fontWeight: part.highlight ? 700 : 400
}}
>
{part.text}
</span>
))}
</div>
</li>
);
}}
renderInput={(params) => <TextField {...params} />}
filterOptions={createFilterOptions({
// join with some arbitrary separator to prevent matches across adjacent terms
stringify: (option) => option.matchTerms.join("//")
})}
/>
);
};
TypeScript
import {
Autocomplete,
TextField,
Checkbox,
createFilterOptions
} from "#mui/material";
import { data } from "./data";
import parse from "autosuggest-highlight/parse";
import match from "autosuggest-highlight/match";
// describes the input data
type Category = {
id: number;
name: string;
childCategories?: Category[];
};
// describes the format that we want
interface Option {
id: number;
name: string;
depth: number;
parentId: number | null;
matchTerms: string[];
}
const toOptions = (
category: Category,
depth: number = 0,
parentId: number | null = null
): Option[] => {
const { id, name, childCategories = [] } = category;
const children = childCategories.flatMap((child) =>
toOptions(child, depth + 1, id)
);
const option = {
id,
name,
depth,
parentId,
matchTerms: [name].concat(children.map((obj) => obj.name))
};
return [option].concat(children);
};
const optionsList: Option[] = data.flatMap((category) => toOptions(category));
export default () => {
return (
<Autocomplete
options={optionsList}
getOptionLabel={(option) => option.name}
renderOption={(props, option, { selected, inputValue }) => {
const matches = match(option.name, inputValue);
const parts = parse(option.name, matches);
return (
<li {...props}>
<Checkbox checked={selected} sx={{ ml: 2 * option.depth }} />
<div>
{parts.map((part, index) => (
<span
key={index}
style={{
fontWeight: part.highlight ? 700 : 400
}}
>
{part.text}
</span>
))}
</div>
</li>
);
}}
renderInput={(params) => <TextField {...params} />}
filterOptions={createFilterOptions({
// join with some arbitrary separator to prevent matches across adjacent terms
stringify: (option) => option.matchTerms.join("//")
})}
/>
);
};
CodeSandbox Link

How to implement a filter function for multiple conditions in Vue?

I want to implement a filter function in Vue3 using two filter conditions. I would like to know how to combine object arraies to narrow down the conditions. I have implemented the followings, but I'm not sure know how to fix it because There is no errors. I'm trying to debug, but if you could give me some advice, that would be really helpful. Thank you.
<template>
<div>
<div>City</div>
<a-checkbox-group :options="plainOptions" >
<div v-for="city in cityList" >
<a-checkbox :value="city" #change="cityFilter(city)"/> {{ city.cityName }}
</div>
</a-checkbox-group>
<div>Category</div>
<a-checkbox-group :options="plainOptions" >
<div v-for="category in cattegoryList" >
<a-checkbox :value="category" #change="categoryFilter(category)"/> {{ category.categoryName }}
</div>
<div>
<a-checkbox #change="categoryFilter(category)"/> ALL
</div>
</a-checkbox-group>
</div>
</template>
let filteredProducts = ref();
let filteredByCity = ref();
let filteredByCategory = ref();
// City filter event
const cityFilter = (city) => {
filteredByStatus.value = products.value.filter(function(x){
return x.cityName === city.cityName
});
// Filter by city and category
filteredProducts.value = filteredByCity.value.push(filteredByCategory.value);
}
// Category filter event
const categoryFilter = (category) => {
filteredByCategory.value = products.value.filter(function(x){
return x.categoryName === category.categoryName
});
// Filter by city and category
filteredProducts.value = filteredByCategory.value.push(filteredByCity.value);
}
const products = [
{ categoryName: "A", cityName: "Canada", walk: 5 },
{ categoryName: "A", cityName: "Canada", walk: 5 },
{ categoryName: "A", cityName: "America", walk: 5 },
{ categoryName: "B", cityName: "Canada", walk: 10 },
{ categoryName: "C", cityName: "America", walk: 15 },
]
const categoryList = [
{categoryName:"A", categoryId: 0},
{categoryName:"B", categoryId: 1},
{categoryName:"C", categoryId: 2}
];
const cityList = [
{cityName:"America", cityId: 0},
{cityName:"Canada", cityId: 1}
];

arrange elements in vertical order

Found some answers, but all are implemented on multi dimension array.
I have a scenario, where I need to render the array elements in the vertical order.
Expected Behavior:
Get the length of given array and split the array into four columns.
Each column should have max of 6 items in it.
Expected Output:
1 7 13 19
2 8 14 20
3 9 15 21
4 10 16 22
5 11 17 23
6 12 18 24
Sample Array:
{
"items": [
{
"val": "1"
},
{
"val": "2"
},
{
"val": "3"
},
{
"val": "4"
},
{
"val": "5"
},
{
"val": "6"
},
{
"val": "7"
},
{
"val": "8"
},
{
"val": "9"
}
]
}
This is what I tried, not able to achieve the expected output.
createList(list, index) {
const max = Math.max.apply(null, list.items.map((x, i) => (i + 1) / 4));
return Array.from({ length: max }, function(item, index) {
return <div className="row" key={index}>
{list.items.slice(0, max).map((x, i) => (
<div className="col-3" key={i}>
{x.val}
</div>
))}
</div>;
});
}
There's probably tons of way to do this but i came up with this solution using lodash:
renderColumn(row) {
// map through columns
const columns = row.map(column => (
<td key={column}>{column}</td>
));
return columns;
}
render() {
// Create an array of length 50
let items = [];
for (let i=0; i<50; i++) {
items.push({val:i});
}
/** Transform the array in something like this:
* items: [
* [1, 7, 13, 19],
* [2, 8, 14, 20],
* [3, 9, 15, 21],
* [4, 10, 16, 22],
* [5, 11, 17, 23],
* [6, 12, 18, 24],
* ]
*/
// Get only 24 first items as it is a 4x6 matrix
items = items.slice(0, 24);
const res = _.reduce(items, (result, value, key) => {
const index = key % 6;
(result[index] || (result[index] = [])).push(value.val);
return result;
}, {});
// map through the rows of the arrays
const rows = _.map(res, row => {
const column = this.renderColumn(row);
return <tr>{column}</tr>;
});
return (
<table>
<tbody>
{rows}
</tbody>
</table>
);
}
I hope you get the point :)
Here is a sandbox with the code

Populate table with JSON data and variable columns

I have the following JSON data:
{
"languageKeys": [{
"id": 1,
"project": null,
"key": "GENERIC.WELCOME",
"languageStrings": [{
"id": 1,
"content": "Welcome",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 2,
"project": null,
"key": "GENERIC.HELLO",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
}, {
"id": 5,
"content": "Hello",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 3,
"project": null,
"key": "GENERIC.GOODBYE",
"languageStrings": []
}]
}
I want that converted into a table where the columns are variable.
The table output should look like the following:
------------------------------------------------
| Key | EN | DK | SE | [...] |
| GENERIC.WELCOME | Welcome | | | |
| GENERIC.HELLO | Hello | Hej | | |
| GENERIC.GOODBYE | | | | |
------------------------------------------------
As you can see, the table is dynamic in both rows and columns, and I am struggling to figure out how to map the correct data in each of the "EN", "DK", "SE" [...] fields to the correct column since they are not neccessarily in order when I get them in the JSON response from the API.
I got the following render function so far:
private static renderLanguageKeysTable(languageKeys: ILanguageKey[], languages: ILanguage[]) {
return <table>
<thead>
<tr>
<td>Key</td>
{languages.map(language =>
<td key={language.id}>{language.key}</td>
)}
</tr>
</thead>
<tbody>
{languageKeys.map(languageKey =>
<tr key={languageKey.id}>
<td>{languageKey.key}</td>
{languages.map(language =>
<td key={language.id}>
</td>
)}
</tr>
)}
</tbody>
</table>
;
}
This works as it should, the only part missing is the data in the columns.
I have tried various variations of filter and map but nonw of them worked out the way I wanted them to.
I am using ReactJS and writing in typescript (es2015)
To clarify a bit:
The columns will always be defined by the API, and the rows cannot have an ID pointing to a column that is not there since they are related in the backend.
It may however happen that some rows does not have all the columns, in such case they should just be blank
I ended up using a different approach from what was suggested (after a good nights sleep and some thinking)
Basically, I created a new component for each individual cell, resulting in the following render on the table side of the code:
private static renderLanguageKeysTable(languageKeys: ILanguageKey[], languages: ILanguage[]) {
return <table>
<thead>
<tr>
<th>Key</th>
{languages.map(language =>
<th key={language.id}>{language.key}</th>
)}
</tr>
</thead>
<tbody>
{languageKeys.map(languageKey =>
<tr key={languageKey.id}>
<td>{languageKey.key}</td>
{languages.map(language =>
<Cell language={language} languageKey={languageKey} key={language.id} />
)}
</tr>
)}
</tbody>
</table>
;
}
And the following code for rendering each cell:
import * as React from "react";
export class Cell extends React.Component {
render() {
let string: any;
if (this.props.languageKey && this.props.languageKey.languageStrings) {
let languageString =
this.props.languageKey.languageStrings.find((i: any) => i.language.id === this.props.language.id);
if (languageString === null || languageString === undefined) {
string = "";
} else {
string = languageString.content;
}
} else {
string = "";
}
return <td>
{string}
</td>;
}
props: any;
}
const findDistinctLang = (langKeys) => {
let langString = []
langKeys.forEach((element) => {
if(element.languageStrings.length !== 0) {
langString = [...langString, ...element.languageStrings]
}
})
const langArr = []
langString.forEach((element) => {
if (langArr.indexOf(element.language.key) === -1) {
langArr.push(element.language.key)
}
})
return langArr
}
class Table extends React.Component {
state = {
"languageKeys": [{
"id": 1,
"project": null,
"key": "GENERIC.WELCOME",
"languageStrings": [{
"id": 1,
"content": "Welcome",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 2,
"project": null,
"key": "GENERIC.HELLO",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
}, {
"id": 5,
"content": "Hello",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 3,
"project": null,
"key": "GENERIC.GOODBYE",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
},{
"id": 5,
"content": "XYZ",
"language": {
"id": 7,
"key": "XYZ"
}
}]
}]
}
getContentName = (languageSet, langName) => {
return _.find(languageSet.languageStrings, function(o) { return o.language.key === langName })
}
render() {
const lanKeyArr = findDistinctLang(this.state.languageKeys)
return ( <
table >
<
thead >
<
tr >
<
td > Key < /td> {
lanKeyArr.map((lang) => {
return ( < td > {
lang
} < /td>)
})
} <
/tr> <
/thead> <
tbody >
{
this.state.languageKeys.map((languageSet) => {
return(
<tr>
<td>{languageSet.key}</td>
{[...lanKeyArr].map((element, index) => {
const contentObj = this.getContentName(languageSet, element)
return (
<td>{contentObj && contentObj.content || ""}</td>
)
})
}
</tr>
)
})
}
<
/tbody> < /
table >
)
}
}
ReactDOM.render(<Table />,document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I have implemented based on the test data you provided,
Note: you can make it more clean , just giving you an idea by this example
You can parse the object and render the table according to the expected rendering.
Note, have minimal experience using ReactJS and have not tried TypeScript
let languages = {"languageKeys":[{"id":1,"project":null,"key":"GENERIC.WELCOME","languageStrings":[{"id":1,"content":"Welcome","language":{"id":1,"key":"EN"}}]},{"id":2,"project":null,"key":"GENERIC.HELLO","languageStrings":[{"id":2,"content":"Hej","language":{"id":2,"key":"DK"}},{"id":5,"content":"Hello","language":{"id":1,"key":"EN"}}]},{"id":3,"project":null,"key":"GENERIC.GOODBYE","languageStrings":[]}]};
const table = document.querySelector("table");
const thead = table.querySelector("thead").querySelector("tr");
const tbody = table.querySelector("tbody");
Object.values(languages.languageKeys).forEach(({key, languageStrings}) => {
// check if `languageStrings` array has `.length` greater than `0`
if (languageStrings.length) {
languageStrings.forEach(({content, language:{key:lang}}) => {
console.log(key, content, lang);
// use block scopes
{
// check if the `lang` is already appended to `<thead>`
if (![...thead.querySelectorAll("td")].find(({textContent}) => textContent === lang)) {
let td = document.createElement("td");
td.textContent = lang;
thead.appendChild(td);
}
}
{
// append `key`
let tr = document.createElement("tr");
let tdKey = document.createElement("td");
tdKey.textContent = key;
tr.appendChild(tdKey);
// append `content`
let tdContent = document.createElement("td");
tdContent.textContent = content;
tr.appendChild(tdKey);
tr.appendChild(tdContent);
tbody.appendChild(tr);
// append a `<td>` for placing `<td>` in correct column
// not an optimal approach, adjust if necessary
if ([...thead.querySelectorAll("td")].findIndex(el => el.textContent === lang) === tr.children.length) {
tr.insertBefore(document.createElement("td"), tr.lastElementChild);
};
}
})
} else {
// handle empty `languageStrings` array
let tr = document.createElement("tr");
let tdKey = document.createElement("td");
tdKey.textContent = key;
tr.appendChild(tdKey);
tbody.appendChild(tr);
}
})
<table>
<thead>
<tr>
<td>Key</td>
</tr>
</thead>
<tbody>
</tbody>
</table>

Resources