I have an issue where my state is only set on the second click. Ive been reading about lifting up the state which Ive tried to implement but I am still running into the same issue with this button. There are multiple components that the state is passed through and maybe thats the issue I could use some help understanding how to resolve this issue Ill try and outline in the simplest way:
I have my parent table component:
const TableView = ({...all the props}) => {
const [sort, setSortBy] = useState({
sortBy: "",
order: "",
});
const handleOnsortChange = (values) => {
setSortBy(values);
trimData(items.sort(sorter), pageNumber, 50);
};
// sort function
const sorter = (a, b) => { ...}
return (
<Table>
<TableHeader handleOnsortChange={handleOnsortChange} sort={sort} />
...
)
Table Header Component:
const TableHeader = ({ handleOnsortChange, sort }) => {
const onSortChange = (value) => {
handleOnsortChange(value);
};
return (
<thead className="payroll-list-header">
<tr>
<th>
<span>Name</span> <SortView onChange={onSortChange} type={"fullName"} sort={sort} />
<div className="arrow"></div>
</th> ...
)
Sorter Component:
const SortView = ({ onChange, type, sort: { sortBy, order } }) => {
const onClick = (order) => {
onChange({ sortBy: type, order });
};
return (
<div className="sort">
<div onClick={() => onClick("asc")} className="icon-up">
<Octicon
icon={TriangleUp}
className={`${order === "asc" && sortBy == type ? "active" : ""}`}
></Octicon>
</div>
<div onClick={() => onClick("desc")} className="icon-down">
<Octicon
icon={TriangleDown}
className={`${order === "desc" && sortBy == type ? "active" : ""}`}
></Octicon>
</div>
</div>
);
};
UPDATE:
TrimData & sorter function: outside of these functions the state for sort is updated but inside when i console sort its empty.
//sort providers
const sorter = (a, b) => {
const nameA = a.FullName.toUpperCase();
const nameB = b.FullName.toUpperCase();
let compare = 0;
if (sort.sortBy === "fullName") {
if (nameA > nameB) {
compare = 1;
} else if (nameA < nameB) {
compare = -1;
}
if (sort.order === "asc") {
return compare;
} else if (sort.order === "desc") {
return compare * -1;
}
};
const trimData = (items, pageNumber, rows) => {
const pages = items.length > 0 ? Math.ceil(items.length / rows) : 1;
const trimStart = (pageNumber - 1) * rows;
const trimEnd = trimStart + rows;
const trimedData = items.slice(trimStart, trimEnd);
setEndRange(trimEnd);
setTrimedData(trimedData);
setNumberOfPages(pages);
};
You can see sort is passed through to the sort component and set in the parent. Ive tried setting it in the descendant components but that didn't help. Also passing setSortBy offered the same result. I hope this isnt to big of a code chunk and someone can offer advice to a junior dev :)
The reason sorting is only working on the second click is because on the first click, while the sort value updates, the sort used by trimData function is the old sort object before the click was made.
So let's think about it, sorter is a function with a sort object that is set to {order: "", sortBy: ""} on its first render. When you click on a button, this is the sort object that is being used by your sorter function on your items.sort. Updating sort in setSortBy will then update the components with the new value, and the components will rerender. When the component has rerendered, the sorter function is using the sort that was just set, lets say {order: "asc", sortBy: "fullName"} (but that function hasn't yet been called). When you click a second time, let's say on the desc now, then the function is called with the sort object that was used to render the component i.e. the {order: "asc", sortBy: "fullName"} one, not the sort object that you passed to update the state {order: "desc", sortBy: "fullName"}.
Trying not to be too confusing as there are many approaches you can take, the most direct option you have is to make the "sorter" function using sort values you want to update your state with before you pass it into your items.sort(...):
const handleOnsortChange = (values) => {
// `values` is what you want the next `sort` value to be.
setSortBy(values);
// but `sort` wont be updated yet, so just use `values` instead.
const sorterWithCorrectSort = (a, b) => {
const nameA = a.FullName.toUpperCase();
const nameB = b.FullName.toUpperCase();
let compare = 0;
if (values.sortBy === "fullName") { // "sort" renamed to "values"
if (nameA > nameB) {
compare = 1;
} else if (nameA < nameB) {
compare = -1;
}
if (values.order === "asc") { // "sort" renamed to "values"
return compare;
} else if (values.order === "desc") { // "sort" renamed to "values"
return compare * -1;
}
};
trimData(items.sort(sorterWithCorrectSortValues), pageNumber, 50);
}
There are better ways to format the code, but I believe this is at the heart of the issue. Your sort object in your sorter function is the one that was used to render the component, not the one you used to update the state with.
Related
Im building a vue application for quizzes, I want to display the all the previous results of the person that has taken the quiz. For that I fetch the results from my backend and then pass them to the "view" component with a computed property:
computed: {
allResults() {
return this.$store.state.allResults;
},
I want to also sort out the best results, and the most recent results and display them separately, In order to do that I have the following methods:
bestResults() {
let orderedArray = this.allResults;
orderedArray.sort((a, b) =>
a.score < b.score ? 1 : a.score > b.score ? -1 : 0
);
let half = Math.round(orderedArray.length / 2);
let bestResults = orderedArray.slice(0, half);
return bestResults;
},
recentResults() {
let recentResults = this.allResults.slice(0, 5);
return recentResults;
}
This works, however it sorts the allResults array in a way that shows the scores from highest to lowest, which is what I do in the bestResults() function. This is a problem since I want to display the recentResults based on date, which should show the most recent result on top.
Well, you first sort the array in bestResults(), then use the sorted array in recentResults.
As a solution, you can create a new array with the same elements and sort that, which will leave the original array untouched:
bestResults() {
let orderedArray = [...this.allResults];
orderedArray.sort((a, b) =>
a.score < b.score ? 1 : a.score > b.score ? -1 : 0
);
let half = Math.round(orderedArray.length / 2);
let bestResults = orderedArray.slice(0, half);
return bestResults;
},
recentResults() {
let recentResults = this.allResults.slice(0, 5);
return recentResults;
}
I am using ag-grid to have the tool panel on the sidebar which has column checkboxes.I am having issue with sorting the unchecked columns in the alphabetical order.
I am trying to achieve some sort of functionality like shown the ag grid example.
I am using the below function to sort the columns by checked and unchecked order but not able to achieve alphabetical sorting on checked and unchecked columns.
const sortColumns = (columnDefs: gridColDef[]): void => {
columnDefs.sort((cd1,cd2) => +cd1.hide - +cd2.hide);
};
sortColumns(gridColumns);
Assuming that your array contains object (because you read the .hide value on them), are you referring to the correct variable when sorting? Is the name on the object under .hide?:
const sortColumns = (columnDefs: gridColDef[]): void => {
columnDefs.sort((cd1,cd2) => cd1.name - cd2.name); //Sort by name field
};
sortColumns(gridColumns);
but it would seem simpler to just call it on the array you want directly:
gridColumns.sort((cd1,cd2) => cd1.name - cd2.name);
You can sort it then by checked or not afterwards to group checked and un-checked together:
gridColumns.sort((cd1,cd2) => cd1.hide - cd2.hide);
It would be helpful to see the structure of the data your are trying to sort too.
Option 1
If you want to sort the columns in the sidebar only, use the handler onGridReady() and the api argument to apply a Custom Column Layout:
<AgGridReact
onGridReady={({api}) => {
const columnsToolPanel = api.getToolPanelInstance("columns");
const sortedColumnDefs = [...columnDefs].sort((cd1, cd2) => {
if (cd1.field < cd2.field) return -1;
if (cd1.field > cd2.field) return 1;
return 0;
});
// set custom Columns Tool Panel layout
columnsToolPanel.setColumnLayout(sortedColumnDefs);
}}
//...other props
/>
Option 2
If you want to sort the columns in the table and the sidebar - it's a bit simpler - sort the column defs first, then pass it into the component:
const sortedColumnDefs = [...columnDefs].sort((cd1, cd2) => {
if (cd1.field < cd2.field) return -1;
if (cd1.field > cd2.field) return 1;
return 0;
});
return (
<AgGridReact
columnDefs={sortedColumnDefs}
// ...other props
/>
);
--EDIT--
Option 3
If you want to display the columns in the sidebar like so:
Visible columns in default order
Hidden columns in alphabetical order
Solution is a bit more complex, use the onColumnVisible() handler and its columnApi argument to access a list of the columns. Separate visible from hidden using key visible, and sort accordingly.
<AgGridReact
onColumnVisible={({ api, columnApi }) => {
const columnsToolPanel = api.getToolPanelInstance("columns");
const columns = columnApi.getAllColumns();
const visibleColumns = columns.filter(
({ visible }) => visible === true
);
const hiddenColumns = columns.filter(
({ visible }) => visible === false
);
const sortedHiddenColumns = [...hiddenColumns].sort(
(cd1, cd2) => {
if (cd1.colDef.field < cd2.colDef.field) return -1;
if (cd1.colDef.field > cd2.colDef.field) return 1;
return 0;
}
);
const newColumns = [...visibleColumns, ...sortedHiddenColumns];
const newColDefs = newColumns.map(({ colDef }) => colDef);
columnsToolPanel.setColumnLayout(newColDefs);
}}
// ...other props
/>
Live Demo
useEffect(() => {
setShowProducts(true);
if (_cloneArray(currentProducts) > 0) {
sortByPrice();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const defaultProducts = () => {
let sortedProducts = _cloneArray(currentProducts);
return sortedProducts;
}
const sortByPrice = (e) => {
let sortValue = e.value;
let sortedProducts = _cloneArray(currentProducts);
if (sortValue === "lowest") {
sortedProducts = _sortArray(sortedProducts, "locationPrice");
} else if (sortValue === "highest") {
sortedProducts = _sortArray(sortedProducts, "locationPrice", "desc");
} else if (sortValue === "default") {
sortedProducts = defaultProducts();
}
setCurrentProducts(sortedProducts);
}
return (
<Menu menuButton={<MenuButton><CgSortAz size={20}/></MenuButton>} onClick={sortByPrice}>
<MenuItem value="default">Default</MenuItem>
<MenuItem value="lowest">Price: Low to High</MenuItem>
<MenuItem value="highest">Price: High to Low</MenuItem>
</Menu>
)
So here I've created the sort items feature in ascending and descending order and I want to return to default state, which is not working after so many trials. Please I need some help here
The issue is that once you sort an array and you override your variable there is no way to tell what the original order was.
const numbers = [9,1,8,2,7,3,6,4,5];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1,2,3,4,5,6,7,8,9]
There is no way to turn back [1,2,3,4,5,6,7,8,9] into the initial value, because there is no info stored about the initial value.
To solve this we should keep the original array around.
const numbers = [9,1,8,2,7,3,6,4,5];
// `sort()` mutates the array, so we have to make a copy first
// to prevent `numbers` from changing
let sorted = Array.from(numbers).sort((a, b) => a - b);
console.log(sorted); // [1,2,3,4,5,6,7,8,9]
Now if we want to restore the original order we can simply do:
sorted = numbers;
// or create a copy `Array.from(numbers)` if you intent to mutate `sorted`
The same applies for React. A common way to solve this would be to have 2 states. One containing the initial/default array, the second containing the sorted variant.
const [currentProducts, setCurrentProducts] = useState(...);
const [sortedProducts, setSortedProducts] = useState(currentProducts);
When sorting, store the result as sortedProducts. If you want to reset sortedProducts simply assign it to currentProducts.
const sortByPrice = (e) => {
let sortValue = e.value;
let products = _cloneArray(sortedProducts);
if (sortValue === "lowest") {
products = _sortArray(products, "locationPrice");
} else if (sortValue === "highest") {
products = _sortArray(products, "locationPrice", "desc");
} else if (sortValue === "default") {
products = currentProducts;
}
setSortedProducts(products);
}
Note that you should use sortedProducts in your view instead of currentProducts.
Since we never update currentProducts there is no real reason for it to be a state. This could just be a constant or a property (wherever the values comes from). If the values comes from an external API (or something async) it makes sense to keep currentProducts as an state, because it has to be set once fetched.
Here is an example that keeps the original order NUMBERS around and stores the sorted variant in a separate state sorted:
const NUMBERS = [9,1,8,2,7,3,6,4,5];
function Numbers() {
const [sorted, setSorted] = React.useState(NUMBERS);
const sort = (e) => {
switch (e.target.value) {
case "asc":
setSorted(Array.from(NUMBERS).sort((a, b) => a - b));
break;
case "desc":
setSorted(Array.from(NUMBERS).sort((a, b) => b - a));
break;
case "default":
setSorted(NUMBERS);
break;
}
};
return <React.Fragment>
<div className="sort-actions">
<button onClick={sort} value="asc">asc</button>
<button onClick={sort} value="desc">desc</button>
<button onClick={sort} value="default">default</button>
</div>
<p>{JSON.stringify(sorted)}</p>
</React.Fragment>;
}
ReactDOM.render(<Numbers />, document.querySelector("#numbers"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="numbers"></div>
I am making a card game in React JS that requires 3 sets of unique cards.
The way the format works is there are ingredient cards that can create potions. The ingredients are dealt in the Top Row of the game, so I have the component called TopRow.
Since these are not normal playing cards I had to generate arrays with 10 of each of 5 different cards for the deal. ( shuffle(a) )
Then I am splicing the deal to only get 5 cards ( a.splice(5); )
So I want the value of the ingredients to increment based on the number of times the ingredients appear, example: function handleHoneyIncrement should increase countHoney by 1.
I've tried a couple different things and I guess I am having a brain fart on how to make a for loop for this.
function TopRow(props) {
let a=["Honey0", "Bone0", "Herbs0", "Mushroom0", "Seeds0",
"Honey1", "Bone1", "Herbs1", "Mushroom1", "Seeds1",
"Honey2", "Bone2", "Herbs2", "Mushroom2", "Seeds2",
"Honey3", "Bone3", "Herbs3", "Mushroom3", "Seeds3",
"Honey4", "Bone4", "Herbs4", "Mushroom4", "Seeds4",
"Honey5", "Bone5", "Herbs5", "Mushroom5", "Seeds5",
"Honey6", "Bone6", "Herbs6", "Mushroom6", "Seeds6",
"Honey7", "Bone7", "Herbs7", "Mushroom7", "Seeds7",
"Honey8", "Bone8", "Herbs8", "Mushroom8", "Seeds8",
"Honey9", "Bone9", "Herbs9", "Mushroom9", "Seeds9"
];
shuffle(a);
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a.splice(5);
}
let imageIngredients = a.map(image => {
return <img key={image} src={require(`../pngs/${image}.png`)}
alt="ingredients" className="img-responsive"
style={{width:"15%", float:"left"}}
/>
});
let handleHoneyIncrement = () => {
if (shuffle.length= "Honey0" ||"Honey1" ||"Honey2" ||"Honey3" ||"Honey4" ||"Honey5" ||"Honey6" ||"Honey7" || "Honey8" || "Honey9" ){
this.setState({countHoney: this.state.countHoney + 1})
};
};
return (
<div className="row" id="topRow"
style={{WebkitBorderRadius:2, WebkitTextStrokeColor: "red", width:"90%", maxHeight:"30%", padding:0}} >
<div className="col-6-md">
<img src={require('../pngs/IngredientBacks.png')} alt="ingredientsBack" style={{width:"15%", float:"left"}} />
</div>
<div className="col-6-md">
{imageIngredients}
{handleHoneyIncrement}
{a}
</div>
</div>
);}
export default TopRow;
Not 100% sure if this is what you were going for, but it sounds like you just need to turn the ingredients list into a collection of ingredient/count pairs?
const ingredientCounts = a.reduce((obj, curr) => ({
...obj,
[curr]: obj[curr] ? obj[curr] + 1 : 1
}), {})
ingredientCounts["Honey0"] // 1
If you're looking to count all Honeys together like Honey0 + Honey1, etc., this should work:
const ingredientCounts = a.reduce((obj, curr) => {
const keys = ["Honey", "etc"]; // maybe this list should be somewhere else, IDK
const key = keys.find(k => curr.includes(k)); // missing null check :)
return {
...obj,
[key]: obj[key] ? obj[key] + 1 : 1
}
}, {})
ingredientCounts["Honey"] // 10
Then we can set state for all of them like:
this.setState({
counts: ingredientCounts
})
And have a state of counts like:
{
Honey: 10,
etc: 0
}
I'm not 100% sure that I understand your goals correctly, but I think a simplified version is that you want to show:
5 random cards from your deck
A button or trigger that shuffles the deck and displays a new hand of 5 cards from the same deck
A count of the total number of honey cards accumulated as the hand is updated
There are a number of confusing things in your code sample, so rather than try to make corrections I threw up a quick demo of how I would approach that problem with some comments explaining what I did differently, given these assumptions. https://codesandbox.io/s/trusting-mclean-kwwq4
import React, { useState, useEffect } from "react";
// The deck of cards is probably a constant whose values never change directly.
// It's possible that I'm wrong and the deck *does* change, but even so I imagine
// it would come from a prop or context from a parent component. Either way the
// cards array should not be mutable.
const CARDS = [
"Honey0", "Bone0", "Herbs0", "Mushroom0", "Seeds0",
"Honey1", "Bone1", "Herbs1", "Mushroom1", "Seeds1",
"Honey2", "Bone2", "Herbs2", "Mushroom2", "Seeds2",
"Honey3", "Bone3", "Herbs3", "Mushroom3", "Seeds3",
"Honey4", "Bone4", "Herbs4", "Mushroom4", "Seeds4",
"Honey5", "Bone5", "Herbs5", "Mushroom5", "Seeds5",
"Honey6", "Bone6", "Herbs6", "Mushroom6", "Seeds6",
"Honey7", "Bone7", "Herbs7", "Mushroom7", "Seeds7",
"Honey8", "Bone8", "Herbs8", "Mushroom8", "Seeds8",
"Honey9", "Bone9", "Herbs9", "Mushroom9", "Seeds9"
];
const initialCards = [];
function TopRow(props) {
// Keep the current hand of cards in state rather than mutating an array
// directly in the function body. React function components should be pure,
// with all side effects occurring inside of effect hooks.
let [cards, setCards] = useState(initialCards);
let [honeyCount, setHoneyCount] = useState(
countSubstrings(initialCards, "Honey")
);
let imageIngredients = cards.map(image => (
<img
key={image}
src={require(`../pngs/${image}.png`)}
alt={humanReadableAltTag}
className="img-responsive"
style={{ width: "15%", float: "left" }}
/>
));
function shuffleCards() {
// Reset your hand of cards with the original array (the deck)
setCards(shuffleArray(CARDS));
}
// Return all state to initial values
function reset() {
setCards(initialCards);
setHoneyCount(countSubstrings(initialCards, "Honey"));
}
// Any time our cards are updated, we want to increment the number of Honey
// cards in our hand. useState accepts a lazy initializer to access the
// previous state, which is very useful for effects like this!
useEffect(() => {
setHoneyCount(count => count + countSubstrings(cards, "Honey"));
}, [cards]);
return (
<div
{...props}
className="row"
id="topRow"
style={
{
WebkitBorderRadius: 2,
WebkitTextStrokeColor: "red",
width: "90%",
maxHeight: "30%",
padding: 0
}
}
>
<button onClick={shuffleCards}>
{cards.length ? "Shuffle" : "Deal"}
</button>
<button onClick={reset}>Reset</button>
<hr />
<div className="col-6-md">
<img
src={require("../pngs/IngredientBacks.png")}
alt="Back of ingredient card"
style={{ width: "15%", float: "left" }}
/>
</div>
<div className="col-6-md">
{imageIngredients}
</div>
<hr />
<div>
<strong>TOTAL HONEY COUNT:</strong> {honeyCount}
</div>
</div>
);
}
export default TopRow;
// I put these utility functions outside of the component body since there is no
// real reason to recreate them on each render.
/**
* #param {any[]} array
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
// Use slice instead of splice here to prevent mutating the original array
return array.slice(0, 5);
}
/**
* #param {string[]} array
* #param {string} subs
*/
function countSubstrings(array, subs) {
return array.filter(card => card.includes(subs)).length;
}
There's an array looking as follows:
[[3,0], [6,0], [2,0], [9,0]....]
I'm trying to create a React/Redux reducer that changes the value of one of the 0s to 1. I click on an element and an action is dispatched. Idx is the index of an element in an array (eg. 0, 1, 2, 3)
export const toggleTile = (idx) => {
return {
type: TOGGLE_TILE,
idx
};
};
The reducer below does not work as I'd like it to be. I just created the skeleton of the conditional statements. If I click on a tile with index 3 (so the fourth tile), it changes the [n,0] to [n,1] for all elements. First of all it should only do if I click any of the tiles, and it should change [n,0] to [n,1] only for the clicked tile so I'm trying to change the 3 in the code below to the index of an 'i' element being mapped.
export default (state = [], action = {}) => {
switch (action.type) {
case LOAD_GRID:
return action.payload || [];
case TOGGLE_TILE:
return state.map((i) => {
if (action.idx === 3) {
return (i[1] === 0
? [i[0], parseInt(i[1], 10) + 1]
: [i[0], parseInt(i[1], 10) - 1]
);
}
return [i[0], i[1]];
});
default:
return state;
}
};
A grid component:
export default class Grid extends Component {
render() {
const mygrid = [];
this.props.inGrid.forEach((r, i) => {
mygrid.push(
<Square
key={i}
idx={i}
sqValue={r}
toggleTile={this.props.toggleTile}
/>
);
});
const { grid } = styles;
return (
<View style={grid}>
{mygrid}
</View>
);
}
}
export default class Square extends Component {
myaction() {
this.props.toggleTile(this.props.idx);
console.log(this.props.idx);
}
render() {
const { square, textStyle, squareActive } = styles;
const { sqValue } = this.props;
return (
<TouchableHighlight
style={[square, sqValue[1] && squareActive]}
onPress={this.myaction.bind(this)}
>
<View>
<Text style={textStyle}>{sqValue[0]},{sqValue[1]}</Text>
</View>
</TouchableHighlight>
);
}
}
Please advise.
There are a number of ways you can do this, with varying degrees of verbosity (due to Redux's insistence on immutability), but here's a pretty straightforward one:
case TOGGLE_TILE:
const nextValue = state[action.idx].slice(); // Make a copy of the tuple to be toggled
nextValue[1] = nextValue[1] === 0 ? 1 : 0; // Toggle it
const nextState = state.slice(); // Make a copy of the state
nextState[action.idx] = nextValue; // Replace the old tuple with the toggled copy
return nextState;
Or:
case TOGGLE_TILE:
const prevValue = state[action.idx];
const nextState = state.slice();
nextState[action.idx] = [ prevValue[0], prevValue[1] === 0 ? 1 : 0 ];
return nextState;
Ok, I'm gonna try and see what we can do with just the following portion of code you shared.
I would like to note that the code presented is not succinct. It would be a great benefit to yourself, your team, as well as anyone here on this site if your code was refactored the more you understand what you need to build.
// So state is just an array of arrays...
var state = [3,0], [6,0], [2,0], [9,0]];
return state.map((i) => { // i => [3,0] or [9,0] !! i is not index !!
// Map is going to iterate over the entire array of arrays.
if (action.idx === 3) {
// action.idx is what comes in from the click.
// Here is where your doing your work.
// If the first element of "i" is zero, then
// return the same array but add 1 to the second element of array.
// so [3,0] or [4,0] should become [3,1] or [4,1] but only for #3 as
// action.idx === 3 says to only change when... Nope, this is not how it will work. You need your exception in the MAP.
return (i[1] === 0 ? [i[0], parseInt(i[1], 10) + 1] : [i[0], parseInt(i[1], 10) - 1]);
}
// ?? Why don't you just return i, as i is each array of numbers.
return [i[0], i[1]];
});
// It seams to me that something like this should work, just plug and play.
// I am assuming a few things here that I will spell out. If they are incorrect, let me know and I'll change it.
// state will be an array of arrays that only contain two numbers each.
// They may or may not be in original order.
// The second element of each array will be either 0 or 1.
var state = [3,0], [6,0], [2,0], [9,0]];
state.map(function(cell){ // call it what you want, you called it "i".
if(cell[0] === action.idx){ // If clicked action index is === to cell[0]
// You could just hard code 3 as you did above, but this makes it dynamic.
// and only changes the cell that was clicked.
cell[1] = cell[1] ? 1 : 0; // if cell[1] is 0, then it is falsey, no need for complex logic. No need to parseInt if they are numbers to begin with. But if you do, then use "+" to change a string to number.
}
return cell;
});
Without notes
var state = [3,0], [6,0], [2,0], [9,0]];
state.map(function(cell){
if(cell[0] === action.idx){
cell[1] = cell[1] ? 1 : 0;
}
return cell;
});