Map an object within an object - Reactjs - reactjs

Hi! I'm trying to render a dynamic settings items.
const settingsItemData = [
{
settingsCategory: 'Exam',
settingsCategoryItems: [
{
image: LabExamRequestIcon,
link: '/settings/exam-request',
label: 'Exam Items',
col: '4',
// offset: 4
},
{
image: LabExamRequestIcon,
link: '/settings/lab-exam-request',
label: 'Exam Request',
col: '4',
}
]
},
{
settingsCategory: 'Others',
settingsCategoryItems: [
{
image: LabExamRequestIcon,
link: '../settings/panel-exam',
label: 'Panel Exam',
col: '4'
},
{
image: UserMaintenanceIcon,
link: '/settings/user-maintenance',
label: 'User Maintenance',
col: '4'
}
]
}
]
What I want to do is display the title which is Exam on the left side. And display to the right side everything inside the settingsCategoryItems. I've mapped out the settingsCategory. But for the settingsCategoryItems, I can't seem to get render anything.
I've tried this:
const Items = settingsItemData.map((item) => (
<SettingsCard
image={item.settingsCategoryItems.image}
link={item.settingsCategoryItems.link}
label={item.settingsCategoryItems.label}
/>
))
But none of it renders. How do you usually do this in reactjs? Thank you.

Try this. you gotta also perform second map via settingsCategoryItems to make it work
const Items = settingsItemData.map((item) => (
item.settingsCategoryItems.map(el => (
<SettingsCard
image={el.image}
link={el.link}
label={el.label}
/>
))
))

you're in the right track, but you also need to map the settingsCategoryItems array in order to access the values and paint the UI that you want to show (As you can see in the following code snippet):
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class Component1 extends Component {
state = {};
render() {
const settingsItemData = [
{
settingsCategory: 'Exam',
settingsCategoryItems: [
{
image: 'LabExamRequestIcon',
link: '/settings/exam-request',
label: 'Exam Items',
col: '4',
// offset: 4
},
{
image: 'LabExamRequestIcon',
link: '/settings/lab-exam-request',
label: 'Exam Request',
col: '4',
}
]
},
{
settingsCategory: 'Others',
settingsCategoryItems: [
{
image: 'LabExamRequestIcon',
link: '../settings/panel-exam',
label: 'Panel Exam',
col: '4'
},
{
image: 'UserMaintenanceIcon',
link: '/settings/user-maintenance',
label: 'User Maintenance',
col: '4'
}
]
}
];
const rowItems = settingsItemData.map((item) => {
return <div className={`row__${item.settingsCategory}`}>
<h1>{item.settingsCategory}</h1>
{item.settingsCategoryItems.map(item2 => {
return <button>{item2.label}</button>;
})}
</div>;
});
return (
<div className="container">
<h1>Settings</h1>
{rowItems}
</div>
);
}
}
This way you could render all the data that you want (Then you could refactor this in order to use the SettingsCard component).

item.item.settingsCategoryItems is an array, not an object, so as a minimum you need to iterate the settingsCategoryItems as well.
And your arrow functions looks a bit weird, it should be
(item) => { ... }
and not
(item) => (....)
This
const Items = settingsItemData.map((item) => {
<SettingsCard
image={item.settingsCategoryItems.image}
link={item.settingsCategoryItems.link}
label={item.settingsCategoryItems.label}
/>
})
could/should become some thing like
const Items = settingsItemData.map((outerItem) => {
outerItem.map((item) => {
<SettingsCard
image={item.image}
link={item.link}
label={item.label}
/>
})
})
I can't test it, but hope you get the general idea..

Related

React state not updating on second index

