Generating components through iteration - javascript

Something weird is going on:
This is my initial state (a .js file)
import moment from 'moment';
let date = moment();
var previousDate = moment("2015-12-25");
export const projects = [
{
"id": 0,
"metadata": {
"fields":
[{
"id": 1,
"order": 1,
"name": "Name",
"value": "Collection 1"
},
{
"id": 2,
"order": 2,
"name": "Created On",
"value": date
},
{
"id": 3,
"order": 3,
"name": "Last Modified On",
"value": previousDate
},
{
"id": 4,
"order": 4,
"name": "Status",
"value": "Filed"
}],
"tags":
[{
"id": 1,
"order": 1,
"value": "tag1"
},
{
"id": 2,
"order": 2,
"value": "tag2"
},
{
"id": 3,
"order": 3,
"value": "tag3"
},
{
"id": 4,
"order": 4,
"value": "tag4"
}]
}
}
This is ProjectsList.js:
import React from 'react';
import Project from './Project';
import { projects } from 'initialState';
export default (props) => {
return(
<div className="projectsList">
{projects.map(project => (
<article key={project.id}><Project fields={project.metadata.fields} /></article>
))}
</div>
)
}
And this one's Project.js:
import React from 'react';
export default (props) => {
return(
<ul className="fields">
{props.fields.map(field => <li key={field.id}>{field.name}</li>) }
</ul>
)
}
I am trying to render a bunch of projects in a list, and every project contains a bunch of metadata key-value pairs that it shows.
So basically, the wiring does not matter, it all works fine.
Except for this:
If you look up at the initial state file (first one up there), there is an array of multiple objects in fields. Each object shows 4 key-value pairs
id
order
name
value
Now, in Project.js, the line where I go
{props.fields.map(field => <li key={field.id}>{field.name}</li>) }
looks like I can switch the {field.name} for {field.id}, to show the id in text. Or I can go {field.order}, to display the order.
But weirdly enough, if I want to show the actual value of the field, like so {field.value}, it throws.
invariant.js?4599:38
Uncaught Invariant Violation: Objects are not valid as a React child (found: Mon Jun 20 2016 21:40:33 GMT-0400). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `StatelessComponent`.
I even went as far (sigh) as changing the string value in every fields to val, juste to make sure value wasn't some kind of a reserved word.
Still threw.
Anybody can help me understand what I have done wrong, here?
Thanks Guys.

You are assigning to variable values to the value property in your state file, which are most likely not strings, but objects:
export const projects = [{
"id": 0,
"metadata": {
"fields":
[
...
{
"id": 2,
"order": 2,
"name": "Created On",
"value": date // one
},
{
"id": 3,
"order": 3,
"name": "Last Modified On",
"value": previousDate // and another one
},
...
]
...
}
}

If typeof children returns "object" (and children is neither an array, nor a ReactElement), it throws:
https://github.com/facebook/react/blob/dc6fc8cc0726458a14f0544a30514af208d0098b/src/shared/utils/traverseAllChildren.js#L169
Here's a simplest example to demonstrate this:
const IllegalComponent = () => <span>{{}}</span>
You are supposed to supply a string (or number) so that React could inline that as the children in <li>. Children should be something that's renderable and implements ReactNode.
If the children is an object, React would not know how to render it. You should explicitly convert the value to String.
Try this to see if it works:
{props.fields.map(field => <li key={field.id}>{field.value.toString()}</li>) }

Related

Why is my component not rendering a prop value?

I have a simple component tree:
RecipeFeed which renders Recipe
In my RecipeFeed component I map an an array of recipe objects to render a Recipe component for each recipe object in the array
const renderRecipes = () => {
if (recipes) {
return (
<div>
{recipes.map(recipe =>
<Recipe
key={recipe.id}
recipe={recipe}
ingredients={recipe.ingredients}/>
)}
</div>
)
} else {
return (
<div>
....loading
</div>
)
}
}
My Recipe component is similar:
const renderIngredients = () => {
if (props.ingredients) {
props.ingredients.map(ingredient => {
console.log(ingredient.name)
<div>{ingredient.name}</div>
})
}
}
return (
<div>
<div>{props.recipe.name}</div>
{renderIngredients()}
</div>
)
}
My recipe.name is OK, and renders to the DOM. However, although my ingredients are defined in the console.log in Recipe.jsx, Nothing is rendered to the screen.
I believe it must have something to do with the shape of the data and the way I am trying to access the value, but I am confused why it appears to be okay when inspecting the console log - right value type of string, no errors, etc.
The json data looks like this
"data": [
{
"id": 2,
"name": "pizza",
"ingredients": [
{
"id": 5,
"name": "dough",
"food_group": "grain",
"created_at": "2022-03-08T04:39:41.334Z",
"updated_at": "2022-03-08T04:39:41.334Z"
},
{
"id": 6,
"name": "sauce",
"food_group": "vegetable",
"created_at": "2022-03-08T04:40:11.684Z",
"updated_at": "2022-03-08T04:40:11.684Z"
},
{
"id": 7,
"name": "cheese",
"food_group": "dairy",
"created_at": "2022-03-08T04:40:33.032Z",
"updated_at": "2022-03-08T04:40:33.032Z"
}
],
"recipe_ingredients": [
{
"id": 3,
"recipe_id": 2,
"ingredient_id": 5,
"quantity": null,
"measurement_unit": null,
"created_at": "2022-03-08T04:41:06.482Z",
"updated_at": "2022-03-08T04:41:06.482Z"
},
{
"id": 4,
"recipe_id": 2,
"ingredient_id": 6,
"quantity": null,
"measurement_unit": null,
"created_at": "2022-03-08T04:41:06.484Z",
"updated_at": "2022-03-08T04:41:06.484Z"
},
{
"id": 5,
"recipe_id": 2,
"ingredient_id": 7,
"quantity": null,
"measurement_unit": null,
"created_at": "2022-03-08T04:41:06.485Z",
"updated_at": "2022-03-08T04:41:06.485Z"
}
]
}
You've got numerous mistakes here.
First: the second return statement is not inside a function. It's not part of the renderIngredients function -- which it can't be anyway, since then it would call itself. (Yes, recursion is legit in javascript, but this case would be perfectly circular and thus break.)
(On second glance, this is maybe because you forgot to include the beginning of the Recipe component, but we're not mind readers here.)
Second: your renderIngredients function won't accomplish anything, because: (1) it does not return anything, and (2) the map inside it also doesn't return anything. This is the direct answer to your question, "Why is my component not rendering a prop value?": it's because you haven't chosen to return anything, either with the return keyword, or by using the short arrow form: () => returnValue.
Third: the signature of your Recipe component is bad:
<Recipe
key={recipe.id}
recipe={recipe}
ingredients={recipe.ingredients}
/>
Why are you passing both the recipe and the ingredients separately? The component can access the ingredients through the recipe. You've set it up so that the same data has to be fed into it twice.
Fourth: the names of component functions must start with a capital letter so that the React framework recognizes them as custom components instead of native HTML components. And don't name your component "renderRecipes", name it "RecipeList".

How do I create an unique dataset of json object literal in Javascript?

I want to get an output as an unique set of Categories array with the following output [Men,Woman].
Is there any way to do it in Javascript?
For example this my data
{
"products:"[
{
"id": 1,
"categories": {
"1": "Men",
},
},
{
"id": 2,
"categories": {
"1": "Men",
},
}, {
"id": 3,
"categories": {
"1": "Woman",
},
}
];
}
A simple 1 line answer would be
new Set(input.products.map(p => p.categories["1"]))
This is if you're expecting only key "1" in the categories object.
If it can have multiple categories then you can always do
const uniqueCategories = new Set();
input.products.forEach(p => uniqueCategories.add(...Object.values(p.categories)))
Now you can convert a Set into an array
PS: This is not a ReactJs problem but a pure JS question. You might want to remove the ReactJs tag from this question altogether.

How to get JSON entry from returned objects

I got JSON data, like:
{
"id": 1,
"active": true,
"dependency": [
{ "id": 2 }
{ "id": 3 }
]
},
{
"id": 2,
"active": true
},
{
"id": 3,
"active": true
}
I want to retrieve the "active" value for each dependency in the id 1 object. So far I used a forEach to get those dependency.
thisElement.dependency.forEach(function(id) {
console.log(id)
}
Which returns id 2 and id 3 as objects. Is there a way to use id.active straight away? By using only one loop? Because the result so far are objects without any connection to the related JSON data. Would it require to loop through the whole JSON data to retrieve those values?
The most efficient thing to to is create a hashmap with an Object or Map first so you only need to iterate the array once to find dependency related values.
You could do it by using Array#find() to search whole array each time but that is far more time complexity than the o(1) object lookup
const activeMap = new Map(data.map(obj => [obj.id, obj.active]))
data[0].dependency.forEach(({id}) => console.log(id ,' active: ' , activeMap.get(id)))
<script>
const data =
[
{
"id": 1,
"active": true,
"dependency": [
{"id": 2 },
{"id": 3}
]
},
{
"id": 2,
"active": false
},
{
"id": 3,
"active": true
}
]
</script>

React - wrap multiple elements in parent node

I have the following array of words that compose some sentences:
let words = [
{
"start_time": "2.54",
"end_time": "3.28",
"alternatives": [
{
"confidence": "1.0",
"content": "Hello",
"__typename": "Alternative"
}
],
"type": "pronunciation",
"__typename": "TranscriptItems"
},
{
"start_time": null,
"end_time": null,
"alternatives": [
{
"confidence": "0.0",
"content": ".",
"__typename": "Alternative"
}
],
"type": "punctuation",
"__typename": "TranscriptItems"
},
{
"start_time": "3.29",
"end_time": "3.52",
"alternatives": [
{
"confidence": "1.0",
"content": "This",
"__typename": "Alternative"
}
],
"type": "pronunciation",
"__typename": "TranscriptItems"
}
]
Also i have this variable that contain an array of snippets selected from the words above:
const snippets = [{ start: 2.54, end: 4.00, id: 12, snippetTitle: "This is the title" }];
Right now I am showing the words by doing a simple .map and returning <span>s for every word.
What I am trying to accomplish is the following: if the span i am currently on is between a snippet from snippets variable, wrap it in another div/component so i can highlight it.
So instead of <span>Hello</span>.<span>This</span> i would be able to wrap both spans in a new wrapper component or div like <Highlight><span>Hello</span>.<span>This</span></Highlight>
You can do something like this using the reduce function:
export default myComponent = () => {
let cache = [];
return (
<>
{myArray.reduce((accumulator, currentValue) => {
if(!isInSnippets(currentValue)){
if(cache.length > 0){ // return the cached value for highlight
accumulator.push( (<Highlight>
{cache.map((elt, i) => <span key={i}>{elt}</span>)
</Highlight>));
cache = [];
}
accumulator.push(<span>{currentValue}</span>);
}
else cache.push(currentValue);
return accumulator;
}}
</>
)
}
The accumulator will contain an array of jsx elements. The regular elements will be contained in spans, while the Highlighted elements will be contained all together within a Highlight component.
We store the Highlighted elements in a cache variable. When we face a "regular" element, we check if there is something in the cache. If there is, we put them all in a highlight element, and add it to the cache. Then we add the "current" regular value.

React element - array objects only returning first item?

I am trying to retrieve item values from an object containing several items. The object is an array object. I am puzzled by only being able to retrive the first item and its values in each array instead of all items. Can anybody tell me what I am missing here.
The array object example:
{ "ITEM 1": [
{
"id": 123,
"name": "item1a"
},
{
"id": 234,
"name": "item1b"
},
{
"id": 345,
"name": "item1c"
}
],
"ITEM 2": [
{
"id": 456,
"name": "item2a"
},
{
"id": 567,
"name": "item2b"
},
{
"id": 678,
"name": "item2c"
}],
}
I have data within the new element and on debugging see that loop flows correctly but for some reason only the first item is rendered.
My code that is wrapped in an html element is as follows:
{ Object.keys(this.props.data).map(function (key) {
var list = component.props.data[key];
for (i = 0; i < facetParent.length; i++) {
var item = list[i];
return (
<CheckBox
key={item.id}
data={item}
name={item.name} />
)
}}, this)}
Any suggestions are much appreciated.
The return function immediately terminates the function execution. That is why you only get the first item. Depending on what version of react you use, you should create 2 arrays and join them before rendering

Categories

Resources