React Iterate and insert components based on count of array - javascript

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>
);
}
}

Related

React loop through array and render instances of multiple child component

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>
);
}
}

Loop over all instances of component, log each state

I'm building out a simple drum machine application using ReactJS and could use some help understanding how to loop through all instances of a component while outputting each instance's state.
The application UI shows 16 columns of buttons, each containing 4 unique drum rows. There is a "SixteenthNote.js" component which is essentially on column containing each "Drum.js" instance. In the "DrumMachine.js" module, I am outputting "SixteenthNote.js" 16 times to display one full measure of music. When you click on a drum button, that drum's value is pushed into the SixteenthNote' state array. This is all working as intended.
The last part of this is to create a "Play.js" component which, when clicked, will loop through all of the SixteenthNote instances and output each instance's state.
Here is the "DrumMachine.js" module
class DrumMachine extends Component {
constructor(props) {
super(props);
this.buildKit = this.buildColumns.bind(this);
this.buildLabels = this.buildLabels.bind(this);
this.buildAudio = this.buildAudio.bind(this);
this.state = {
placeArray: Array(16).fill(),
drumOptions: [
{type: 'crash', file: crash, title: 'Crash'},
{type: 'kick', file: kick, title: 'Kick'},
{type: 'snare', file: snare, title: 'Snare'},
{type: 'snare-2', file: snare2, title: 'Snare'}
]
}
}
buildLabels() {
const labelList = this.state.drumOptions.map((sound, index) => {
return <SoundLabel title={sound.title} className="drum__label" key={index} />
})
return labelList;
}
buildColumns() {
const buttonList = this.state.placeArray.map((object, index) => {
return <SixteenthNote columnClassName="drum__column" key={index} drumOptions={this.state.drumOptions}/>
});
return buttonList;
}
buildAudio() {
const audioList = this.state.drumOptions.map((audio, index) => {
return <Audio source={audio.file} drum={audio.type} key={index}/>
})
return audioList;
}
render() {
return (
<div>
<div className={this.props.className}>
<div className="label-wrapper">
{this.buildLabels()}
</div>
<div className="drum-wrapper">
{this.buildColumns()}
</div>
</div>
<div className="audio-wrapper">
{this.buildAudio()}
</div>
</div>
)
}
}
Here is "SixteenthNote.js" module
class SixteenthNote extends Component {
constructor(props) {
super(props);
this.buildColumn= this.buildColumn.bind(this);
this.buildDrumOptions = this.buildDrumOptions.bind(this);
this.updateActiveDrumsArray = this.updateActiveDrumsArray.bind(this);
this.state = {
activeDrums: []
}
}
buildDrumOptions() {
return this.props.drumOptions;
}
updateActiveDrumsArray(type) {
let array = this.state.activeDrums;
array.push(type);
this.setState({activeDrums: array});
}
buildColumn() {
const placeArray = this.buildDrumOptions().map((button, index) => {
return <Drum buttonClassName="drum__button" audioClassName="drum__audio" type={button.type} file={button.file} key={index} onClick={() => this.updateActiveDrumsArray(button.type)}/>
})
return placeArray;
}
render() {
return (
<div className={this.props.columnClassName}>
{this.buildColumn()}
</div>
)
}
}
Here is the "Drum.js" module
class Drum extends Component {
constructor(props) {
super(props);
this.clickFunction = this.clickFunction.bind(this);
this.state = {
clicked: false
}
}
drumHit(e) {
document.querySelector(`.audio[data-drum=${this.props.type}]`).play();
this.setState({clicked:true});
}
clickFunction(e) {
this.state.clicked === false ? this.drumHit(e) : this.setState({clicked:false})
}
render() {
const drumType = this.props.type;
const drumFile = this.props.file;
const buttonClasses = `${this.props.buttonClassName} drum-clicked--${this.state.clicked}`
return (
<div onClick={this.props.onClick}>
<button className={buttonClasses} data-type={drumType} onClick={this.clickFunction}></button>
</div>
)
}
}
You will need to contain the information about the activeDrums in your DrumMachine component.
That means:
In your DrumMachine component you create the state activeDrums like you have in your SixteenthNote.js. You will need to put your updateActiveDrumsArray function to your drumMachine component as well.
Then you pass this function to your SixteenthNote component like:
<SixteenthNote columnClassName="drum__column" key={index} drumOptions={this.state.drumOptions} onDrumsClick={this.updateActiveDrumsArray} />
After doing so, you can access that function via props. So, in your SixteenthNote component it should look like:
<Drum buttonClassName="drum__button" audioClassName="drum__audio" type={button.type} file={button.file} key={index} onClick={() => this.props.onDrumsClick(button.type)}/>
(Don't forget to get rid of the unneccessary code.)
With this, you have your activeDrums state in DrumMachine containing all the active drums. This state you can then send to your play component and do the play action there.

Easy communication of image between siblings

I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>

Invisible changes when screen update

I'm new in react-native and have some probems. In the fahterScreen I add some items to array and pass to childs as prop, I need that the child (CanastaScreen) update every 1seg and show the new value. I have the next code:
export default class CanastaScreen extends React.Component {
constructor(props) {
super(props);
setInterval( () => { this.render(); }, 1000);
};
render() {
return (
<Container>
<Content>
{this.props.screenProps.canasta.map( (item) => {
console.log(item.nombre);
return (
<Text>{item.nombre}</Text>
);
})}
</Content>
</Container>
);
}
}
Console output show correctly:
Item1
Item2
Item3
etc.
But the screen is always in blank. Some can help my about it ?
Thanks
First of all, you never should call render method of a component. in React Native, a component should update only if it's state changes. so if you have something like this :
<Parent>
<Canasta> ... </Canasta>
</Parent>
assuming that the changing variable is called foo in state of Parent, you need to pass it as prop to Canasta (child) and now by changing state of Parent (changing foo), Canasta should get updated. here's an example (calling updateFoo will update both Parent and Canasta):
class Parent extends Component {
constructor(props){
super(props); // it's recommended to include this in all constructors
this.state = { foo: initalValue } // give some value to foo
}
updateFoo(newValue){
this.setState({foo: newValue}) // setting state on a component will update it (as i said)
}
render() {
return(
<Canasta someProp={this.state.foo}> ... </Canasta>
)
}
}
}
After various changes, is found, the complete structure is: Parent(App.js) call children(Menu, Canasta). Menu allow add items to the shop-car and Canasta allow to sort and delete items. These are the important parts of the code:
App.js
export default class App extends React.Component {
constructor(props) {
super(props);
this.stateUpdater = this.stateUpdater.bind(this);
this.state = { canasta:[] };
}
render() {
return (
<View>
<RootNavigation data={[this.state, this.stateUpdater]} />
</View>
);
}
}
Menu.js
tryAddCanasta(index, plato){
let canasta = this.props.screenProps[0].canasta;
plato.id_Plato = canasta.length;
canasta.push(plato);
this.props.screenProps[1]('canasta', canasta);
}
Canasta.js
shouldComponentUpdate(nextProps, nextState) {
return true;
}
render() {
return (
<Container>
<Content>
<List>
{this.props.screenProps[0].canasta.map( (item) => {
return ( this._renderRow(item) );
})}
</List>
</Content>
</Container>
);
}
Special thanks to #Shadow_m2, now I don't need check every time, it works in "real time"