I have nested objects as described below and updating states.
`
interface BookState {
name: string
authors: AuthorState[]
}
interface AuthorState {
name: string
}
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial)
// Add new empty author; which will later be filled from textfields
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { } as AuthorState
let authors = [...bookValues[bookIndex].authors, newAuthor]
let newBookState = update(bookValues, { [bookIndex]: { authors: { $set: authors } } })
setBookValues(newBookState) // ** edited
}
// somewhere i populate bookValues as:
bookValues = [
{name: "Book-1", authors: [{name: "Author-1"}] },
{name: "Book-2", authors: [{name: "Author-1"}, {name: "Author-2"}]}
]
`
When I add an author, suppose in "Book-1" index 0, I call the onClickAddAuthor(0), the state updates and UI updates. But when I add an author, suppose in "Book-2" index 1, i call the onClickAddAuthor(1), the state value can be seen updating when printing to console but the UI does not update. I am using https://github.com/kolodny/immutability-helper to update the state.
I expect to add a new empty author on index-1 as well, which should update the state and UI. I tried making deep copies of the book Values and updating the state with that, but it is not working. If it is working in index 0, it should work on other indexes (1, 2, 3 .. ) as well. I am not able to understand.
I tested the posted code with 4 items in bookValues, it seems that the onClickAddAuthor is working as expected. Perhaps the output logic could be checked to see if it updates correctly.
Simple test demo on: stackblitz
import { useState } from 'react';
import './App.css';
import update from 'immutability-helper';
interface AuthorState {
name: string;
}
interface BookState {
name: string;
authors: AuthorState[];
}
const bookStateInitial = [
{ name: 'Book-1', authors: [{ name: 'Author-1' }] },
{ name: 'Book-2', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
{ name: 'Book-3', authors: [{ name: 'Author-1' }] },
{ name: 'Book-4', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
];
function App() {
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial);
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { name: 'Test Author' } as AuthorState;
let authors = [...bookValues[bookIndex].authors, newAuthor];
let newBookState = update(bookValues, {
[bookIndex]: { authors: { $set: authors } },
});
setBookValues(newBookState);
};
return (
<main className="App">
<section>
{[0, 1, 2, 3].map((item) => (
<button key={item} onClick={() => onClickAddAuthor(item)}>
{`Test: add author for Book-${item + 1}`}
</button>
))}
</section>
<ul>
{bookValues.map((book) => (
<li key={book.name}>
{`name: ${book.name}, authors: ${book.authors
.map((author) => author.name)
.join(', ')}`}
</li>
))}
</ul>
</main>
);
}
export default App;

Is this an Array of Object BUG? Ionic v6 React

As the image shows, it only renders one array of objects.
How to reproduce:
Create a Blank Template and paste this code on ExploreContainer.tsx:
import {
IonCard,
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonCardContent,
} from '#ionic/react'
import { useEffect, useState } from 'react'
import './ExploreContainer.css'
interface ContainerProps {}
interface TestArrayObject {
key: string
id: string
name: string
age: number
}
const ExploreContainer: React.FC<ContainerProps> = () => {
const [testArray, setTestArray] = useState<TestArrayObject[]>([])
const arraySample: TestArrayObject[] = [
{
key: '1',
id: '12345',
name: 'Jack',
age: 40,
},
{
key: '2',
id: '67890',
name: 'Black',
age: 30,
},
]
useEffect(() => {
arraySample.map((arr: TestArrayObject) => {
setTestArray([...testArray, arr])
})
}, [])
const listArray = testArray.map((arr) => {
return (
<IonCard>
<IonCardHeader>
<IonCardSubtitle>{arr.id}</IonCardSubtitle>
<IonCardTitle>{arr.name}</IonCardTitle>
</IonCardHeader>
<IonCardContent>
Keep close to Nature's heart... {arr.age}
</IonCardContent>
</IonCard>
)
})
return <>{ listArray }</>
}
export default ExploreContainer
I'm trying to figure out the solution, but happens that i`m more than 24hours trying to figure out and nothing. could someone help?

Dynamic React child components not rendering in UI based on JSON Config

