React loop through array and render instances of multiple child component - javascript

I am trying to iterate over an array and assign fields to corresponding child components.
The way I am currently doing it looks like this:
class CardExtension extends React.Component {
render() {
return (
<div>
{ this.props.value }
</div>
);
}
}
class Card extends React.Component {
render() {
return (
<div>
{ this.props.title }
</div>
);
}
}
Once child components are defined and imported, I do push new instances of these classes to a completely new array:
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
var elements=[];
for(var i=0;i<arr.length;i++){
elements.push(<Card title={ arr[i].value } />);
elements.push(<CardExtension value={ arr[i].title } />);
}
return (
<div>
{elements}
</div>
);
}
}
Is there any way to accomplish the same using the following format
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Card title={ el.value } />
<CardExtension value={ el.title } />
))}
</div>
);
}
}
#update
The problem is that whenever I do use the latest solution, I do receive following error message: Adjacent JSX elements must be wrapped in an enclosing tag (39:24)

Working solution:
import React, { Component, Fragment } from 'react';
export default class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Fragment>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</Fragment>
))}
</div>
);
}
}

The error message "Adjacent JSX elements must be wrapped in an enclosing tag" means just that: the <Card> and <CardExtension> elements need to be wrapped in a parent tag.
Now, you probably don't want to wrap the elements in a <div> (since it would create unnecessary DOM nodes), but React has a nifty thing called "Fragments", letting you group elements without extra nodes.
Here is a working solution for your example, using the fragment short syntax:
class CardContainer extends React.Component {
render() {
var arr = [{
'id':1,
'title':'title',
'value':'test_value'
}]
return (
<div>
{arr.map((el, idx) => (
<>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</>
))}
</div>
);
}
}

Related

Passing a variable to `renderMenuItemChildren` in react typeahead bootstrap

