Render HTML from array items - html

I am attempting to render a list of HTML elements (links) stored in an array.
I am initially constructing the array as such:
const availableSizes = product.simples.filter((value) => {
return value.stockStatus === STATUS_AVAILABLE;
}).map((value, index) => {
return `${value.filterValue} `;
});
An example of the array contents is :
["35 ", "36 ", "36.5 ", "37.5 ", "38 ", "39 ", "39.5 ", "40 ", "41 ", "41.5 ", "42 ", "42.5 ", "43 ", "44 ", "44.5 ", "45 ", "46 ", "46.5 ", "48 ", "49 "]
I attempted to modify how each string is built as such:
const availableSizes = product.simples.filter((value) => {
return value.stockStatus === STATUS_AVAILABLE;
}).map((value, index) => {
return `${value.filterValue}`;
});
but the HTML was escaped and printed directly in the output without it being parsed as HTML but as a common string.
Please note that not only I need to render the links but I also need to have onClick handlers that do specific actions (save a cookie for example), so the links need to be handled by React as well.

In .map you return String however you should return JSX
const availableSizes = product.simples.filter((value) => {
return value.stockStatus === STATUS_AVAILABLE;
}).map((value, index) => {
return <a key={ index } href="#">{ value.filterValue }</a>;
});

As you have JSX available you could do the following instead:
class MyComponent extends React.Component {
render() {
const availableSizes = product.simples
.filter((value) => value.stockStatus === STATUS_AVAILABLE)
.map((value, index) => <a key={index} href="#">${value.filterValue}</a>);
return (
<div>
{availableSizes}
</div>
);
}
}
Pay attention to the key={index} that I added. React needs this to optimise the rendering process. If you have a unique id for each product you could use that instead for a better optimisation. It's used in React's diffing algorithm. For example: <a key={value.id} href="#">${value.filterValue}</a>

Related

How to save an array of JSON objects (with nested objects) to state in React

I'm trying to save an array of JSON objects returned from an API call to state in React (so that I can use the data to render a table). I'm getting the error Error: Objects are not valid as a React child (found: object with keys {street, suite, city, zipcode, geo}). If you meant to render a collection of children, use an array instead.
I can't figure out how to fix this. It looks like the JSON is being stored inside an array as it should be. However, there are also nested objects inside the objects that may be causing an issue, for example:
address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
Any assistance would be much appreciated. Here's my code below:
let tableData = []
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => {
tableData = data
props.addItem(tableData)
})
Here's the addItem function:
addItem(item) {
this.setState(function(prevState) {
return {
tables: [...prevState.tables, item]
}
})
}
UPDATE
Here's how I am rendering the data:
App.js:
render() {
return (
<div>
{this.state.tables.map(item => {
return (<TableComponent key={item} data={item} />)
})}
</div>
)
}
TableComponent.js:
class TableComponent extends React.Component {
constructor(props){
super(props);
this.getHeader = this.getHeader.bind(this);
this.getRowsData = this.getRowsData.bind(this);
this.getKeys = this.getKeys.bind(this);
}
getKeys = function(){
return Object.keys(this.props.data[0]);
}
getHeader = function(){
let keys = this.getKeys();
return keys.map((key, index)=>{
return <th key={key}>{key.toUpperCase()}</th>
})
}
getRowsData = function(){
let items = this.props.data;
let keys = this.getKeys();
return items.map((row, index)=>{
return <tr key={index}><RenderRow key={index} data={row} keys={keys}/></tr>
})
}
render() {
return (
<div>
<table>
<thead>
<tr>{this.getHeader()}</tr>
</thead>
<tbody>
{this.getRowsData()}
</tbody>
</table>
</div>
);
}
}
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{props.data[key]}</td>
})
}
The error message threw me off here because it made it seem like the issue was in saving the objects to state. However, as was pointed out in the comments, the error happened during rendering. To solve the issue I changed RenderRow to the following:
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{typeof props.data[key] === "object" ? JSON.stringify(props.data[key]) : props.data[key]}</td>
})
}
Specifically, the piece that I changed is to first check whether a specific element is an object, and if it is, to use JSON.stringify() to convert it to a string before rendering it to the screen.

Display different data from json file, depending on link clicked