I am trying to render React Components based on a JSON response from a CMS system, but I can't seem to render the child item components. Can anyone tell me what I am missing/not understanding here?
Here is my code...
// ComponentRenderer.tsx
export const ComponentsHandler: { [key: string]: any } = {
test: Test,
};
export default function ComponentRenderer(config: IBlock): JSX.Element {
if (typeof ComponentsHandler[config.component] !== 'undefined') {
return React.createElement(
ComponentsHandler[config.component],
{
key: config.id,
...config,
},
config.content &&
(typeof config.content === 'string'
? config.content
: config.content.map((c: IBlock) => ComponentRenderer(c)))
);
}
return React.createElement(() => (
<div>
Error: Unable to load <strong>{config.component}</strong> component.
</div>
));
}
Here is the JSON object I am trying to render:
blocks: [
{
id: '1',
component: BlockType.Test,
content: [],
},
{
id: '2',
component: BlockType.Test,
content: [
{
id: '3',
component: BlockType.Test,
content: [],
},
{
id: '4',
component: BlockType.Test,
content: 'this is a string',
},
],
}
]
export interface IBlock {
id: string;
component: BlockType;
content: IBlock[] | string;
}
export enum BlockType {
Test = 'test',
Hero = 'hero',
Video = 'video',
Menu = 'menu',
Navigation = 'navigation',
Testimonial = 'testimonial',
Card = 'card',
CarTile = 'carTile',
HorizontalScrollSection = 'horizontalScrollSection',
Div = 'div',
Section = 'section',
}
Here is the "Test" component I am looking to render in the JSON response
// TestComponent.tsx
export default function Test(props: IBlock) {
return props.content && <p>{props.id}</p>;
}
// UI HomePage
export default function HomePage() {
return (
mockData &&
mockData.blocks.map((block: IBlock) => (
<ComponentRenderer {...block} key={block.id}></ComponentRenderer>
))
);
}
I would expect the browser to render
1
2
3
4
But instead, I only get the top-level JSON items.
1
2
You created a really cool react recursive structure. A few days ago I tried to do something similar but gave up... I will use yours as an inspiration.
Check if this changes can help you:
IBlock:
export interface IBlock {
id: string;
component: BlockType;
content: IBlock[] | string;
children?: React.ReactNode;
}
TestComponent.tsx
export default function Test(props: IBlock) {
return (
props.content && (
<p>
{props.id} {props.children}
</p>
)
);
}

How can I expand the option without using the arrow button in antd select?

I would like to know if this is possible in antd tree select. As you can see in the image. How can I expand the option without using the arrow button? I just want to click the word "Expand to load" and then drop down options will show.
I was playing around with their code in codesandbox.io.
This is what I came up with: https://codesandbox.io/s/loving-frog-pr1qu?file=/index.js
You'll have to create a span node when populating the title of your tree elements, where the span elements will listen for a click event.
const treeData = [
{
title: <span onClick={() => expandNode("0-0")}>0-0</span>,
key: "0-0",
children: [
{
title: <span onClick={() => expandNode("0-0-0")}>0-0-0</span>,
key: "0-0-0",
children: [
{
title: "0-0-0-0",
key: "0-0-0-0"
},
{
title: "0-0-0-1",
key: "0-0-0-1"
},
{
title: "0-0-0-2",
key: "0-0-0-2"
}
]
},
{
title: <span onClick={() => expandNode("0-0-1")}>0-0-1</span>,
key: "0-0-1",
children: [
{
title: "0-0-1-0",
key: "0-0-1-0"
},
{
title: "0-0-1-1",
key: "0-0-1-1"
},
{
title: "0-0-1-2",
key: "0-0-1-2"
}
]
},
{
title: "0-0-2",
key: "0-0-2"
}
]
},
{
title: <span onClick={() => expandNode("0-1")}>0-1</span>,
key: "0-1",
children: [
{
title: "0-1-0-0",
key: "0-1-0-0"
},
{
title: "0-1-0-1",
key: "0-1-0-1"
},
{
title: "0-1-0-2",
key: "0-1-0-2"
}
]
},
{
title: "0-2",
key: "0-2"
}
];
Then use a custom function to set the expandedKeys state and pass it as a prop to the Tree component. I tried using the filter method on the previous state for removing keys but it kept giving me an error so I fell back to using the for loop.
const expandNode = (key) => {
setAutoExpandParent(false);
setExpandedKeys((prev) => {
const outArr = [];
if (prev.includes(key)) {
for (let i = 0; i < prev.length; i++) {
if (prev[i] !== key) {
outArr.push(prev[i]);
}
}
return outArr;
} else {
prev.push(key);
return prev;
}
});
};
Note: I used antd's "Controlled Tree" example as the template and progressed from there.
Update
As I was using this solution for a project of mine, I found out a more robust method.
Instead of individually overriding the title properties on the data array, it is possible to override the titleRender prop on the tree component. titleRender is available from antd v.4.5.0.
Style the span tags as inline-block with width and height set to 100%. This makes the entire node block (instead of just the span text) listen for the onClick event when the tree has a prop of blockNode={true}.
Got rid of the for loop and successfully used filter method instead in the toggle expand method. This is more performant than looping.
I created a custom ClickExpandableTree component.
import React, { useState } from "react";
import { Tree } from "antd";
const ClickExpandableTree = props => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const toggleExpandNode = key => {
setExpandedKeys(prev => {
const outArr = [...prev];
if (outArr.includes(key)) {
return outArr.filter(e => e !== key);
} else {
outArr.push(key);
return outArr;
}
});
setAutoExpandParent(false);
};
const onExpand = keys => {
setExpandedKeys(keys);
setAutoExpandParent(false);
};
return (
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
titleRender={record => (
<span
key={record.key}
onClick={() => toggleExpandNode(record.key)}
style={{ display: "inline-block", width: "100%", height: "100%" }}
>
{record.title}
</span>
)}
{...props}
/>
);
};
export default ClickExpandableTree;

