Dynamically create DOM in ReactJS from json string - javascript

I have following JSON string from which I need to dynamically create DOM of the form:
{
"formData": [{
"inputType": "text",
"type": "string",
"min": 10,
"label": "Enter Name:",
"objectId": "test1"
}],
"gridLayout": {
"rows": [
{
"column": [
{
"width": "4",
"id": "test1"
}
]
}
]
}
}
from the gridLayout object, I wish to create bootstrap style grid. For instance, first rowobject of the JSON's "rows" has 1 column in the columns object with width of 4. Thus, the layout for that row should be
<div class="row">
<div class="col-md-4" id="test1">
</div>
</div>
Later, using formData object of JSON, TextBox component should be appended to grid layout like so:
<div class="row">
<div class="col-md-4" id="test1">
<TextBox />
</div>
</div>
For now, I have written code to display the TextBox component as it is without the grid layout in following way.
Form.jsx:
class Form extends React.Component {
getComponent = (formObj, index) => {
let returnString;
if (formObj.inputType === 'text') {
returnString = (<TextBox key={index} />);
}
return returnString;
}
render() {
let formData = JSON.parse(this.getData()).formData;
return (
<React.Fragment> {formData.map((o, index) => this.getComponent(o, index))} </React.Fragment>
);
}
}
Thus now, how do I dynamically create the Grid Layout and insert the TextBox component in it?

