What is a react way to do this? - javascript

I have a pretty basic scenario where I have bunch of selectable/clickable elements and want to highlight the last selected/clicked. Here is my jquerish version of it where I add particular css class to the selected element and remove that class from its siblings. Is there any better way to do this with React ?
function Controls(props) {
const getSiblings = (element) => {
let siblings = [];
if (!element.parentNode) {
return siblings;
}
let sibling = element.parentNode.firstChild;
while (sibling) {
if (sibling.nodeType === 1 && sibling !== element) {
siblings.push(sibling);
}
sibling = sibling.nextSibling;
}
return siblings;
};
const handleClick = (event) => {
event.target.classList.add("symbol-preview-selected");
let siblings = getSiblings(event.target);
siblings.map((e) => {
e.classList.remove("symbol-preview-selected")
});
};
return(
<div className="grid-control-container">
<div className="flex-row-container">
<span style={{marginRight: "4px"}}>Color:</span>
<div className="flex-row-container">
{[...Array(10).keys()].map((i) => <div className={`symbol-preview symbol-`+i} onClick={handleClick} key={i.toString()}/>)}
</div>
</div>
</div>
);
}

Put the clicked element index into state instead.
function Controls(props) {
const [selectedIndex, setSelectedIndex] = useState(-1);
const makeHandleClick = i => () => {
setSelectedIndex(i);
};
return (
<div className="grid-control-container">
<div className="flex-row-container">
<span style={{ marginRight: "4px" }}>Color:</span>
<div className="flex-row-container">
{[...Array(10).keys()].map((i) => (
<div
className={'symbol-preview symbol-' + i + (i === selectedIndex ? ' symbol-preview-selected' : '')}
onClick={makeHandleClick(i)}
key={i}
/>
))}
</div>
</div>
</div>
);
}

Related

How to enable manual value text input in custom increment-decrement field in REACT.js

I'm pretty new to react so please bear with me. I'm trying to create a custom increment/decrement quantity selector. Everything is working as I want it to except it's not allowing me to manually change the value of the input box. I can't figure out how to enable manual input.
I want to allow the user to be able to change the value using the increment/decrement buttons or manually enter a numeric value. Thanks in advance!!
const Quantity = ({max}) => {
const [qty, setQty] = useState(0)
let increaseQty = () => setQty(qty + 1)
let decreaseQty = () => setQty(qty - 1)
if(qty<=0){
decreaseQty = () => setQty(0)
}
if(qty>=max){
increaseQty = () => setQty(max)
}
return (
<div>
<Button onClick={decreaseQty}> - </Button>
<input type="text" value={qty}/>
<Button onClick={increaseQty}> + </Button>
</div>
)
}
export default Quantity
onChange={onChange} is required for the user to be able to enter the value manually.
import React, { useState } from "react";
const Quantity = ({ max }) => {
const [qty, setQty] = useState(0);
const decreaseQty = () => {
if (qty <= 0) {
setQty(0);
} else {
setQty(qty - 1);
}
};
const increaseQty = () => {
if (qty >= max) {
setQty(max);
} else {
setQty(qty + 1);
}
};
const onChange = (e) => {
const value = parseInt(e.target.value);
if (value >= 0 && value <= max) {
setQty(value);
}
};
return (
<div>
<button onClick={decreaseQty}> - </button>
<input type="text" onChange={onChange} value={qty} />
<button onClick={increaseQty}> + </button>
</div>
);
};
export default Quantity;
const StackOverflow = ({max}) => {
const [qty, setQty] = useState(0)
let increaseQty = () => setQty((qty) => qty+ 1)
let decreaseQty = () => setQty(qty - 1)
if(qty<=0){
decreaseQty = () => setQty(0)
}
if(qty>=max){
increaseQty = () => setQty(max)
}
const manuallyUpdate = (event) => {
let newvalue=parseInt(event.target.value)
if(newvalue <= 0){
setQty(0)
}else if(newvalue >= parseInt(max)){
setQty(max)
}else{
setQty(newvalue)
}
}
return (
<Container maxWidth="lg" style={{paddingTop:"5%"}}>
<div>
<Button onClick={decreaseQty}> - </Button>
<input type="text" value={qty} onChange={(e) => manuallyUpdate(e)}/>
<Button onClick={increaseQty}> + </Button>
</div>
</Container>
)
}
The issue is the functional component only runs once at render time. Therefore, the if statements checking for 0 or max won't run when this is changed. Instead, if you include the if statement in the function body then it will run each time the function is fired.
You could implement something similar to this:
const Quantity = ({ max }) => {
const [qty, setQty] = useState(0);
function decreaseQty() {
if (qty <= 0) {
setQty(0);
} else {
setQty(qrt - 1);
}
}
function increaseQty() {
if (qty >= max) {
setQty(max);
} else {
setQty(qrt + 1);
}
}
function onChange(e) {
const v = e.target.value;
if (v <= 0) setQty(0);
else if (v >= max) setQty(max);
else setQty(v);
}
return (
<div>
<Button onClick={decreaseQty}> - </Button>
<input type="text" value={qty} onChange={(e) => onChange(e)} />
<Button onClick={increaseQty}> + </Button>
</div>
);
};
export default Quantity;