From the App component it passes the variableitem to the Todo component. From the Todo component passes to theSearchResult component. However, the `SearchResult 'component is not displayed.
Demo here: https://stackblitz.com/edit/react-e2zqvd
import {Typeahead} from 'react-bootstrap-typeahead';
class App extends Component {
constructor() {
super();
this.state = {
name: [{id:1, name: 'mario'}, {id:2, name: 'paul'}],
item: 'flower'
};
}
render() {
return (
<div>
<Todo
item = {this.state.item}
name = {this.state.name}
/>
</div>
);
}
}
class Todo extends Component {
constructor(props){
super(props);
}
render () {
return (
<div>
<Typeahead
id={'example4'}
labelKey= 'name'
multiple
options={this.props.name}
onChange={this.handleSelectPeopleToCalendar}
ref={(ref) => this._typeahead = ref}
renderMenuItemChildren={(option, props) => (
<SearchResult
key={option.id}
user={option}
item={props.item}
/>
)}
/>
</div>
)
}
}
const SearchResult = ({user, item}) => (
<div>
<p>{item}</p>
<span>{user.name}</span>
</div>
);
try
<SearchResult
key={option.id}
user={option}
item={this.props.item}
/>
and you need start type something to see this component, there have input with no borders, and without background

How to dynamically render a nested component in React?

I want to render a child element based on the state in its parent. I tried to do the following (simplified version of the code):
class DeviceInfo extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: "General",
};
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
navToggle(tab) {
this.setState({ currentTab: tab });
}
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
render() {
return (
<React.Fragment>
<div className="container">
<Nav className="nav-tabs ">
<NavItem>
<NavLink
className={this.state.currentTab === "General" ? "active" : ""}
onClick={() => {
this.navToggle("General");
}}
>
General
</NavLink>
</div>
{ this.tabsMap[this.state.currentTab] }
</React.Fragment>
);
}
}
But it did not work properly. Only when I put the contents of the tabsMap straight in the render function body it works (i.e. as a react element rather then accessing it through the object). What am I missing here?
Instead of making tabsMap an attribute which is only set when the component is constructed, make a method that returns the object, and call it from render:
getTabsMap() {
return {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
};
render() {
...
{ this.getTabsMap()[this.state.currentTab] }
...
}
You defining instance property with this.tabsMap (should be syntax error):
export default class App extends React.Component {
tabsMap = { General: <div>Hello</div> };
// Syntax error
// this.tabsMap = { General: <div>World</div> };
render() {
// depends on props
const tabsMapObj = {
General: <div>Hello with some props {this.props.someProp}</div>
};
return (
<FlexBox>
{this.tabsMap['General']}
{tabsMapObj['General']}
</FlexBox>
);
}
}
Edit after providing code:
Fix the bug in the constructor (Note, don't use constructor, it's error-prone, use class variables).
Moreover, remember that constructor runs once before the component mount if you want your component to be synchronized when properties are changed, move it to render function (or make a function like proposed).
class DeviceInfo extends Component {
constructor(props) {
...
// this.props.id not defined in this point
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={props.id}
/>
</React.Fragment>
}
render() {
// make a function to change the id
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
return (
<>
{ this.tabsMap[this.state.currentTab] }
</>
);
}
}
I think it's a this binding issue. Not sure if your tabsMap constant should have this in front of it.
Alternative answer... you can inline the expression directly in the render as
{ this.state.currentTab === 'General' && <GeneralCard id={this.props.id} /> }

Add class after action onClick React / Redux

I have table and action icon 'delete' for example:
http://joxi.ru/gmvzWx4CxKbX5m
After click on icon, in my Redux Action i get TR and add class 'adm-pre-removed'
import * as types from '../constant/domain.const.js';
export function deleteDomain(event){
return (dispatch, getState) => {
(async () => {
let domainIdElement = event.target.closest('tr'),
id = domainIdElement.getAttribute('data-id');
domainIdElement.classList.add('adm-pre-removed');
setTimeout(() => {
dispatch({
type: types.REMOVE_DOMAIN_SUCCESS,
id: id
})
}, 3000)
})()
}
}
The problem is after the dispatch state is triggered and the render happens but the class remains.
I know that this is not a react way. And I really do not want to create a property in an article and to control this class is too much code. And to all this class should appear when clicking and disappear when the state has changed.
You can of course create another action and order the event onMouseDown but this is also a lot of code.
Which option is correct and how best to do it?
Render (Controll view)
function mapStateToProps (state) {
return {...state.menu}
}
function mapDispatchToProps(dispatch){
return {
...bindActionCreators({
...listMenu,
...deleteMenu,
...addMenu,
...removeError
}, dispatch),
dispatch
}
}
class Menu extends React.Component {
constructor (props, context) {
super(props, context);
}
componentDidMount() {
this.props.listMenu()
}
componentDidUpdate(){
//FormHelper.reactReRender('.adm-domain-form');
}
render() {
return (<div>
{this.props.errorMessage ? <Notification close={this.props.removeError} errorMessage={this.props.errorMessage} /> : ""}
<Sidebar />
<MenuPreview {...this.props} />
</div>);
}
}
Menu.contextTypes = {
router: PropTypes.object
}
export default connect(mapStateToProps, mapDispatchToProps)(Menu);
Render Component:
class MainEventsView extends React.Component {
render(){
return (
<div className="adm-content">
<Header />
<div className="container-fluid adm-card-head">
<Add {...this.props} />
<List {...this.props} />
</div>
</div>
);
}
}
export default MainEventsView;
Render List:
class ListMenu extends React.Component {
render(){
return(
<div className="col-xs-12 col-sm-12 col-md-6 col-lg-6">
<div className="adm-card adm-card-table">
<div className="table-responsive">
{this.props.menu && this.props.menu.length > 0 ?
<Table {...this.props} /> :
<p className="adm-card-table__text-no-available turn-center">Available menu are not found</p>
}
</div>
</div>
</div>
)
}
}
export default ListMenu;
Render Table:
class Table extends React.Component {
render () {
return (
<table className="adm-users-table__wrapper-table">
<Thead {...this.props} />
<Tbody {...this.props} />
</table>
);
}
}
export default Table;
Render Tbody:
class Cell extends React.Component {
render () {
let usersTemplate = this.props[this.props.name].map((item, i) => {
return (
<tr key={i} data-id={item.id}>
{this.template(item)}
</tr>
);
});
return (
<tbody>
{usersTemplate}
</tbody>
);
}
template (items) {
let keyArray = Object.keys(items),
uniqKey,
field,
userCell = keyArray.map((item, i) => {
uniqKey = i;
field = items[item] instanceof Object ? _.values(items[item]).join(', ') : items[item];
return (
<td key={i} className="adm-users-table__wrapper-table-data relative-core">{field}</td>
);
});
userCell.push(<Actions key={uniqKey + 1} id={items.id} {...this.props} />)
return userCell;
}
}
export default Cell;
Maybe, it could be. React renders elements in virtual dom and update changed elements in real-dom.
So your class is remained still.
I think that you have to figure out another way.

React Iterate and insert components based on count of array

So say i have an array
var arr=["one", "two", "three", "four"];
and i have a component
CardContainer
class CardContainer extends React.Component {
render() {
return (
<div>
<Card/>
</div>
);
}
}
what im trying to do is
create a number of Card components based on length/count of array "arr",
and also
set the text of the div in the Card component from the array.
class Card extends React.Component {
render() {
return (
<div>
<!--Print arr[i] val using this.props? -->
</div>
);
}
}
So my output will be 4 cards with,
array values printed on each card individually.
This is what ive come up with unsucessfully
class CardContainer extends React.Component {
render() {
var arr=["one", "two", "three", "four"];
var elements=[];
for(var i=0;i<arr.length;i++){
elements.push(<Card value=arr[i]/>);
}
return (
<div>
{elements}
</div>
);
}
}
You were close, only forgot to populate the elements array with the Cards, so it's still empty after the loop finishes. And while using map as others suggest is the most idiomatic way to do it in React it still simply generates an array of components which can be generated using a for loop as well:
https://jsfiddle.net/mn0jy5v5/
class Card extends React.Component {
render() {
return (
<div>
{ this.props.value }
</div>
);
}
}
class CardContainer extends React.Component {
render() {
var arr=["one", "two", "three", "four"];
var elements=[];
for(var i=0;i<arr.length;i++){
// push the component to elements!
elements.push(<Card value={ arr[i] } />);
}
/* the for loop above is essentially the same as
elements = arr.map( item => <Card value={ item } /> );
The result is an array of four Card components. */
return (
<div>
{elements}
</div>
);
}
}
You almost got it right!
You missed the curly-braces around arr[i]. So a working code would look like:
class CardContainer extends React.Component {
render() {
var arr=["one", "two", "three", "four"];
var elements=[];
for(var i=0;i<arr.length;i++){
elements.push(<Card value={arr[i]} />);
}
return (
<div>
{elements}
</div>
);
}
}
However I suggest you use map() to iterate through the array:
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results
So try this:
class CardContainer extends React.Component {
render() {
var arr=["one", "two", "three", "four"];
return (
<div>
{arr.map(item => <Card key={item} value={item} />)}
</div>
);
}
}
You can then access your value inside Card like this:
class Card extends React.Component {
render() {
return (
<div>
{this.props.value}
</div>
);
}
}
You can also rewrite your Card component into a stateless functional component, like this:
const Card = (props) =>
return (
<div>
{props.value}
</div>
);
}
if you want it more compact:
const Card = props => <div>{props.value}</div>
You need to use .map method https://facebook.github.io/react/docs/lists-and-keys.html
render() {
var arr=["one", "two", "three", "four"];
return (
<div>
// curly braces for parenthesis
{
arr.map((item, index) => {
<Card value={item} key={index} />
});
}
</div>
);
}
try this using map
class CardContainer extends React.Component {
render() {
var arr=["one", "two", "three", "four"];
return (
<div>
{
arr.map(function(value,i)
{
return <Card value={value} key={i}/>
}
)
}
</div>
);
}
}
You can try passing an array to the cards class and dynamically generate elements accordingly.
https://jsfiddle.net/69z2wepo/83859/
class CardContainer extends React.Component {
constructor(props){
super(props);
this.props=props;
this.arr=["one", "two", "three", "four"];
}
render() {
return (
<div>
<Card arr={this.arr}/> //pass your props value
</div>
);
}
}
your cards class here
class Card extends React.Component {
constructor(props){
super(props);
this.props=props; //set props value
}
render() {
return (
<div>
{
// itterate through the array
this.props.arr.map((value,index)=>(
<div key={index}>{value}</div>
))
}
</div>
);
}
}

React: Update className of this.props.children

I'm creating a component where I'd like to update the className prop of this.props.children.
To accomplish this I'm using the React.Children utilities. React.Children.map(children) to create a new array from the children and React.cloneElement(child) to manipulate the child props.
The map and cloneElement work in some ways. I can add props and change the children of the individual elements. But props.className is not propagated to the class of the child when rendered. That is, I see the update to props.className in the new children array, but the rendered children don't contain the new props.className.
Is this the incorrect way to update props.className for this.props.children? If so, what is the correct method?
class List extends React.Component {
constructor(props) {
super(props);
this.items = React.Children.map(this.props.children, item => {
const className = `${item.props.className} test`;
const props = { ...item.props, className: className };
return React.cloneElement(item, props, "TEST");
});
}
render() {
console.log(this.items);
return (
<ul>
{this.items}
</ul>
);
}
}
class ListItem extends React.Component {
render() {
return (
<li className="item">
{this.props.children}
</li>
);
}
}
class Application extends React.Component {
render() {
return (
<List>
<ListItem ref="item1" className="item">A</ListItem>
<ListItem ref="item2" className="item">B</ListItem>
<ListItem ref="item3" className="item">C</ListItem>
<ListItem ref="item4" className="item">D</ListItem>
</List>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react-dom.min.js"></script>
<div id="app"></app>
It's propagated... You're just not using it.
convert
<li className="item">
to
<li className={this.props.className}>
class List extends React.Component {
constructor(props) {
super(props);
this.items = React.Children.map(this.props.children, item => {
const className = `${item.props.className} test`;
const props = { ...item.props, className: className };
return React.cloneElement(item, props, "TEST");
});
}
render() {
return (
<ul>
{this.items}
</ul>
);
}
}
class ListItem extends React.Component {
render() {
return (
<li className={this.props.className}>
{this.props.children}
</li>
);
}
}
class Application extends React.Component {
render() {
return (
<List>
<ListItem ref="item1" className="item">A</ListItem>
<ListItem ref="item2" className="item">B</ListItem>
<ListItem ref="item3" className="item">C</ListItem>
<ListItem ref="item4" className="item">D</ListItem>
</List>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById("app"));
.test{
background-color: red
}
.test {
color: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.0/react-dom.min.js"></script>
<div id="app"></app>

Categories