My goal is to have the function handleClick() alert the squareNum for any Square Object clicked, as can be seen in the code below. Though implementation of such isn't working. At first, I tried implementing in accordance to this link , through binding - but no success.
After further research, I suspect the binding issue exists since upon click the square object created isn't identified, but rather the html rendered (the actual button).
As such I created the data-id attribute within the rendered button html that retrieves data from the square object. I proceeded to implement in accordance to this link.
Unfortunately, I get the error that getAttributes isn't defined, and not too sure what's wrong.
Along with the solution for the getAttributes issue, is there a better way to implement this, so the respective squareNum is identified upon click?
Thanks in advance
CODEPEN
CODE
class Square extends React.Component {
//constructor
constructor() {
super();
this.state = {
value: null,
squareNum: null,
};
}
render() {
return (
//<button className="square" onClick = {() => this.setState({value: 'X'})}>
// <button className = "square" data-id = {this.props.squareNum} onClick = {() => this.props.onClick()}>
<button className="square" data-id = {this.props.squareNum} onClick = {this.onClickSquare()}>
{this.props.value}
</button>
);
}
onClickSquare() {
this.props.onClick(this.props.squareNum);
}
}
class Board extends React.Component {
constructor() {
super();
this.state = {
squares: Array(9).fill(null),
};
}
renderRow(i) {
var rowSquare = [];
var square;
//{objects.map((o, i) => <ObjectRow obj={o} key={i}/>}
// create row of 3, creating square elements in array then return array
for(var iterator = i; iterator < i+3; iterator++) {
square = <Square key = {iterator} squareNum = {iterator} value = {this.state.squares[iterator]} onClick = {() => this.handleClick} />;
rowSquare.push(square);
}
return <div className ="board-row">
{rowSquare}
</div>
} // end of renderRow()
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
{this.renderRow(0)}
{this.renderRow(3)}
{this.renderRow(6)}
</div>
);
}
handleClick(squareId) {
// utilize slice to copy not change state
// want to keep mutating changes to setState only
const squares = this.state.squares.slice();
alert(squareId);
//squares[i] = 'X';
this.setState({squares: squares});
} // end of handleClick
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
There are a few issues with your example, primarily that you're not passing props to the parent class. Furthermore, your onClick handlers require functions. ES6 arrow functions will implicitly bind the context, so you can wrap your handlers in an anonymous callback. As for the getAttributes error, my guess is that you were trying to query the DOM for that attribute, but you weren't selecting the element properly or keeping a reference to it. Regardless, the rest should be fairly straight forward and you wouldn't need to use the data-id attribute at all. See https://codepen.io/anon/pen/Kavmyz?editors=0010
class Square extends React.Component {
//constructor
constructor(props) {
super(props);
this.state = {
value: null,
squareNum: null,
};
}
render() {
return (
<button className="square" onClick = {() => this.onClickSquare()}>
{this.props.value}
</button>
);
}
onClickSquare() {
this.props.onClick(this.props.squareNum);
}
}
class Board extends React.Component {
constructor() {
super();
this.state = {
squares: Array(9).fill(null),
};
}
renderRow(i) {
var rowSquare = [];
var square;
// create row of 3, creating square elements in array then return array
for(var iterator = i; iterator < i+3; iterator++) {
square = ( <Square
key={iterator}
squareNum={iterator}
value={this.state.squares[iterator]}
onClick={(squareNum) => this.handleClick(squareNum)} />);
rowSquare.push(square);
}
return <div className ="board-row">
{rowSquare}
</div>
} // end of renderRow()
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
{this.renderRow(0)}
{this.renderRow(3)}
{this.renderRow(6)}
</div>
);
}
handleClick(squareId) {
// utilize slice to copy not change state
// want to keep mutating changes to setState only
const squares = this.state.squares.slice();
squares[squareId] = squareId;
this.setState({squares: squares});
} // end of handleClick
}
Hope this helps!
Related
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.
I'm trying to generate several divs based off an array - but I'm unable to. I click a button, which is supposed to return the divs via mapping but it's returning anything.
class History extends Component {
constructor(props) {
super(props);
this.state = {
info: ""
};
this.generateDivs = this.generateDivs.bind(this);
}
async getCurrentHistory(address) {
const info = await axios.get(`https://api3.tzscan.io/v2/bakings_history/${address}?number=10000`);
return info.data[2];
}
async getHistory() {
const info = await getCurrentHistory(
"tz1hAYfexyzPGG6RhZZMpDvAHifubsbb6kgn"
);
this.setState({ info });
}
generateDivs() {
const arr = this.state.info;
const listItems = arr.map((cycles) =>
<div class="box-1">
Cycle: {cycles.cycle}
Count: {cycles.count.count_all}
Rewards: {cycles.reward}
</div>
);
return (
<div class="flex-container">
{ listItems }
</div>
)
}
componentWillMount() {
this.getHistory();
}
render() {
return (
<div>
<button onClick={this.generateDivs}>make divs</button>
</div>
);
}
You are not actually rendering the the divs just by invoking the generateDivs function, the JSX it is returning is not being used anywhere.
To get it to work you could do something like -
render() {
return (
<div>
<button onClick={this.showDivs}>make divs</button>
{this.state.isDisplayed && this.generateDivs()}
</div>
);
}
where showDivs would be a function to toggle the state property isDisplayed to true
The main point is that the JSX being returned in the generateDivs function will now be rendered out in the render function. There is many ways to toggle the display, that is just one straight forward way
I am in the process of learning React and Redux. Currently I am working on a project where I need to append a component on button click.
New Component should be added down the previous component
Previously added component contains the data added and it should not be refreshed while adding a new component.
I tried to search but all the solutions are recommending to use a List and incrementing the count on every click.
This is my requirement diagram:
Update:
I have added my code which I tried in the below JS Fiddle.
While appending the new component, the data modified in the existing component should be retained.
https://jsfiddle.net/np7u6L1w/
constructor(props) {
super(props)
this.state = { addComp: [] }
}
addComp() { // Onclick function for 'Add Component' Button
//this.setState({ addComp: !this.state.addComp })
this.setState({
addComp: [...this.state.addComp, <Stencil />]
});
}
render() {
return (
<div>
<div class="contentLeft"><h2>Workflows:</h2>
<Stencil />
{this.state.addComp.map((data, index) => {
{ data }
})}
</div>
<div class="contentRight" >
<button name="button" onClick={this.addComp.bind(this)} title="Append new component on to the end of the list">Add Component</button>
</div>
<div class="clear"></div>
</div>
)
}
Code is Updated:
You can do something like that
// New state
this.state = {
appendedCompsCount: 0
}
// Outside render()
handleClick = () => {
this.setState({
appendedCompsCount: this.state.appendedCompsCount + 1
})
}
getAppendedComponents = () => {
let appendedComponents = [];
for (let i = 0; i < this.state.appendedCompsCount; i++) {
appendedComponents.push(
<AppendedComponents key={i} />
)
}
return appendedComponents;
}
// In render()
<button onClick={this.handleClick}>Click here</button>
{
this.getAppendedComponents()
}
maybe when added new child, you want animation to work.
this is the best method react-transition-group
example: https://reactcommunity.org/react-transition-group/transition-group
i want to remove dynamic element in my program, but i think, i have problem with 'this'.When i click in 'X', nothing happens, console doesn't show any error. Maybe someone more experienced will help me.
('items' is array in state)
Main file:
removeItemCity(i){
let arr = this.state.items;
arr.splice(i, 1);
this.setState({items:arr})
}
renderItems(item,i){
return(<Tiles key = {'key_' + i} index = {i} delete = {() =>
{this.removeItemCity}}/>);
}
render() {
return(
<div className = "BodyAppContainer">
<div className = "grid" id="items">
{this.state.items.map(this.renderItems) }
</div>
</div>
);
}
And my component "Tiles"
import React from 'react';
class Tiles extends React.Component {
constructor(props) {
super(props);
}
remove(){
this.props.delete(this.props.index);
}
render() {
return (
<div className = "col-4_sm-6_xs-12 item">
<h2>City : {this.props.index}</h2>
<button className="removeButton" onClick={() => this.remove} >X</button>
</div>
);
}
}
export default Tiles;
Your onClick prop for the X button is not doing anything:
onClick={() => this.remove}
When you click, it calls that arrow function. But that arrow function only has this.remove, which is the definition to a method. The first step in helping you out is you should call that method using parentheses:
onClick={() => this.remove()}
The same thing applies to your renderItems(), where you are also missing parentheses to enact a function call in the delete prop passed to Tiles:
delete={() => {this.removeItemCity}}
Try this:
<button className="removeButton" onClick={this.remove} >X</button>
(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}