is there any method in javascript to operate a function singly for one HTML element?

I have three box in my web page , each box includes a + button , a - button and a span with pre-assigned value as 0.
I want when I click on + or - button, the value of span get one more or less according to times of clicking + or -.
but my code doesnt work well.
what should I do ?
Thanks.
<div class="box">
<button class="minus-btn" type="button">-</button>
<span class="display-num">0</span>
<button class="plus-btn" type="button">+</button>
</div>
<div class="box">
<button class="minus-btn" type="button">-</button>
<span class="display-num">0</span>
<button class="plus-btn" type="button">+</button>
</div>
<div class="box">
<button class="minus-btn" type="button">-</button>
<span class="display-num">0</span>
<button class="plus-btn" type="button">+</button>
</div>
function $(query) {
return document.querySelector(query);
}
function $All(query) {
return document.querySelectorAll(query);
}
const box = $All('.box');
const plusBtn = $All('.plus-btn');
const minusBtn = $All('.minus-btn');
const displayNum = $All('.display-num');
box.forEach((e) => {
e.addEventListener('click', (el) => {
if (el.target.classList.contains('plus-btn')) {
let num = 0;
displayNum.forEach((e) => {
e.innerHTML = num++;
})
} else if (el.target.classList.contains('minus-btn')) {
let num = 0;
displayNum.forEach((e) => {
e.innerHTML = num--;
})
}
}, false)
})
It is probably easier to react on the actual button being pressed, rather than checking the boxes etc, try the following:
plusBtn.forEach((e, idx) => {
e.addEventListener(
'click',
(el) => {
calcIt(displayNum[idx], 1);
},
false
);
});
minusBtn.forEach((e, idx) => {
e.addEventListener(
'click',
(el) => {
calcIt(displayNum[idx], -1);
},
false
);
});
function calcIt(element, value) {
var num = element.innerHTML;
element.innerHTML = (+num) + value;
}
Remove the block:
box.forEach((e) => {
e.addEventListener('click', (el) => {
if (el.target.classList.contains('plus-btn')) {
let num = 0;
displayNum.forEach((e) => {
e.innerHTML = num++;
})
} else if (el.target.classList.contains('minus-btn')) {
let num = 0;
displayNum.forEach((e) => {
e.innerHTML = num--;
})
}
}, false)
})
Stackblitz:
https://jquery-rfrzsp.stackblitz.io
(https://stackblitz.com/edit/jquery-rfrzsp)

how to change color of boxes in 2D grid of specific cell on click in reactJs?

Basically I was trying to make 10*10 grid such that clicking on specific cell by it cell number change all multiple cell color? for example clicking on cell 5 it change all boxes which having multiple of 5 values. For grid I used tailwindcss
Can anyone please tell that why in my code background color of multiple cells is not changing?
const { useState } = React;
function App() {
const [fillcolor, setFillcolor] = useState("");
var items = [];
for (let i = 1; i <= 100; i++) {
items.push(<div onClick={() => changeColor(i)} className="border border-gray-600 text-center">
{i}
</div>)
}
function changeColor(index) {
alert(index);
setFillcolor("yellow");
for (var i = 0; i < items.length; i++) {
if ((i + 1) % index === 0) {
items[i] = <div style={{ backgroundColor: fillcolor }} className="border border-gray-600 text-center">
{(i + 1)}
</div>
}
}
console.log(items[0]);
}
return (
<div className="App">
<div className="container">
<div className="grid grid-cols-10 grid-rows-10">
{items}
</div>
</div>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You are modifying items inside your changeColor function, which React will never find out (since it's not a state). When React rerenders the component, the variable will be redefined. Instead, I'd recommend storing the highlighted factor. The fillColor does not need to change (at least in your code it's always set to 'yellow') and therefore should not be a state.
The following should work:
function App() {
const [selectedIndex, setSelectedIndex] = useState(null);
const items = [];
for (let i = 1; i <= 100; i++) {
let style = {};
if(selectedIndex !== null && i % selectedIndex === 0){
style = {
backgroundColor: "yellow"
};
}
items.push(<div onClick={() => changeColor(i)} className="border border-gray-600 text-center" style={style}>
{i}
</div>)
}
function changeColor(index) {
// only need to set the state here
setSelectedIndex(index);
}
return (
<div className="App">
<div className="container">
<div className="grid grid-cols-10 grid-rows-10">
{items}
</div>
</div>
</div>
);
}
As #skrrrt said, in React is suggested to not manipulate DOM like you are doing now. Much better use state variables. I suggest you to modify your code in this way:
const { useState } = React;
function App() {
const [fillcolor, setFillcolor] = useState(new Array(100).fill("white")); // <-- this is an array of colors to apply to elements
const numbers = Array.from({length: 100}, (_, i) => i + 1); // <-- this is an array of first 100 numbers from 1 to 100
const multiplesOf = (numbers, number) => numbers.filter(n => !(n % number)); // <-- this function returns multiple of selected value in numbers array
function changeColor(index) {
let result = [...fillcolor];
let indexToColor = multiplesOf(numbers, index);
indexToColor.forEach((idx) => result[idx - 1] = "yellow");
setFillcolor(result);
}
return (
<div className="App">
<div className="container">
<div className="grid grid-cols-10 grid-rows-10">
{numbers.map((el) => (
<div style={{ backgroundColor: fillcolor[el - 1] }} onClick={() => changeColor(el)} className="border border-gray-600 text-center">
{el}
</div>
))}
</div>
</div>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Credits: multiplesOf function is taken from here; numbers fill function is taken from here.

How to print selectors of DOM objects

Given a DOM structure like this:
<div>
<div>
<span>
<img/>
<i>
<span></span>
<meter></meter>
</i>
<a><span></span></a>
</span>
</div>
<nav>
<form>
<input/>
<button></button>
</form>
</nav>
</div>
Wondering how you take that and then return a flat array of all the selectors:
[
'div > div > span > img',
'div > div > span > i > span',
'div > div > span > i > meter',
'div > div > span > a > span',
'div > nav > form > input',
'div > nav > form > button'
]
My attempt hasn't gotten anywhere:
function outputSelectors(array, node) {
var tag = node.tagName
array.push(tag)
for (var i = 0, n = node.children.length; i < n; i++) {
var child = node.children[i]
outputSelectors(array, child)
}
}
outputSelectors([], document.body.children[0])
Not sure where to go from here.
One possible, a non-recursive approach going from top (root, to be precise) to bottom:
function collectLeafNodePathes(root) {
const paths = [];
const selectorParts = [];
let el = root;
while (el) {
const tagName = el.tagName.toLowerCase();
if (el.childElementCount) {
selectorParts.push(tagName);
el = el.firstElementChild;
continue;
}
paths.push(selectorParts.concat([tagName]).join(' > '));
do {
if (el.nextElementSibling) {
el = el.nextElementSibling;
break;
}
el = el.parentNode;
selectorParts.pop();
if (el === root) {
el = null;
}
} while (el);
}
return paths;
}
const selectors = collectLeafNodePathes(document.getElementById('xxx'));
console.log(selectors);
<div id="xxx">
<div>
<span>
<img/>
<i>
<span></span>
<meter></meter>
</i>
<a><span></span></a>
</span>
</div>
<nav>
<form>
<input/>
<button></button>
</form>
</nav>
</div>
That last part (do-while loop) is a bit rough around the edges, though; open to any improvement.
I've used helper properties (childElementCount, firstElementChild, nextElementSibling) to skip checking for text nodes and stuff. If that's not an option (because of compatibility reasons), it's easy to either implement polyfills or just 'rewind' the loop on non-element nodes.
You can map all elements on a page using the getPath method from this answer.
Best try this in your own console, as the snippet takes some time to run, and the snippet's console doesn't seem to handle the output properly.
jQuery.fn.extend({
getPath: function () {
var path, node = this;
while (node.length) {
var realNode = node[0], name = realNode.localName;
if (!name) break;
name = name.toLowerCase();
var parent = node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1) {
allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
const allElements = $("*");
const allPaths = allElements.map((_, e) => $(e).getPath());
console.log(allPaths);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Here is a version without jQuery, if that's preferable:
function getPath (node) {
var path;
while (node.parentElement) {
var name = node.localName;
if (!name) break;
name = name.toLowerCase();
var parent = node.parentElement;
var sameTagSiblings = [...parent.children].filter(e => e.localName === name);
if (sameTagSiblings.length > 1) {
allSiblings = parent.children;
var index = [...allSiblings].indexOf(node) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
};
const allElements = document.querySelectorAll("*");
const allPaths = [...allElements].map(e => getPath(e));
console.log(allPaths);
Slightly modifying this solution to get path and this one to get leaf nodes.
function getPath(node)
{
var path;
while (node.parentNode )
{
name = node.nodeName;
if (!name) break;
var parent = node.parentNode;
path = name + (path ? ' > ' + path : '');
node = parent;
}
return path;
}
function getLeafNodes()
{
var allNodes = document.getElementsByTagName("*");
var leafNodes = Array.from( allNodes ).filter(function(elem) {
return !elem.hasChildNodes();
});
return leafNodes;
}
var leadNodes = getLeafNodes() ;
var output = leadNodes.map( s => getPath(s) );
console.log(output);
<div>
<div>
<span>
<img/>
<i>
<span></span>
<meter></meter>
</i>
<a><span></span></a>
</span>
</div>
<nav>
<form>
<input/>
<button></button>
</form>
</nav>
</div>
You can create recursive function and check if current element contains children using children() method.
const result = []
const getTag = (el) => el.prop('tagName').toLowerCase()
function print(el, prev = '') {
prev = prev.length ? prev : getTag(el)
const children = el.children();
if(!children.length) result.push(prev)
else {
children.each(function() {
let tag = getTag($(this))
let str = prev + (prev.length ? ' > ' : '') + tag;
print($(this), str)
})
}
}
print($('#start'))
console.log(result)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="start">
<div>
<span>
<img/>
<i>
<span></span>
<meter></meter>
</i>
<a><span></span></a>
</span>
</div>
<nav>
<form>
<input/>
<button></button>
</form>
</nav>
</div>
To get array of unique selectors you can use Set on final result to remove duplicates.
let result = []
const getTag = (el) => el.prop('tagName').toLowerCase()
function print(el, prev = '') {
prev = prev.length ? prev : getTag(el)
const children = el.children();
if(!children.length) result.push(prev)
else {
children.each(function() {
let tag = getTag($(this))
let str = prev + (prev.length ? ' > ' : '') + tag;
print($(this), str)
})
}
}
print($('#start'))
result = [...new Set(result)]
console.log(result)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="start">
<div>
<span>
<img/>
<i>
<span></span>
<meter></meter>
</i>
<a><span></span></a>
<a><span></span></a>
</span>
</div>
<nav>
<form>
<input/>
<button></button>
</form>
</nav>
</div>

React - Create nested components with loops

I have a little issue with React. I can't create a nested component with a for loop. What I want to do is create 9 cells of a table and then create 3 rows with 3 cells for every row and after that mount the 3 rows together and create a board 9x9.
Let say that I want to get something like this, but using a loop
class Board extends React.Component {
renderSquare(i) {
return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
}
render(){
return(
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
I searched others question for hours and I think my code is almost correct but it does not render what I want. I only get a white page.
here is my code:
class Board extends React.Component {
renderSquare(i) {
return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
}
createCells(i){
if(i%3){return;}
var index = this.fillN(Array(i)); //index=[0,1,2,3,4,5,6,7,8]
var cells = [];
index.forEach(function(i){
cells.push(() => {
return(
<div>
{this.renderSquare(i)}
</div>
);
});
});
return cells;
}
createRows(cells){
var index = this.fillMod3(Array(3)); //index=[0,3,6]
var rows = []
index.forEach(function(i){
rows.push(() => {
return(
<div>
{cells[i]}
{cells[i+1]}
{cells[i+2]}
</div>
);
});
});
return rows;
}
render(){
var cells = this.createCells(9);
var rows = this.createRows(cells);
var board = [];
var index = this.fillN(Array(1));
index.forEach(function(row){
board.push(() => {
return(
<div>{row}</div>
);
});
})
return(
<div>
{board[0]}
</div>
);
}
I always get on the screen something like this:
<Board>
<div> /*empty*/ </div>
</Board>
I want to clarify that I am sure that the rest of the code with which that component (Board) interacts has no issues.
I am new in react and if someoane can help me i will apreciate very much.
Sorry for my poor English
EDIT1:
following marklew examples i should be able to do something like this
render(){
var index1 = this.fillN(Array(3)); //index1=[0,1,2]
var index2 = this.fillN(Array(3)); //index2=[0,1,2]
return(
<div>
{index1.map((e1,i1) => {
return(
<div key={i1} className="board-row">
{index2.map((e2, i2) => {
return(
<p key={i2+10}>
{this.renderSquare(i2)}
</p>
)
})}
</div>
)
})}
</div>
);
}
but it doesn't do what I want. I obtain just a column with 9 cells and the cells are the same objects. I dont understand why. (I understand that are the same objects because i assign a handle function onClick when I create them like that:
<Board
onClick={(i) => this.handleClick(i)} //handleClick just draws a X in the cell
/>
and I get the X drown in 3 cells simultaneously
EDIT2:
I reached a solution:
render(){
var index1 = this.fillMod3(Array(3));
return(
<div>
{index1.map((e,i) => {
return(
<div key={i} className="board-row">
{this.renderSquare(e)}
{this.renderSquare(e+1)}
{this.renderSquare(e+2)}
</div>
)
})}
</div>
);
}
}
but is not what I want. I want another loop even for the intern renderSquare(i) function.
To render a list of elements inside JSX, you can do something like that:
render() {
return <div>
{
[1,2,3].map ( (n) => {
return this.renderSquare(n)
})
}
</div>;
}
Just wrap your array of components into {} in your JSX.
To clarify a bit, this is the same logic of:
return <div>
{
[
<h1 key="1">Hello 1</h1>,
<h1 key="2">Hello 2</h1>,
<h1 key="3">Hello 3</h1>
]
}
</div>;
Note that everytime you render an array of components, you must provide a key prop, as pointed here.
Also, if you want simply print row value in your render function, you should replace:
index.forEach(function(row){
board.push(() => {
return(
<div>{row}</div>
);
});
})
with:
index.forEach( (row, index) => {
board.push(<div key={index}>{row}</div>)
})
or, yet, replacing forEach and push with map:
board = index.map( (row, index) => <div key={index}>{row}</div> )
EDIT I created a fiddle with a 9x9 board using your code as a base: https://jsfiddle.net/mrlew/cLbyyL27/ (you can click on the cell to select it)
I see you too are doing the JS React tutorial! Here's what I did, but I'm working on this because there has to be a good way to give each of these individual keys.
I ran into the same issue you were running into where the X's were drawn into 3 squares, and the reason that happens is because when you were rendering squares, some of the "i's" were duplicated. So there were multiple Squares with the "i" of 2 for example (at least that was the case in my issue).
So each Square has an index right? [0,1,2,3,4,5,6,7,8].
First we need to find how these are related in a way that we can render them into rows! So, in your example you have index1 and index2, which I assume will refer to the x and y coordinates of the Square in the Board. Re-writing the array above we come to: [{0,0}, {1,0}, {2,0}, {0,1}, {1,1}, {2,1}, {0,2}, {1,2}, {2,2}] using {x,y} format. How can we use these values (which we would get from your index1 and index2 in order to get the original array of values that we started with [0,1,2,3,4,5,6,7,8]?
What I did was 3 * x + y (or in a loop, 3 * i + j). This way each square has a unique "i" value associated with it.
After I set up my loops I used this SO post to correctly return the elements I created from a separate function (in a poor attempt to keep my code cleaner).
This is what I ended up with, but I need to make sure to set the keys correctly which is actually how I stumbled onto this post:
createSquares() {
let rows = [];
for(var i = 0; i < 3; i++){
let squares = [];
for(var j = 0; j < 3; j++){
squares.push(this.renderSquare(3*i+j));
}
rows.push(<div className="board-row">{squares}</div>);
}
return rows;
}
Then my render function in Board looks like this:
render() {
return (
<div>
{this.createSquares()}
</div>
);
}
Here is the best I could think of after reading answers here and in Loop inside React JSX
:
render() {
return (
<div>
{
[...Array(3)].map((_, i) => (
<div key={i} className="board-row">
{
[...Array(3)].map((_, j) => this.renderSquare(3 * i + j))
}
</div>
))
}
</div>
);
}
P.S. I'm also doing the challenges in the new React Tutorial. =p
Live demo on codepen
Nested loops in render() function
(explanation in comments:)
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
key={i}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
var self = this;
return (
<div>
// you can use: [...Array(3)] instead of [1,2,3]
{[1, 2, 3].map(function(row, rowIdx) { // create rows
return (
<div className="board-row" key={rowIdx}>
{
// you can use: [...Array(3)] instead of [1,2,3]
[1, 2, 3].map((col, colIdx) => { // create columns
return self.renderSquare((3 * rowIdx) + colIdx); // calculate square index
})
}
</div>
);
})}
</div>
);
}
}
I will assume you are looking at the React Tutorial example.. As the instructions explicitly point out, the idea is to make two loops, not just the one.
The first loop in this example creates the 3 rows. The second nested loop creates the 3 necessary columns, returning a Square for each position through the renderSquare function. You may notice that I use the indexes of both loops to correctly assign the corresponding index to the square.
return (
<div>
{[0,1,2].map(i => {
return (
<div className="board-row">
{[0,1,2].map(j => {
return this.renderSquare(3*i + j)
})}
</div>
);
})}
</div>
);
Here's my solution. It may help.
renderSquare(i) {
return (
<Square
key={i}
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
let rows = [];
for (let i = 0; i <= 2; i++) {
let children = []
for (let j = i * 3; j <= i * 3 + 2; j++) {
children.push(this.renderSquare(j))
}
rows.push(
<div key={i} className="board-row">
{children}
</div>
)
}
return (
<div>
{rows}
</div>
);
}
This snippet uses map in a way similar to how we define a nested loop.
const buildBoard = [0, 1, 2].map((row) => {
return (
<div className='board-row' key={row}>
{[0, 1, 2].map((col) => {
return this.renderSquare(row * 3 + col);
})}
</div>
);
});
return <div id='Board Container'>{buildBoard}</div>;
You are pushing functions to the board array. If you want render them, you have to call those functions like
{board[0]()}
I prepared an example that covers your problem: http://jsbin.com/hofohiy/edit?js,output
I'm working on the React tutorial also. Thanks for your responses. I got to this solution:
render() {
const rows = [];
for(let i = 0; i<9; i=i+3) {
const oneRow = [];
for(let j = i; j < i+3; j++) {
oneRow.push(this.renderSquare(j, j))
}
rows.push(<div className="board-row" key={i + 'someId'}>{oneRow}</div>)
}
return (
<div>
{rows}
</div>
);
}
Where I changed renderSquare to set a key for the Square component, as the second argument.
renderSquare(i, key) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
key={key}
/>
);
}
This should resolve the problem your facing.
This adds another loop even for the intern renderSquare(i) function.
It does what you want i.e. display 3 columns with 3 cells for the tic tac toe game.
render() {
let count = 0;
const index = [1, 2, 3];
return (
<div>
{index.map((v, i) => {
return (
<div key={i} className="board-row">
{index.map((v2, i2) => {
return this.renderSquare(count++);
})}
</div>
);
})}
</div>
);
}
The first solution:
import React from 'react';
import Square from './Square';
export default
class Board extends React.Component {
render() {
const board2 = [];
for (let i = 0; i < 3; i++) {
const s = [];
for (let k = 0; k < 3; k++) {
s[k] = <Square
key ={3*i+k}
value ={this.props.squares[3*i+k]}
onClick ={()=>this.props.onClick(3*i+k)}
/>;
}
board2[i] = <div className="board-row" key={i}>{s}</div>;
}
///////////////////////////////////////
return (
<div>{board2}</div>
);
}
}
The second solution:
import React from 'react';
import Square from './Square';
export default
class Board extends React.Component {
render() {
let board =Array(3).fill(null);
let square =Array(3).fill(null);
const x = board.map((b,bIndex)=>{
return<div className="board-row" key={bIndex}>
{
square.map((s, sIndex)=>{
return <Square
key ={3*bIndex+sIndex}
value ={this.props.squares[3*bIndex+sIndex]}
onClick ={()=>this.props.onClick(3*bIndex+sIndex)}
/>;
})
}
</div>;
});
///////////////////////////////////////
return (
<div>{x}</div>
);
}
}
Square:
import React from 'react';
export default
function Square(props) {
console.log('square render')
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (<Square key={i} value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />);
}
render() {
let board = [];
for(let x = 0; x<3; x++){
let lis =[];
for(let y = 0; y<3; y++){
lis.push(this.renderSquare(3*x + y));
}
board.push(<div key={x}>{lis}</div>)
}
return (
<div>
{board}
</div>
);
}
}
My solution is:
class Board extends React.Component {
//range is a Sequence generator function
//Array.from(arrayLike[, mapFn [, thisArg]]).
//arrayLike - An array-like or iterable object to convert to an array
//mapFn - Map function to call on every element of the array
//thisArg - Value to use as this when executing mapFn
//range(0, 4, 1); => [0, 1, 2, 3, 4]
range = (start, stop, step) => Array.from(
{ length: (stop - start)/step +1 },
(_, i) => start + (i * step)
);
renderSquare(i) {
return <Square
key={i}
value={this.props.squares[i]}
onClickChange={() => this.props.onClickChange(i)}
/>;
}
render() {
let row = 3;
let col = 3;
return (
<div>
{
//This generates an Array of [0, 3, 6] if row = 3 and col = 3
// n is the element and i is the index of the element, i starts at 0
this.range(0, row * col - 1, col).map( (n, i) => {
return (
<div key={i} className="board-row">
{
this.range(n, (i + 1) * col - 1, 1).map( (n, i) => {
return this.renderSquare(n)
})
}
</div>
)
})
}
</div>
);
}
Changed the code of the Board from the tutorial to reflect as
class Board extends React.Component {
constructor(props) {
super(props);
this.rows = Array(3).fill(null);
}
renderSquare(i) {
return (<Square
key={i}
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>);
}
renderRow(row) {
return (
<div key={row} className="board-row">
{this.rows.map((x, col) => this.renderSquare(3*row+col))}
</div>
);
}
render() {
return (
<div>
{this.rows.map((x, rowIndex) => this.renderRow(rowIndex))}
</div>
);
}
}
The answers above that provide an array, defeat the purpose of a loop; this isn't pretty, but then again, neither is React with JSX:
class Board extends React.Component {
render() {
let board=[];
for(let row=0;row<9;row+=3) {
let cols=[];
for(let col=0;col<3;col++) {
let i=col+row;
cols.push(<Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)}/>)
}
board.push(
<div className="board-row">
{cols}
</div>
);
}
return (
<div>
{board}
</div>
);
}
}
hmm, i think it's way simpler nowadays, cuz i just did
export default function Board(props){
return(
<div id = "board">
{createBoard(3, 3)}
</div>
)
}
function Square(props){
return(
<div className = "boardSquare" style = {{
gridColumn: props.x,
gridRow: props.y}}>
</div>
)
}
function createBoard(x, y){
// x and y being the max "cells" my board will have
let grid = [];
for(let i = 0; i < x; i++){
for(let u = 0; u < y; u++){
grid.push(<Square x = {i + 1} y = {u + 1} key = {`x${i+1}y${u+1}`}/>);
}
}
return grid;
}
and it did exactly what i wanted it to, so yeah, just returning an array solved my problem, i hope my answer is useful to someone.
Just for giggles here's a recent working example as a function component
import React from "react";
import Square from "./Square";
import "./Board.css";
export default function Board(props) {
function renderSquare(i) {
return (
<Square
key={i}
value={props.squares[i]}
onClick={() => props.onClick(i)}
/>
);
}
return (
<div className="board-wrapper">
{
[...Array(3).keys()].map( row => (
<div key={row} className="board-row">
{
[...Array(3).keys()].map( col => renderSquare((row * 3) + col))
}
</div>
)
)
}
</div>
)
}

Categories