I'm trying to build a connect 4 game, which has a Board component comprised of 7 Column components all contain 6 Space components. Every Column has a Drop button above it, which will be used to drop the piece into the column. In order to alternate between "red" and "black" players, I have created a state "redOrBlue" which returns a boolean.
In a nutshell, when the Drop button is clicked, I want to toggle the value of "redOrBlue" to keep track of whose turn it is. I've set the onClick function to setState({redOrBlue: !this.state.redOrBlue)}, however, calling this function will cause react to render an extra column right below the column in which the button was clicked. I understand that setState automatically re-renders the DOM, but how can I keep from it rendering duplicates of a component? Thanks for your help!
class Column extends Component{
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
}
makeRow(){
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
this.state.myArray.push(component);
}
}
handleClick(){
var num = this.props.colNumber;
var array = this.props.boardArray[num];
var boolean = false;
var color;
if(this.state.redOrBlue === true){
color = "red"
}else{
color = "black"
}
for(var i = 5; i > -1; i--){
if(boolean === false){
if(array[i] === null){
array[i] = color;
boolean = true;
}
}
}
this.setState({redOrBlue: !this.state.redOrBlue})
console.log(array)
}
render(){
{this.makeRow()}
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber} buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.state.myArray.map(function(component, key){
return component
})}
</div>
)
}
}
There are many things that you need to change in your code:
*First of all never store the ui items in state variable, state variable should contain only data and values.
*Never do any changes in state variable by this.state.a = '' or this.state.a.push({}), always treat the state values as immutable and use only setState to change the value.
*Always call function inside render that will create the ui part directly if you want to create something dynamically.
Call makeRow method from render and it will return the ui directly without storing it in state variable, like this:
makeRow(){
var component = [];
for(var i = 0; i < 6; i++){
component.push(<Space key={i} className="space"/>);
}
return component;
}
render(){
return(
<div className="column">
<DropButton onClick={() => this.handleClick()} id={'button-' + this.props.colNumber}
buttonNumber={this.props.colNumber} className={this.props.className}/>
{this.makerow()}
</div>
)
}
Remove {this.makeRow()} from your render function. All you're doing is adding another row to the state, in a rather non-kosher method, every time the component renders. Try something like this:
constructor(){
super();
this.state = {
myArray: [],
buttonArray: [],
buttonNumber: null,
newArray: [],
redOrBlue: true
}
this.makeRow();
}
makeRow(){
var tempArray = [];
var component = <Space className="space"/>
for(var i = 0; i < 6; i++){
tempArray.push(component);
}
this.setState({ myArray: tempArray }};
}
Related
have a simple React component that displayes a character and should call a handler when clicked, and supply a number. The component is called many times, thus displayed as a list. The funny thing is that when the handler is called, the supplied index is always the same, the last value of i+1. As if the reference of i was used, and not the value.
I know there is a javascript map function, but shouldn't this approach work too?
const charComp = (props) => {
return (
<div onClick={props.clicked}>
<p>{props.theChar}</p>
</div>
);
deleteHandler = (index) => {
alert(index);
}
render() {
var charList = []; // will later be included in the output
var txt = "some text";
for (var i=0; i< txt.length; i++)
{
var comp =
<CharComponent
theChar = {txt[i]}
clicked = {() => this.deleteHandler(i)}/>;
charList.push(comp);
}
Because by the time you click on a letter, i is already 9 and it will remain 9 since the information is not held anywhere.
If you want to keep track of the index you should pass it to the child component CharComponent and then pass it back to the father component when clicked.
const CharComponent = (props) => {
const clickHandler = () => {
props.clicked(props.index);
}
return (
<div onClick={clickHandler}>
<p>{props.theChar}</p>
</div>
);
};
var comp = (
<CharComponent theChar={txt[i]} index={i} clicked={(index) => deleteHandler(index)} />
);
A little codesandbox for ya
I am trying to create an application that will sort the array from the smallest to the largest, but at the very beginning I encountered an error. React does not show a single error and the component does not render anyway.
App.js
import { SortingVizualize } from './SortingVizualize/SortingVizualize';
function App() {
return (
<div className="App">
<SortingVizualize />
</div>
)
}
export default App;
SortingVizualize.jsx
import React from 'react';
// import styles from './SortingVizualize.modules.scss';
export class SortingVizualize extends React.Component {
constructor(props) {
super(props);
this.state = {
array: [],
};
}
componentDidMount() {
this.resetArray();
}
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
}
render() {
const { array } = this.state;
return (
<>
{array.map((value, idx) => (
<div className="array-bar" key={idx}>
{value}
</div>
))}
</>
)
}
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
export default SortingVizualize;
Your state.array and the array in resetArray are two different arrays and you never update the one in the state.
You will need to call setState to update the state
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
this.setState({ array });
}
You're not triggering any update events since you're using array.push to a local variable, which then isn't assigned to the component state. Remember to use this.setState(...) to update the array saved in your state, like so:
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
this.setState({
array,
});
}
In your resetArray method you're not assigning your data to your state. Your state's array field still empty then. You may add this.setState({ array }) in order to trigger the rerender with generated data.
I made a sorting algorithm visualiser which displays vertical bars of different heights and sort them. I have used a button here called "Generate new Array" which will call a function to generate new array everytime and I have also used this function in componentDidMount() function. How do I change the style property whenever I click that button?
I tried taking document.getElementByClassName('array-bars') into an array and change its style property using loop but its not happeneing. I am adding the necessary code below.
{ //array is const storing array of numbers which is also only state of this program.
array.map((value, idx) => (
<div
className="array-bar"
key={idx}
style={{ height: value, backgroundColor: 'turquoise' }}></div>))
}
componentDidMount(){
this.resetArray();
}
// this is called when I click generate new array
resetArray(){
const array = [];
for (let i = 0; i < 300; i++) {
array.push(randomIntFromInterval(15, 650));
}
const arrayBars = document.getElementByClassName('array-bar');
for (let i = 0; i < arrayBars.length; i++)
arrayBars[i].style.backgroundColor = 'green'; //this is failing
this.setState({ array });
}
Edited:
This is function where I am changing style property using the method written in above code. Its working here.
Also, Can you tell how can I change the color in this mergeSort() in last?
I tried using this.setState() at last but that's changing the color in the beginning only.
mergeSort(){
for(let i=0;i<animations.length;i++){
const arrayBars= document.getElementsByClassName('array-bar');
const colorChange=i%3!==2;
if(colorChange){
const [barOne,barTwo] =animations[i];
const barOneStyle=arrayBars[barOne].style;
const barTwoStyle=arrayBars[barTwo].style;
const color=i%3===0?'red':'turquoise';
setTimeout(()=>{
barOneStyle.backgroundColor=color;
barTwoStyle.backgroudColor=color;
},i*2);
}
else{
setTimeout(()=>{
const[barOne,newHeight]=animations[i];
const barOneStyle=arrayBars[barOne].style;
barOneStyle.height=newHeight+'px';
},i*2)
}
}
}
In React, you should rely on state changes to "make things happen". As suggested by other in the question comments, set an initial state containing the initial background color and update the value as needed.
UPDATE: If you want changes to happen after button click, just set its onclick attribute to point to a function that does what you want. Here I added a button and pointed its onclick attribute to resetArrays.
class YourComponent extends React.Component {
constructor(props){
super(props);
this.state = {
barBg: 'turquoise'
}
}
render(){
return <div>
<button onClick={this.resetArray.bind(this)}>Generate new array</button>
{ //array is const storing array of numbers which is also only state of this program.
array.map((value, idx) => (
<div
className="array-bar"
key={idx}
style={{ height: value, backgroundColor: this.state.barBg }}></div>))
}
</div>
}
componentDidMount(){
this.resetArray();
}
// this is called when I click generate new array
resetArray(){
const array = [];
for (let i = 0; i < 300; i++) {
array.push(randomIntFromInterval(15, 650));
}
this.setState({ array, barBg: 'green' });
}
}
I have this react component. This is not rendering properly but getting an annoying warning like
Functions are not valid as a React child. This may happen if you return a Component instead of from the render. Or maybe you meant to call this function rather than return it.
Here's my component. What am I doing wrong here?
import React, { Component } from 'react';
class Squares extends Component {
constructor(props){
super(props);
this.createSquare = this.createSquare.bind(this);
}
createSquare() {
let indents = [], rows = this.props.rows, cols = this.props.cols;
let squareSize = 50;
for (let i = 0; i < rows; i++) {
for (let j = 0; i < cols; j++) {
let topPosition = j * squareSize;
let leftPosition = i * squareSize;
let divStyle = {
top: topPosition+'px',
left: leftPosition+'px'
};
indents.push(<div style={divStyle}></div>);
}
}
return indents;
}
render() {
return (
<div>
{this.createSquare()}
</div>
);
}
}
export default Squares;
UPDATE
#Ross Allen - After making that change, the render method seems to be in infinite loop with potential memory crash
You need to call createSquare, right now you're just passing a reference to the function. Add parentheses after it:
render() {
return (
<div>
{this.createSquare()}
</div>
);
}
React uses JSX to render HTML and return function within render() should contain only HTML elements and any expression that needed to be evaluated must be within { } as explanied in https://reactjs.org/docs/introducing-jsx.html. But the best practice would be to do any operation outside return just inside render() where you can store the values and refer them in the return() and restrict usage of { } to just simple expression evaluation. Refer for In depth JSX integration with React https://reactjs.org/docs/jsx-in-depth.html
render() {
var sq = this.createSquare();
return (
<div>
{sq}
</div>
);
Ross Allen's answer is also fine , the point is Inside JSX enclose any operation / evaluation inside { }
You just need to remove () from your function call.
render() {
return (
<div>
{this.createSquare}
</div>
);
}
I am very new to react and am trying to create a checkbox and also display the selected checkbox names list in a textbox. I am using selected[] to store the values for the selected box and checked[] to store whether that box is checked or not.If it is checked I update the value of selected accordingly.
The code as of now works fine but I want to avoid use of forceupdate() and use setState(). When I use I am unable to update the selected[] value using it. Can somebody tell me of how to update the particular array index value using setstate so thatit gets render itself and I do not have to use forceupdate() ?
thank you.
var history = React.createClass({
getInitialState : function(){
return {
checked : [],
selected: []
};
},
componentWillMount : function(){
},
handleChangechk: function (e){
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
if(value===true)
{
this.state.checked[name]= true;
this.state.selected[name] = name;
this.forceUpdate();
}
else
{
this.state.checked[name]= false;
this.state.selected[name] = '';
this.forceUpdate();
}
},
render : function() {
var historyList = [];
var selectedList = [];
for (var i = 0; i < 10; i++) {
historyList.push(<span key={i}><input type="checkbox" name = {i} checked={!!this.state.checked[i]} onChange ={(e)=> this.handleChangechk(e)}/><span ></span><label >checkbox {i}</label></span>);
if(this.state.selected[i])
{
selectedList.push(this.state.selected[i]);
}
};
return( /* display selected checkbox (selectedList ); */}});
Never mutate the state variable directly by this.state.a.push() or this.state.a = '', always use setState to update the value.
From Doc:
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
forceUpdate() is not required if you update the state values properly, it will automatically do the re-rendering, Check this:
class App extends React.Component{
constructor(){
super();
this.state = {value: [], selected: []}
}
change(event) {
let target, value, selected;
target = event.target;
value = this.state.value.slice();
selected = this.state.selected.slice();
value[target.name] = target.checked;
if(target.checked){
selected.push(target.name);
}else{
let index = selected.indexOf(target.name);
selected.splice(index, 1);
}
this.setState({
value, selected
});
}
render(){
return(
<div>
{[1,2,3,4,5].map((el,i)=>{
return <div key={i}>
<input checked={this.state.value[i]} type='checkbox' onChange={this.change.bind(this)} name={i}/>
Item- {el}
</div>
})}
Selected Values: {JSON.stringify(this.state.selected)}
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<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>
<div id='app'/>
You could get a copy of the checked and selected arrays, change the relevant index and then update the state:
handleChangechk: function (e) {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
let checked = this.state.checked.slice(),
selected = this.state.selected.slice();
if(value === true) {
checked[name]= true;
selected[name] = name;
} else {
checked[name]= false;
selected[name] = '';
}
this.setState({ items, selected });
}
You should almost never assign values to your state directly and/or use forceUpdate:
this.state.checked[name]= true;
this.state.selected[name] = name;
this.forceUpdate();
Avoiding the use of forceUpdate is as simple as using setState to assign the values. setState will automatically trigger a re-render.
var newChecked = this.state.checked.slice();
newChecked[name] = true;
var newSelected = this.state.selected.slice();
newSelected [name] = name;
this.setState({
checked: newChecked,
selected: newSelected,
});