Getting error while showing hiding React Table columns - React JS

I am working on React Table. I am basically a beginner in React. I have a dashboard page where I display a React Table of 8 columns. I have a customize button which will open a popup page, this popup page has 8 check boxes allows me to show/hide those React columns. Initially all the check boxes in this popup page is set to true. When I uncheck a column that particular column get disabled.
There are images in the end to see what I am trying to do.
I will be using this logic for show hide columns (this question was asked by me two days back) -
How to show and hide some columns on React Table?
The React Table data is like this
const columns = [
{
Header: 'Column 1',
accessor: 'firstName',
},
{
Header: 'Column 2',
accessor: 'firstName',
},
{
Header: 'Column 3',
accessor: 'firstName',
},
{
Header: 'Column 4',
accessor: 'firstName',
},
{
Header: 'Column 5',
accessor: 'firstName',
},
{
Header: 'Column 6',
accessor: 'firstName',
},
{
Header: 'Column 7',
accessor: 'firstName'
},
{
Header: 'Column 8',
accessor: 'firstName',
}
];
The start of the dashboard page
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterState: {},
searchText: '',
isFilterOpen: false,
isCustomizedOpen: false,
isFiltered: false,
isSearched: false,
searchedTableData: [],
filteredTableData: [],
};
this.handleCustClickinv = this.handleCustClickinv.bind(this);
}
This is my code in the render function of my dashboard page for showing the customize button (this is written in parent dashboard page)
{this.state.isCustomizedOpen && <CustomizedView
filterState={this.state.filterState}
applyFilter={(values, clear) => { this.applyFilters(values, clear); }}
/>}
This is the code for the customize button (this is written in parent dashboard page)
<div className="custom-div-dashboard" onClick={() => { this.handleCustClickinv(); }}>
<div className='customize-view-dashboard'>Customized View </div>
</div>
This is function to handle the click on customize button (this is written in parent dashboard page)
handleFilterClickinv() {
if(this.state.isCustomizedOpen) {
this.setState({ isCustomizedOpen: false });
}
const currentState = this.state.isFilterOpen;
this.setState({ isFilterOpen: !currentState });
}
This is my entire popup page which will have 8 check boxes
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ActionCreators } from '../../../actions';
import './enqCustomizedView.scss';
import ButtonComponent from '../../shared/button/ButtonComponent';
import { CheckBox } from '../../shared/chkbox/CheckBox';
class CustomizedView extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, value: 'Column 1', isChecked: true },
{ id: 2, value: 'Column 2', isChecked: true },
{ id: 3, value: 'Column 3', isChecked: true },
{ id: 4, value: 'Column 4', isChecked: true },
{ id: 5, value: 'Column 5', isChecked: true },
{ id: 6, value: 'Column 6', isChecked: true },
{ id: 7, value: 'Column 7', isChecked: true },
{ id: 8, value: 'Column 8', isChecked: true },
]
};
this.handleCheckChildElement = this.handleCheckChildElement.bind(this);
}
handleClick() {
this.setState({ isChecked: !this.state.isChecked });
}
handleCheckChildElement(event) {
//let items = this.state.items;
let { items } = this.state;
items.forEach(items = () => {
if(items.value === event.target.value) {
items.isChecked = event.target.checked;
}
});
this.setState({ items });
const column1checked = items[0].isChecked;
console.log('column1checked ' + column1checked);
const column2checked = items[1].isChecked;
console.log('column2checked ' + column2checked);
const column3checked = items[2].isChecked;
console.log('column3checked ' + column3checked);
const column4checked = items[3].isChecked;
console.log('column4checked ' + column4checked);
const column5checked = items[4].isChecked;
console.log('column5checked ' + column5checked);
const column6checked = items[5].isChecked;
console.log('column6checked ' + column6checked);
const column7checked = items[6].isChecked;
console.log('column7checked ' + column7checked);
const column8checked = items[7].isChecked;
console.log('column8checked ' + column8checked);
}
render() {
return (
<div className='popup-page-custom' >
<div className='bottomBar'>
<ButtonComponent
text='Apply'
className='activeButton filterMargin'
width='100'
display='inline-block'
onClick={() => { this.props.applyFilter(this.state, false); }}
/>
<ButtonComponent
text='Clear Filter'
className='greyedButton clear-filter'
width='100'
display='block'
marginTop='60'
onClick={() => { this.props.applyFilter(this.state, true); }}
/>
</div>
<div>
<div className='data-points-text'>
<span> Columns </span>
</div>
<div className="App">
<ul>
{
this.state.items.map((item, i) => {
return (<div key={i} ><CheckBox handleCheckChildElement={this.handleCheckChildElement} {...item} /></div>);
})
}
</ul>
</div>
</div>
</div>
);
}
}
CustomizedView.propTypes = {
applyFilter: PropTypes.func.isRequired
};
CustomizedView.defaultProps = {
};
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomizedView);
And ultimately this is my checkbox page
import React from 'react';
import PropTypes from 'prop-types';
export const CheckBox = (props) => {
// super(props);
return (
<li>
<input key={props.id} onClick={props.handleCheckChildElement} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
);
};
CheckBox.propTypes = {
id: PropTypes.string,
handleCheckChildElement: PropTypes.func,
isChecked: PropTypes.bool,
value: PropTypes.string,
};
CheckBox.defaultProps = {
id: '',
handleCheckChildElement: null,
isChecked: null,
value: '',
};
export default CheckBox;
This is a very basic (ugly) style of my dashboard page and popup page
This is the error I am getting on Chrome when unchecking the checkboxes
Edit 1 - As per Alireza Yadegari's suggestion, I made a 1 line change. But I am still getting 2 errors.
Edit 2 - As per Alireza Yadegari's suggestion, I applied console.
you have to use this piece of code in your constructor
this.handleCheckChildElement = this.handleCheckChildElement.bind(this)
let { items } = { ...this.state };
this is wrong ....
firstly you destructuring array to object then saying give me items prop from given object... of course this is wrong
const { items} = this.state;
takes items prop from the state
and finally.... implement your task with foreach is bad idea...
CheckBox.defaultProps = {
id: '',
handleCheckChildElement: null,
isChecked: null, value: '',
};
i don't understand what it does. you know?
I think your project is a sample and no need for further examples.
I just say about your mistakes.
first, when you are using methods it is good to use 'bind(this)' to show react where is the method belongs.
secondly, when you are using state, react just allows you to change it in the constructor and wherever you want to change it you have to use 'setState' method (you can read the reason for this in react documentation).
finally, if you have an array in your state you have to get an array in some temp object change the temp object and then apply changes with 'setState' method. if you have more question please feel free to ask.

Resources