Add class to siblings when Component clicked in React

I'm working on building my portfolio using React.js. In one section, I have four components laid out in a grid. What I want to do achieve is when one component is clicked, a css class is added to the siblings of this component so that their opacity is reduced and only the clicked component remains. In jQuery, it would be something like $('.component').on('click', function(){ $(this).siblings.addClass('fadeAway')}). How can I achieve this effect? Here is my code, thanks in advance for any and all help!
class Parent extends Component{
constructor(){
super();
this.state = {fadeAway: false}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
//Add class to siblings
}
render(){
const array = ["Hello", "Hi", "How's it going", "Good Times"]
return(
array.map(function(obj, index){
<Child text={obj} key={index} onClick={() => this.handleClick} />
})
)
}
}
A working example for this problem could look something like this, with a marginally more complex initialization array:
class Parent extends Component{
constructor(){
super();
this.state = {
elements: [
{
id: "hello",
text: "Hello",
reduced: false,
},
{
id: "hi",
text: "Hi",
reduced: false,
}
{
id: "howsItGoing"
text: "How's it going",
reduced: false,
}
{
id: "goodTimes",
text: "Good Times",
reduced: false,
}
],
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(e){
// copy elements from state
const elements = JSON.parse(JSON.stringify(elements));
const newElements = elements.map(element => {
if (element.id === e.target.id) {
element.reduced = false;
} else {
element.reduced = true;
}
});
this.setState({
elements: newElements,
});
}
render(){
return(
this.state.elements.map(function(obj, index){
<Child
id={obj.id}
text={obj.text}
reduced={obj.reduced}
key={index}
onClick={() => this.handleClick} />
});
);
}
}
Then you would just add a ternary, like so, to the Child component:
<Child
id={this.props.id}
className={this.props.reduced ? "reduced" : ""} />
This adds a bit more boilerplate than other examples, but it's extremely brittle to tie business logic to the text inside a component, and a stronger solution requires a stronger piece of identification, like an ID or class on the rendered DOM element. This solution also, if you so wish, easily allows you to expand your logic so that more than one element can remain at maximum opacity at once.
I would simply store in state index of selected item, and then pass fadeAway prop into Child component defined as
fadeAway={this.state.selectedIndex !== index}
After that you only need to set a fade-away class in Child based on this.prop.fadeAway and define necessary CSS rules.
Here is how it could look in your case:
class Parent extends React.Component{
constructor () {
super();
this.state = {selectedIndex: null}
}
handleClick (selectedIndex) {
this.setState({ selectedIndex })
}
render () {
const array = ["Hello", "Hi", "How's it going", "Good Times"]
return (
<div>
{array.map((obj, index) => {
const faded = this.state.selectedIndex && this.state.selectedIndex !== index
return <Child
text={obj}
fadeAway={faded}
key={index}
onClick={() => this.handleClick(index)} />
})}
</div>
)
}
}
class Child extends React.Component {
render () {
return (
<h2
onClick={this.props.onClick}
className={this.props.fadeAway ? 'fade-away' : ''}>
{this.props.text}
</h2>
)
}
}
ReactDOM.render(
<Parent />,
document.body
);
.fade-away {
opacity: 0.3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You can achieve that using using a toggle variable :
handleClick(){
this.setState({fadeAway} => ({
fadeAway: ! fadeAway
)};
}
...
<Child
text={obj}
key={index}
onClick={() => this.handleClick}
className={this.state.fadeAway? 'class1' : 'class2'}/>
I perfer use state like currentWord to save the word was clicked in Parent component, presudo code is like below:
class Parent extends Component {
constructor(){
super();
this.state = {
fadeAway: false,
currentWord: ''
};
this.handleClick = this.handleClick.bind(this)
}
handleClick(currentWord){
this.setState({
currentWord: currentWord,
});
}
render(){
const array = ["Hello", "Hi", "How's it going", "Good Times"]
const currentWord = this.state.currentWord;
return(
array.map(function(obj, index){
<Child currentWord={currentWord} text={obj} key={index} onClick={() => this.handleClick} />
})
)
}
}
And in Child component
class Child extends Component {
// some other code
handleClick(e) {
this.props.handleClick(e.target.value);
}
render() {
const isSelected = this.props.text === this.props.currentWord;
// use isSelected to toggle className
<div
onClick={this.handleClick.bind(this)}
>{this.props.text}
</div>
}
}

Categories