React.js
On the main page example.js there are 4 links: link1, link2, link3, link4
When the user clicks on one of the links they are sent to a site called template.js.
Each link sends the user to the same site, template.js, however the data is different depending on what link was clicked.
I have tried just to display the entire data from one of my .json files, without any functionality and style -- but I didn't get any response whatsoever...
I have tried:
var data = require(url);
for(var i = 0; i < data.length; i++) {
var obj = data[i];
console.log("Name: " + obj.first_name + ", " + obj.last_name);
}
OR
fetch(url)
.then(response => response.json().then(data => ({status:
response.status, body: data})))
.then(object => console.log(object));
OR
fetch(url)
.then(response = response.json())
Question:
How would I tell the template.js file to display the relevant information.
You can pass your query through the link and then read it directly from the url.
I do it this way:
Your links
// Here we want to send our search terms, this is just an example with 'someId'
<a src="/template?first_name=john"></a>
<a src="/template?first_name=jenny"></a>
<a src="/template?first_name=gabriel"></a>
<a src="/template?first_name=jose"></a>
You can read the search values with window.location.search or window.location.hash depending on your router.
I prefer use the parse function from the query-string module
Your template
import React, { Component } from 'react';
import * as qs from 'query-string';
class Dashboard extends Component {
render() {
const {
location,
} = this.props;
const { search } = location;
const query = qs.parse(search, { ignoreQueryPrefix: true });
const info = YOURJSONDATA.filter(data => (
// Here we compare the field we want with the query search
data.first_name === query.first_name
));
return (
<div>
{
!!(info) && info.map(o => (<div>{o.first_name}</div>))
}
</div>
);
}
}
Here's how I did it....
In Learn.js__
//reading url
componentDidMount() {
const values = queryString.parse(this.props.location.search)
console.log(values.filter)
console.log(values.origin)
}
//redirection
redirect = (url) => {
this.props.history.push(url)
console.log(this.props)
}
<LearnCard onClick={() => this.redirect("/learn/Template/Cooks")} name="Cooks" image={process.env.PUBLIC_URL + '/image/cook.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Websites")} name="Websites" image={process.env.PUBLIC_URL + '/image/website.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Tv-Series")} name="Tv-Series" image={process.env.PUBLIC_URL + '/image/tv_series.jpg'}/>
<LearnCard onClick={() => this.redirect("/learn/Template/Cookbooks")} name="Cookbooks" image={process.env.PUBLIC_URL + '/image/cookbook.jpg'}/>
In Template.js__
componentDidMount () {
const url_name = this.props.match.params.name
console.log(this.props.match.params.name)
if (url_name === "Cooks") {
this.setState({data: cooks})
console.log(cooks)
}
if (url_name === "Cookbooks") {
this.setState({data: cookbooks})
console.log(cookbooks)
}
if (url_name === "Tv-Series") {
this.setState({data: tv_series})
console.log(tv_series)
}
if (url_name === "Websites") {
this.setState({data: websites})
console.log(websites)
}
}
render () {
return (
<div>
<div className="templateWrapper">
{
this.state.data && this.state.data.map((data, key) => {
return <TemplateCard className="templateCard" name={data.name} description={data.description} image={data.image} cuisine={data.cuisine} author={data.author} channel={data.channel} href={data.web_url} href={data.chef_url}/>
})
}
</div>
</div>
);
}

Filter search results react

My data back from search result has columns: enTitle,Image,url,enDescription,HasLandingPage,AddInfo.
I want to filter search results by AddInfo to show in different lists. later if I can add a button that would be better.
Render Data:
const ListArticle = (props) =>{
return (
<div className="card">
<div className="search-img-lft">
<a href={props.link} target="_blank">
<img src={props.image} alt="" />
</a>
</div>
<div className="search-imgcont-rgt">
<a href={props.link} target="_blank">
<h3>
{props.title}
{props.kind} // just to see if kind works
</h3>
<p>{props.desc}</p>
</a>
{props.link}
</div>
</div>
);
}
List Class:(ignore the i,brac & lim they are for pagination)
class List extends React.Component {
render(){
const liArt =[];
const searchText = this.props.searchText.toLowerCase().replace(/[^a-z0-9]/g, '');
var i = 0;
const brac = this.props.start;
const lim = brac + this.props.qtyPerPage;
//the filter below works for resources but I want all to be filtered and show in the list in previous code snippet
this.props.list.filter(u=>u.AddInfo == "resource").map((article)=>{
var artText = (article.enTitle + " " + article.URL + " " + article.enDescription + " " + article.AddInfo).toLowerCase().replace(/[^a-z0-9]/g, '');
if(artText.indexOf(searchText)===-1){
return;
}
i++;
if(brac<i && i<lim){
liArt.push(
<ListArticle key={article.Image+article.URL}
title={article.enTitle}
image={article.Image+"?h=100&mode=crop&scale=down"}
link={JSON.stringify(article.HasLandingPage).toUpperCase()=="TRUE" ? "/en/"+article.URL : "/" + article.URL}
desc={article.enDescription}
kind={article.AddInfo.includes("SKU") ? " Product" : (article.AddInfo.includes("resource") ? " Resource" : " Page")} />
);//push
} //limit check
});//map
return (
<div className="search-page-listbox">
{liArt}
</div>
);
}
}
If i got you right, you want to create multiple lists while each list shows items of another"AddInfo".
First, I would recommend to separate your task into three components (instead of two):
First component is the ListArticle which will be the list item,
Second will be the component List -> that will receive the list you want to show (after they have been filtered),
Last component will be ListContainer -> this one will hold multiple lists (as many as the options of AddInfo).
Then, in ListContainer you can go over all unique AddInfo, and create List component for every option - passing only filtered items:
ListArticle.js
import React from 'react';
const ListArticle = (props) =>{
return (
<div className="card">
<div className="search-img-lft">
<a href={props.link} target="_blank">
<img src={props.image} alt="" />
</a>
</div>
<div className="search-imgcont-rgt">
<a href={props.link} target="_blank">
<h3>
{props.title}
{props.kind} // just to see if kind works
</h3>
<p>{props.desc}</p>
</a>
{props.link}
</div>
</div>
);
}
export default ListArticle;
List.js
import React from 'react';
import ListArticle from './ListArticle';
export default class List extends React.Component {
render() {
return (
this.props.list.map(article => <ListArticle key={article.Image + article.URL}
title={article.enTitle}
image={article.Image + "?h=100&mode=crop&scale=down"}
link={JSON.stringify(article.HasLandingPage).toUpperCase() == "TRUE" ? "/en/" + article.URL : "/" + article.URL}
desc={article.enDescription}
kind={article.AddInfo.includes("SKU") ? " Product" : (article.AddInfo.includes("resource") ? " Resource" : " Page")} />
)
)
}
}
ListContainer.js
import React from 'react';
import List from './List';
class ListContainer extends React.Component {
constructor(props){
super(props);
}
render() {
let lists = {};
let searchText = this.props.searchText;
if(this.props){
let filteredList = this.props.list.filter(article=>(article.enTitle + " " + article.URL + " " + article.enDescription + " " + article.AddInfo).toLowerCase().replace(/[^a-z0-9]/g, '').indexOf(searchText)!==-1);
filteredList && filteredList.forEach(u => {
if(lists[u.AddInfo]===undefined) lists[u.AddInfo]=[];
lists[u.AddInfo].push(u);
});
}
return(
Object.keys(lists).map(function(key, index) {
return <List list={lists[key]} />
})
)
}
}
export default ListContainer;
Usage:
<ListContainer list={list} searchText={searchText} />
Hope it helped :)
I'd return something like this from your List class (I tried explaining my thought process in comments inside the code):
return (<React.Fragment>
{
// second (kinda), I'd convert the inside generated collection (object) into an array
// -> where the array elements are now ["AddInfo type", [array of elements with that type]]
Object.entries(
// first, convert the list into an object, collecting each type of "AddInfo" into
// -> a unique property, and storing all objects w/ that type in an array
this.props.list.reduce((output, u) => {
if (!output[u.AddInfo]) output[u.AddInfo] = [];
output[u.AddInfo].push(u);
return output
}, {})
)
// third, I'd map the new array of ["AddInfo type", [array of elements with that type]] into
// -> the jsx output you want, like this:
.map(([type, array]) => {
return <div className="search-page-listbox">
{array.map((article, i) => {
// ... everything inside your ".map((article...)" function can go here
})}
</div>
})
}
</React.Fragment>)
A few notes:
You can replace var i = 0 and i++ lines with the i index that automatically comes from the second parameter in the map function (see my version of array.map((article, i) => ...)
If you haven't seen things like array destructuring (ex: .map(([type, array]) => ...)) let me know, I can explain. It's a pretty shnazzy thing you can do to save some lines.
My first step was to figure out how to create an object container which holds sorted data based on AddInfo - that's why my // first comment comes technically after the // second comment. Hope that makes sense.
Let me know if you have questions or if there was a typeo that's breaking my code. I haven't tested it obviously since I don't have your react code / variables.

React--Div exists, but is empty & more problems

I'm using the code below to pull in a list of data from a JSON file in order to populate a webpage with News. However, with what I have, the div is empty when I inspect it, and I'm not sure why. When I attempt other solutions, I get errors or the same output.
const newsList = labNewsJson['news']
class News extends Component {
render() {
const news = newsList.map((newsItem) => {
<div>{newsItem}</div>
});
return (
<div className='container'>
<h1>Lab News</h1>
<div>{news}</div>
</div>
);
}
}
export default News;
You need to add a return to your map function.
const news = newsList.map((newsItem, index) => {
return <div key={index}>{newsItem.title}</div>
});
When you are using {}, map function does not return anything. You have two options:
1- Try to use () instead of {}:
const news = newsList.map((newsItem) => (
<div>{newsItem}</div>
))
2- Return the item in every iteration:
const news = newsList.map((newsItem) => {
return <div>{newsItem}</div>
})

Extract Value from Json string in Ionic 2

I am using OAuth2 for authorization in my Ionic 2 App and the decoded token response(which I am getting from the BASE64.decode() function) is like the below(key-value form).I am storing it in a variable called 'tokendata' of type 'any'. Now I want to extract values from this decoded token. Now if I simply do 'tokendata.personnelnbr', it is not working. Also if I do a 'json.parse(tokendata) or a json.parse('tokendata'), store it in another variable say 'myVar' and then try to access 'myVar.personnelnbr', then also it is not working. Please help with the solution!
{
"client_id":"xxx",
"scope":"user_profile",
"sub":"yyy",
"amr":"external",
"auth_time":1499753830,
"idp":"eso_enterprise",
"upn":"yyy",
"email":"yyy",
"samaccount_name":"yyy",
"peoplekey":"1169",
"personnelnbr":"1108",
"given_name":"Deblina",
"sn":"Dutta Chowdhury",
"exp":1499,
"nbf":1499
}
The method where I am trying to access the 'personnelnbr' field is given below:
private initializeApp(): void
{
this.platform.ready().then(() => {
console.log("Before login Deblina");
/**
* Read in app configuration, get an oAuthV1 ESO token, register device with REBAR Notification Services
*/
this.configService.Initialize().subscribe(
() => this.esoService.getV2Token().subscribe(
(v2Token) => {
this.tokendata = BASE64.decode(v2Token);
alert("Token Deblina decoded: " + BASE64.decode(v2Token));
console.log("Token Deblina decoded: " + BASE64.decode(v2Token));
this.concatenatedToken = "'" +this.tokendata+ "'";
alert(this.concatenatedToken);
console.log(this.concatenatedToken);
this.myVar = JSON.parse(this.tokendata);
alert("Now:" + this.myVar.personnelnbr);
console.log("Now:" + this.myVar.personnelnbr);
this.myVar = JSON.parse(this.concatenatedToken);
alert("Now:" + this.myVar.personnelnbr);
console.log("Now:" + this.myVar.personnelnbr);
},
(error) => console.log(error),
() => { this.nav.setRoot(HomePage)}
),
(error) => console.log(error)
);
});
}
If you just want to to extract value, you can do this:
let datas = {
"client_id":"xxx",
"scope":"user_profile",
"sub":"yyy",
"amr":"external",
"auth_time":1499753830,
"idp":"eso_enterprise",
"upn":"yyy",
"email":"yyy",
"samaccount_name":"yyy",
"peoplekey":"1169",
"personnelnbr":"1108",
"given_name":"Deblina",
"sn":"Dutta Chowdhury",
"exp":1499,
"nbf":1499
};
for (let key in datas) {
console.log(key + " => " + datas[key]);
}