A solution can be achieved by using a combination of array methods. Use Array#map to render your gridLayout divs as necessary. Use Array#find to find the props of the correct TextBox from formData.
I've simplified formData, but added more rows and columns to give you a complete picture.
const data = {
"formData": [{
"label": "Enter Name:",
"objectId": "test1"
}, {
"label": "Enter Address:",
"objectId": "test2"
}, {
"label": "Enter Number:",
"objectId": "test3"
}, {
"label": "Enter Something:",
"objectId": "test4"
}],
"gridLayout": {
"rows": [{
"column": [{
"width": "4",
"id": "test1"
}, {
"width": "4",
"id": "test2"
}]
},
{
"column": [{
"width": "6",
"id": "test3"
}, {
"width": "6",
"id": "test4"
}]
}
]
}
}
const TextBox = ({ label }) => (
<React.Fragment>
<label>{label}</label>
</React.Fragment>
);
const Form = ({ data: { formData, gridLayout } }) => {
return gridLayout.rows.map(row => {
return (
<div class="row">
{row.column.map(col => {
const textBoxProps = formData.find(data => data.objectId === col.id);
// REPLACE col- with col-md-. Done here to demonstrate layout in small snippet output area
return (
<div className={`col-${col.width} borderedCol`}>
<TextBox {...textBoxProps} />
</div>
);
})}
</div>
);
});
};
ReactDOM.render(<Form data={data} />, document.getElementById("app"));
/* ONLY for demo purposes */
.borderedCol {
border: 1px solid;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

React checkbox tree expand and check button not responding

I have this code :
render() {
let state = {
checked: [
'catc'
],
expanded: [
'cata',
'catb'
],
};
<CheckboxTree
nodes={this.props.chosenCategory.children}
checked={state.checked}
expanded={state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
/>
So I have the result like this :
But when I click on check or expand the list doesn't respond. Can anybody help me please? Thanks in advance.
After putting state outside of render() :
this.props.chosenCategory.children :
"id": 26578,
"label": "CatA",
"value": "cata",
"children": [
{
"id": 26579,
"label": "CatB",
"value": "catb",
"children": [
{
"id": 26580,
"label": "CatC",
"value": "catc",
"children": []
}
]
}
]
You need to define state outside of render method :
state = {
checked: [
'catc'
],
expanded: [
'cata',
'catb'
],
};
render() {
return (
<CheckboxTree
nodes={this.props.chosenCategory.children}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
/>
);
}

React - live search filter and search buttons

I'm new to React and I'm trying to create a live search filter plus a couple of button filters that shows images. It gets dummy JSON-like data for now.
I've managed to create the live search (as below in the code) but I'm now stuck in creating the extra filtering on top of it. I'd like to have three button that says "all", "top", "great" next to the live search field.
How can I filter specifically to look for tag names? In my example when I click on "Top" or "Great" it should filter through the original list and show the related data.
Any tip is appreciated...thanks!
import React, { useState } from 'react';
function App() {
const dataList = [
{
"id": 1,
"tag": "Top",
"name": "bubb",
"image": "../img/bubb.jpg"
},
{
"id": 2,
"name": "lin bubb",
"tag": "Great",
"image": "../img/lin.jpg"
},
{
"id": 3,
"name": "smam",
"tag": "Top",
"image": "../img/smam.jpg"
},
{
"id": 2,
"name": "jumanji",
"tag": "Top",
"image": ""
},
{
"id": 2,
"name": "fruit spin",
"tag": "Great",
"image": ""
}
];
const [searchText, setSearchText] = useState("");
const [data, setData] = useState(dataList);
// exclude column list from filter
const excludeColumns = ["id", "tag", "image"];
// handle change event of search input
const handleChange = value => {
setSearchText(value);
filterData(value);
};
// filter records by search text
const filterData2 = (value) => {
//need to filter when tag selected
}
// filter records by search text
const filterData = (value) => {
const lowercasedValue = value.toLowerCase().trim();
if (lowercasedValue === "") setData(dataList);
else {
const filteredData = dataList.filter(item => {
return Object.keys(item).some(key =>
excludeColumns.includes(key) ? false : item[key].toString().toLowerCase().includes(lowercasedValue)
);
});
setData(filteredData);
}
}
return (
<div className="App">
Search: <input
style={{ marginLeft: 5 }}
type="text"
placeholder="Type to search..."
value={searchText}
onChange={e => handleChange(e.target.value)}
/>
<span onClick={filterData2("all")}>All</span>
<span onClick={filterData2("Top")}>Top</span>
<span onClick={filterData2("Great")}>Great</span>
<div className="box-container">
{data.map((data, i) => {
return <div key={i} className="box" style={{ backgroundColor: data.color }}>
<img src={data.image}></img>
</div>
})}
<div className="clearboth"></div>
{data.length === 0 && <span>No records found to display!</span>}
</div>
</div>
);
}
export default App;

How manage a json array of attributes for display in ReactJS

So, I have to return data from an API like the one below in the UI in React. Im a bit confused about the fact that in the attributes array, all data have the same names but point to different attributes. In a position it refers to name, the other gender and phone number. Whats the best way to deal with this type of api and return each atribute in the UI?
{
"data": [
{
"type": "Other",
"place": "US",
"attributes": [
{
"displayName": "name",
"value": "Jenna"
},
{
"displayName": "Gender",
"value": "Female"
},
{
"displayName": "Phone_number",
"value": "+12346543"
}
]
}
]
}
Code I have
import React from "react";
import People from "./data.json";
const Data = () => {
return (
<div>
{People.data.map((details) => (
<div>
<p>
Type: {details.type}
</p>
<p>
place: {details.place}
</p>
/*{ <p>name: {}</p> } */
/* { <p>gender: {}</p> */ }
/* {<p>phone number: {}</p> } */
</div>
))}
</div>
);
};
export default Data;
Use the method filter in details.attributes for select specifical displayName
details.attributes.filter(x => x.displayName === 'name')[0]
since you have nested array in this kind of api response you should iterate over the first list and then for each attributes list you can iterate and get your data. hope it will help, cheers.
import React from "react";
import People from "./data.json";
const Data = () => {
return (
<div>
{People.data.map((place) => (
<div>
// place data
{place.type}
{place.place}
{place.attributes.map(attribute => (
<div>
// attributes
{attribute.displayName}
{attribute.value}
</div>
)}
</div>
))}
</div>
);
};
export default Data;
import React from "react";
import People from "./data.json";
function App() {
const people = {
"data": [
{
"type": "Other",
"place": "US",
"attributes": [
{
"displayName": "name",
"value": "Jenna"
},
{
"displayName": "Gender",
"value": "Female"
},
{
"displayName": "Phone_number",
"value": "+12346543"
}
]
}
]
}
return (
<div>
{people.data.map((details) => (
<div>
<p>
Type: {details.type}
</p>
<p>
place: {details.place}
</p>
<p>
Name: {details.attributes.filter(x => x.displayName === 'name')[0].value}
</p>
<p>
Gender: {details.attributes.filter(x => x.displayName === 'Gender')[0].value}
</p>
<p>
Phone Number: {details.attributes.filter(x => x.displayName === 'Phone_number')[0].value}
</p>
</div>
))}
</div>
);
}
export default App;

React component render dynamic content in tabs from a JSON object

In my React component I am not able to render a dynamic tab from a JSON object.
I am able to retrieve the JSON data key and the value array, but I am not able to render it in the UI.
I am using PrimeReact UI components.
https://www.primefaces.org/primereact/#/tabview
Component
export default class Report extends Component {
render() {
const { splitGroupedStartingMaterials } = this.state
return (
<div>
<TabView>
{
Object.keys(splitGroupedStartingMaterials).forEach(k => {
console.log('k : ' + k, JSON.stringify(splitGroupedStartingMaterials[k]));
return (<TabPanel header={'Family'}>
simple content here for testing
</TabPanel>);
})
}
</TabView>
</div>);
}
}
JSON Data :-
"splitGroupedStartingMaterials": {
"1": [
{
"id": 45598,
"symbol": "Mn",
"description": "Mn(NO3)2 (fr mn flake)_[10377-66-9]",
"priority": 1,
"matrices": "HNO3",
"family": "F2.0",
"splitGroup": "1"
},
{
"id": 45636,
"symbol": "Ti",
"description": "(NH4)2TiF6 (as Ti)_[16962-40-6]",
"priority": 2,
"matrices": "F- : HNO3",
"family": "F1.1",
"splitGroup": "1"
}
],
"2": [
{
"id": 45572,
"symbol": "Cr",
"description": "CrCl3 (fr Cr shot)_[10025-73-7]",
"priority": 2,
"matrices": "HCl",
"family": "F3.1",
"splitGroup": "1_2"
}
]
}
Update:-
Console Logs:-
10:46:28.769 InOrganicCreateCustomQuote.jsx:704 k : 1 [{"id":45621,"symbol":"Sc","description":"Sc2O3 (as Sc)_[256652-08-1]","priority":1,"matrices":"HNO3","family":"F2.0","splitGroup":"1"},{"id":45636,"symbol":"Ti","description":"(NH4)2TiF6 (as Ti)_[16962-40-6]","priority":2,"matrices":"F- : HNO3","family":"F1.1","splitGroup":"1"},{"id":45640,"symbol":"V","description":"V2O5 (as V)_[1314-62-1]","priority":1,"matrices":"HNO3","family":"F2.0","splitGroup":"1"}]
10:46:28.770 InOrganicCreateCustomQuote.jsx:704 k : 2 [{"id":45646,"symbol":"Zr","description":"ZrCl2O (as Zr)_[7699-43-6]","priority":1,"matrices":"HCl","family":"F3.1","splitGroup":"1_2"}]
For this code no tabs are rendered
Could you try:
export default class Report extends Component {
render() {
const { splitGroupedStartingMaterials } = this.state
return (
<div>
<TabView>
{
Object.keys(splitGroupedStartingMaterials).map(k => (
<TabPanel header={'Family'}>
simple content here for testing
</TabPanel>
))
}
</TabView>
</div>);
}
}

Passing list of values as filtertable content

I'm attempting to filter using a list of values with React.
All my "tags" have a "taglevel" to indicate their relevance.
I want it to "cancel out" tags which are the same (ie don't repeat the tag if its' the same).
I want the first row to show all tag.name with "taglevel" of 1.
I want the second row to show all tag.name with "taglevel" of 2 or more.
I am unable to show and filter on the value "tags". Possibly it is around line 145 of my codepen where I have made the error.
Here is what I am trying to achieve:
I've put this together in a codepen.
http://codepen.io/yarnball/pen/GqbyWr?editors=1010
Without success, I have now tried the following:
I tried filtering using this using:
var LevelFilter = React.createClass({
render: function(){
if (!this.props.tags) return null;
return this.props.tags.filter(tag => tag.taglevel === this.props.targetLevel).map(tag => <a onClick={this.props.onClick}>{tag.name}</a>);
}
});
Then trying to get it in my return here:
render: function(){
...
var getUniqueCategories=[];
PHOTODATA.forEach(function(el){
if(getUniqueCategories.indexOf(el.tag) === -1 ) getUniqueCategories.push(el.tag);
})
return (
<div className="overlay-photogallery">
<div className="filter-panel"><b>Tags with taglevel 1 only to be displayed</b>
{
getUniqueCategories.map(function(el,i){
var boundClick = titleToSelect.bind(null,el);
return <LevelFilter onClick={boundClick} targetLevel={1} tags={el.tag} />
})
}
<a className="resetBtn" onClick={this.resetFilter}> Reset Filter </a>
</div>
My data looks like this:
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
}
TL;DR
You have some serious issues with your array manipulation and your React components.
Remember that React advocates a specific top down structure and you should read up on it some more. Each React Component should use props as much as possible and ideally only 1 top-level component should hold state.
QUICK ways forward:
Pass all the data down and let each level filter make the list unique.
Seriously, split up your components and let them depend on props as much as possible.
Give variables meaningful names. el is not meaningful and in your case refers to PHOTO items in the PHOTODATA array, tags in a PHOTO and then you use element to mean something else again. Don't go to over the top, but at least be able to identify what the variable is supposed to do.
I've given in and made a codepen with a much updated structure. The behaviour may not be exactly what you're looking for, but look at the code and how it is organised and how information is shared and passed between components.
http://codepen.io/anon/pen/AXGGLy?editors=1010
UPDATE
To allow multiple filters two methods should be updated:
selectTag: function (tag) {
this.setState({
displayedCategories: this.state.displayedCategories.concat([tag])
});
}
tagFilter: function (photo) {
return this.props.displayedCategories.length !== 0 &&
this.props.displayedCategories.every(function(thisTag) {
return photo.tag.some(function (photoTag) {
return photoTag.id === thisTag.id &&
photoTag.taglevel === thisTag.taglevel;
});
});
},
selectTag now appends to the displayedCategories array rather than replacing it.
tagFilter now checks that at least one filter has been applied (remove this.props.displayedCategories.length !== 0 to disable this) so that it doesn't display all by default and then checks that every selected filter is present in each photo, thus making the components additive.
There are further improvements that could be made, such as to disable a level when a filter is applied at that level (one choice per level) or to show a list of applied filters, either through colour on the buttons or a tag list above the results.
(codepen updated with these latest changes)
Ok, there are a few problems with your codepen.
First, on line 137 you extract the tag array from the object:
if(getUniqueCategories.indexOf(el.tag) === -1 ) getUniqueCategories.push(el.tag);
Then, on 146 you extract it again:
return <LevelFilter onClick={boundClick} targetLevel={1} tags={el.tag} />
and again for level 2:
return <LevelFilter onClick={boundClick} targetLevel={2} tags={el.tag} />
For both of these it should be:
return <LevelFilter onClick={boundClick} targetLevel={n} tags={el} />
Which then allows another problem to manifest itself, which is that LevelFilter doesn't return a valid React component (an array is not valid).
return this.props.tags.filter(tag => tag.taglevel === this.props.targetLevel).map(tag => <a onClick={this.props.onClick}>{tag.name}</a>);
should be
return (
<div>
{
this.props.tags
.filter(tag => tag.taglevel === this.props.targetLevel)
.map(tag => <a onClick={this.props.onClick}>{tag.name}</a>)
}
</div>
);
After these changes you should have a much closer attempt to where you want to be.
There are further issues you will need to look into, things like your boundClick function won't work correctly because you only have a list of tags, not PHOTODATA.
However, just a final thought. You might want to break your React components up a little more.
For reference, here is the full code listing from the codepen:
var PHOTODATA = [{
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
},{
"title": "Karate Kid",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Kids",
"taglevel": 3,
"id": 4
}
],
"info": []
},
{
"title": "The Alchemist",
"tag": [
{
"name": "Book",
"taglevel": 1,
"id": 2
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Classic",
"taglevel": 2,
"id": 4
},
{
"name": "Words",
"taglevel": 4,
"id": 4
}
],
"info": []
}];
var PhotoGallery = React.createClass({
getInitialState: function() {
return {
displayedCategories: []
};
},
selectTag: function (tag) {
this.setState({
displayedCategories: this.state.displayedCategories.concat([tag])
});
},
resetFilter: function(){
this.setState({
displayedCategories: []
});
},
render: function(){
var uniqueCategories = PHOTODATA.map(function (photo) {
return photo.tag; // tag is a list of tags...
}).reduce(function (uniqueList, someTags) {
return uniqueList.concat(
someTags.filter(function (thisTag) {
return !uniqueList.some(function(uniqueTag) {
return uniqueTag.id === thisTag.id && uniqueTag.taglevel === thisTag.taglevel
});
})
);
}, []);
return (
<div className="overlay-photogallery">
<div className="filter-panel"><b>Tags with taglevel 1 only to be displayed</b>
<PhotoGalleryLevel level={1} tags={uniqueCategories} displayedCategories={this.state.displayedCategories} selectTag={this.selectTag} />
<a className="resetBtn" onClick={this.resetFilter}> Reset Filter </a>
</div>
<div className="filter-panel"><b>Tags with taglevel 2 only to be displayed</b>
<PhotoGalleryLevel level={2} tags={uniqueCategories} displayedCategories={this.state.displayedCategories} selectTag={this.selectTag} />
</div>
<div className="PhotoGallery">
<PhotoDisplay displayedCategories={this.state.displayedCategories} photoData={PHOTODATA} />
</div>
</div>
);
}
});
var PhotoGalleryLevel = React.createClass({
render: function () {
var filteredTags = this.props.tags.filter(function (tag) {
return tag.taglevel === this.props.level;
}.bind(this));
var disabled = this.props.displayedCategories.some(function (tag) {
return tag.taglevel === this.props.level;
}.bind(this));
return (
<div>
{filteredTags.map(function (tag){
return <PhotoGalleryButton tag={tag} selectTag={this.props.selectTag} disabled={disabled} />;
}.bind(this))}
</div>
);
}
});
var PhotoGalleryButton = React.createClass({
onClick: function (e) {
this.props.selectTag(this.props.tag);
},
render: function () {
return (
<a className={this.props.disabled} onClick={this.onClick}>{this.props.tag.name}</a>
);
}
});
var PhotoDisplay = React.createClass({
getPhotoDetails: function (photo) {
console.log(this.props.displayedCategories, photo);
return (
<Photo title={photo.title} name={photo.name} tags={photo.tag} />
);
},
tagFilter: function (photo) {
return this.props.displayedCategories.length !== 0 &&
this.props.displayedCategories.every(function(thisTag) {
return photo.tag.some(function (photoTag) {
return photoTag.id === thisTag.id &&
photoTag.taglevel === thisTag.taglevel;
});
});
},
render: function () {
return (
<div>
{this.props.photoData.filter(this.tagFilter).map(this.getPhotoDetails)}
</div>
);
}
});
var Photo = React.createClass({
getTagDetail: function (tag){
return (
<li>{tag.name} ({tag.taglevel})</li>
);
},
sortTags: function (tagA, tagB) {
return tagA.taglevel - tagB.taglevel;
},
render: function(){
return (
<div className="photo-container" data-title={this.props.title} >
{this.props.title}
<ul>
{this.props.tags.sort(this.sortTags).map(this.getTagDetail)}
</ul>
</div>
);
}
});
ReactDOM.render(<PhotoGallery />, document.getElementById('main'));
With below react component I was able to do what you are looking for,
and here's what i've done in the code,
i) from the PHOTODATA array i have created taglevel1, taglevel2 array
one the render method at the begining.
ii) show them in two rows in showLevel1, showLevel2 function.
iii) when the tag item will be click it will call handleClick function and filter the data and save it to the filterData state.
import React, { Component } from 'react';
import { pluck } from 'underscore';
class Router extends Component {
constructor(props) {
super(props);
this.state = {
filterData: [],
};
this.filterArray = [];
this.PHOTODATA = [{
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
},{
"title": "Karate Kid",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Kids",
"taglevel": 3,
"id": 4
}
],
"info": []
},
{
"title": "The Alchemist",
"tag": [
{
"name": "Book",
"taglevel": 1,
"id": 2
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Classic",
"taglevel": 2,
"id": 4
},
{
"name": "Words",
"taglevel": 4,
"id": 4
}
],
"info": []
}];
}
handleClick(item) {
const findItem = this.filterArray.indexOf(item);
if (findItem === -1) {
this.filterArray.push(item);
} else {
this.filterArray.pop(item);
}
const filterData = [];
if(this.filterArray.length) {
this.PHOTODATA.map((item) => {
const data = pluck(item.tag, 'name');
let count = 0;
// console.log(data);
this.filterArray.map((filterItem) => {
const find = data.indexOf(filterItem);
if(find !== -1) {
count++;
}
});
if(count === this.filterArray.length) {
filterData.push(item);
}
});
}
console.log(this.filterArray);
this.setState({ filterData });
}
render() {
const taglevel1 = [];
const taglevel2 = [];
this.PHOTODATA.map((item) => {
item.tag.map((tagItem) => {
if(tagItem.taglevel === 1) {
const find = taglevel1.indexOf(tagItem.name);
if(find === -1) {
taglevel1.push(tagItem.name);
}
} else {
const find = taglevel2.indexOf(tagItem.name);
if(find === -1) {
taglevel2.push(tagItem.name);
}
}
});
});
const showLevel1 = (item, index) => {
return <span onClick={this.handleClick.bind(this, item)}> {item} </span>
};
const showLevel2 = (item, index) => {
return <span onClick={this.handleClick.bind(this, item)}> {item} </span>
};
const showData = (item, index) => {
return <div>{item.title}</div>
};
return (<div>
<ul>Tag Level 1: {taglevel1.map(showLevel1)}</ul>
<ul>Tag Level 2: {taglevel2.map(showLevel2)}</ul>
<div>Movie Title: {this.state.filterData.map(showData)}</div>
</div>);
}}
and here you can see how my outputs look like

Categories

Resources