Updating single item in react state hook - javascript

I have array useState hook which consists of items with className. I want to update className of a single item from that hook. How do I update single value of a useState hook.Of course I am going to put condition before updating .
here is the code -
display.map( word =>
( Words[leftPointer] === word.props.children ?
{...display, word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)} :
display )
)
setDisplay(display)
here display contains array of span.
what I want is go change the className based on the condition example- after updating the item
className = "singleWord greenword" should be className="singleWord" of that item.

Map function will return a new array so you just have to store the array in a new variable and set the state with that new variable
const newDisplay = display.map( (word) =>{
if(Words[leftPointer] === word.props.children){
return {word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)}
}else{
return word
}
})
setDisplay(newDisplay)

I would recommend against setting className in the state's domain as that is part of the UI's domain. Instead data changes can be used to signal responses in the UI rendering. This is what it means for a program to be data-driven.
Here is a minimal verifiable example. Run the program below and change some quantities to see the display state update. The className is automatically changed based on conditions applied to the application state.
function App() {
const [display, setDisplay] = React.useState([
{item: "walnut", quantity: 0 },
{item: "peanut", quantity: 3 },
{item: "donut", quantity: 6 }
])
function update(index) { return event =>
setDisplay([
...display.slice(0, index),
{ ...display[index], quantity: Number(event.target.value) },
...display.slice(index + 1)
])
}
return <div>
{display.map((d, index) =>
<div key={index}>
{d.item}:
<input
className={d.quantity > 0 ? "instock" : "outofstock" }
value={d.quantity}
onChange={update(index)}
/>
</div>
)}
<div>{display.every(d => d.quantity > 0) ? "✅" : "❌"}</div>
<pre>{JSON.stringify(display, null, 2)}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
input { outline: 0 }
.instock { border-color: limegreen; }
.outofstock { border-color: red; }
pre { padding: 0.5rem; background-color: #ffc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

could you just use a state variable that holds the text for className and update that state variable on the useState hook to whatever you want?

Related

Counter Variable not incrementing React

I have 6 cubes displayed on the page and there is a counter.
When you click on a cube, its background changes to red and the counter increases by one, but the counter does not want to increase. I always have it 1. But if you remove the setcub([...cubikinapole]) line, then the counter starts working as it should, but now the background does not change. What to do?
import "./styles.css";
import {useState} from 'react'
export default function App() {
let [cubikinapole, setcub] = useState([
{id:1,title:1, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png", style: 'none'},
{id:2,title:2, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png" , style: 'none'},
{id:3,title:3, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png" , style: 'none'},
{id:4,title:4, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png" , style: 'none'},
{id:5,title:5, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png" , style: 'none'},
{id:6,title:6, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png" , style: 'none'},
])
let [count,se] = useState(0)
function a(el) {
se(count++)
el.style = 'active'
setcub([...cubikinapole])
console.log(count)
}
return (
<div className="App">
{cubikinapole.length !== 0 ?
cubikinapole.map((el)=>
<div id='cub' key={el.id} className={el.style} onClick={()=>a(el)}>
<img src={el.img} />
</div>
)
: console.log() }
</div>
);
}
CSS
.active{
background-color: red;
}
you're never supposed to change the value of a useState by the value itself, only by the setter - what you're doing where you're writing se(count++) is giving the old value of the count to se and then directly changing count by count++,
so it looks like a race condition, you can fix it by just changing it to se(count+1) and your code will run as it should
There are a few issues. Note: I've changed the names of some variable for clarity.
You can't set your counter like that. Ideally you want to take the previous state, and then update and return that. So setCount(count++) becomes setCount(prev => prev += 1).
ids should be unique. If you want to uniquely identify an element you can use a data attribute, and then pick up that value in the handler.
With the id in hand in your handler you should find the object in state that where the object id matches the id of the element you clicked on, and then update that. To do that make a copy of the state first, update the object, and then set the state using the changed copy.
It's not really clear whether it was the background of the cube you wanted to change, or the image itself so I've just left your code as-is. For the actual image to change would be slightly more difficult.
const { useState } = React;
// For clarity I'm passing in the image
// config to the component, and then setting the
// cube state with it
function Example({ config }) {
// Initialise the states
const [ cubes, setCubes ] = useState(config);
const [ count, setCount ] = useState(0);
// When an element is clicked take the
// id from its data set. Make a copy of the state
// and then find the index of the object that has a
// matching id. Then update that object with the new
// style, and set both the cube and count states
function handleClick(e) {
const { id } = e.currentTarget.dataset;
const copy = [...cubes];
const index = copy.findIndex(cube => cube.id === Number(id));
if (index >= 0) copy[index].style = 'active';
setCubes(copy);
setCount(prev => prev += 1);
}
// I've added a count element here.
// `map` over state adding the data attribute
// to the div element.
return (
<div className="App">
<div className="counter">Count: {count}</div>
{config.map(obj => {
const { id, style, img } = obj;
return (
<div
key={id}
data-id={id}
className={style}
onClick={handleClick}
><img src={img} />
</div>
);
})}
</div>
);
}
const config=[{id:1,title:1,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",style:"none"},{id:2,title:2,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",style:"none"},{id:3,title:3,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",style:"none"},{id:4,title:4,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",style:"none"},{id:5,title:5,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",style:"none"},{id:6,title:6,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",style:"none"}];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
.active { background-color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can try this code. count++ increases its value after the setCount function is executed, so you should use count+1 to pass the increased value to the function. And you'd better use useEffect to monitor the count value.
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
let [cubikinapole, setCub] = useState([
{
id: 1,
title: 1,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",
style: "none"
},
{
id: 2,
title: 2,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",
style: "none"
},
{
id: 3,
title: 3,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",
style: "none"
},
{
id: 4,
title: 4,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",
style: "none"
},
{
id: 5,
title: 5,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",
style: "none"
},
{
id: 6,
title: 6,
img: "https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",
style: "none"
}
]);
let [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
function a(el) {
setCount((prev) => prev + 1);
el.style = "active";
setCub([...cubikinapole]);
}
return (
<div className="App">
{cubikinapole.length !== 0
? cubikinapole.map((el) => (
<div key={el.id} className={el.style} onClick={(e) => a(el)}>
<img src={el.img} alt={el.title} />
</div>
))
: console.log()}
</div>
);
}
Live demo: https://codesandbox.io/s/amazing-haze-0zdb26?file=/src/App.js:0-1369

How can I add new elements to a list?

I was trying to add a new element of array to the list with update of one property (id). I want to make it 1 more than length of array.
But I get some weird outputs, with add every new object. All elements are getting array.length +1 value.
I made several variations of this code with let, const or even operating directly on this.state.produktsToBuy, and every time I got the same output
handleAddToShop = (produktToBuy) => {
const id = this.state.produktsToBuy.length+1;
produktToBuy.id = id + 1;
const produktsToBuy = this.state.produktsToBuy;
produktsToBuy.push(produktToBuy);
this.setState({produktsToBuy});
};
I Should get the output as 1,2,3,4,5,6,7
But on the end I get 7,7,7,7,7,7
Make sure you're not mutating the state directly. In JS, objects are a reference type. When you assign this.state.produktsToBuy to const produktsToBuy and push something to produktsToBuy, you're actually pushing to the original this.state.produktsToBuy and you modify the state.
You can use the spread operator (...) to create a shallow copy of the state (produktsToBuy):
class App extends React.Component {
state = {
items: [
{ name: "test item 1", price: 4.99 },
{ name: "test item 2", price: 7.99 },
{ name: "test item 3", price: 19.99 }
],
produktsToBuy: []
};
handleAddToShop = (produktToBuy) => {
this.setState((prev) => ({
produktsToBuy: [
...prev.produktsToBuy,
{
...produktToBuy,
id: prev.produktsToBuy.length + 1
}
]
}));
};
render() {
return (
<div className="App">
<div style={{ display: "flex" }}>
{this.state.items.map((item) => (
<div
key={item.name}
style={{
border: "1px solid #ccc",
margin: "1rem",
padding: "1rem",
textAlign: "center"
}}
>
<h3>{item.name}</h3>
<p>${item.price}</p>
<button onClick={() => this.handleAddToShop(item)}>Add</button>
</div>
))}
</div>
<pre>{JSON.stringify(this.state.produktsToBuy, null, 2)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You should be maintaining all of the previous state if there's anything other than just the produktsToBuy. Also, you always need the functional form of setState if anything you're setting is dependent on the previous state(as is OFTEN the case). And, like Zsolt said, you never mutate the state directly in React. Here's my answer (very similar to #Zsolt Meszaros'). Note: .concat creates a new array, so we don't have to worry about mutating the original.
handleAddToShop = (produktToBuy) => {
this.setState((prevState) => {
const { produktsToBuy } = prevState;
return {
...prevState,
produktsToBuy: produktsToBuy.concat([
{
...produktToBuy,
id: produktsToBuy.length + 1,
},
]),
};
});
};

Unable to check/uncheck the checkbox

I am making a react application where checkboxes for jobtitles are populated from the dynamic data from api and it will be exactly like given snippet.
const departments =
[
{
"sectorId":29,
"sectorName":"Building Materials Mfg. & Distribution",
"departments":[
{
"deptName":"Manufacturing",
"jobTitles":[
{
"453":false,
"JobTitleID":453,
"DepartmentID":101,
"JobName":"Consultant",
"Deleted":false,
"SortOrder":5
},
{
"323":true,
"JobTitleID":323,
"DepartmentID":101,
"JobName":"Quality Control",
"Deleted":false,
"SortOrder":1
}
]
},
{
"deptName":"Warehouse",
"jobTitles":[
{
"326":false,
"JobTitleID":326,
"DepartmentID":98,
"JobName":"Warehouse Supervisor",
"Deleted":false,
"SortOrder":1
}
]
},
{
"deptName":"Administration",
"jobTitles":[
{
"384":true,
"JobTitleID":384,
"DepartmentID":115,
"JobName":"Controller",
"Deleted":false,
"SortOrder":1
}
]
}
]
}
]
const handleJobTitle = (event, job) => {
const { checked } = event.target;
if (checked) {
document.getElementById(job.JobTitleID).checked = true;
} else {
document.getElementById(job.JobTitleID).checked = false;
}
console.log(document.getElementById(job.JobTitleID));
};
const App = () => (
<div> {departments && departments.map((levelOne, i) => (
<div
key={i}
>
<p> {levelOne.sectorName} </p>
{levelOne.departments.map((levelTwo, j) => (
<div key={j}>
<p >
{" "}
{levelTwo.deptName}{" "}
</p>
{levelTwo.jobTitles.map((job, l) => (
<div
key={l}
>
<input type="checkbox" id={job.JobTitleID} onChange={(e) => {handleJobTitle(e, job)}} name={job.JobName} checked={job[job.JobTitleID]}/>
<span>{job.JobName}</span>
</div>
))}
</div>
))}
</div>
))} </div>
)
// Render it
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here the jobTitles data will be like,
"jobTitles":[
{
"453":false,
"JobTitleID":453,
"DepartmentID":101,
"JobName":"Consultant",
"Deleted":false,
"SortOrder":5
},
{
"323":true,
"JobTitleID":323,
"DepartmentID":101,
"JobName":"Quality Control",
"Deleted":false,
"SortOrder":1
}
]
And I make the checkbox checked based on the value of "453":false like,
<input type="checkbox" ...... checked={job[job.JobTitleID]}/>
And the checkboxes are checked here but when I try to uncheck the checkbox in onChange handler like,
const handleJobTitle = (event, job) => {
document.getElementById(job.JobTitleID).checked = false;
}
The checkboxes are not unchecked.. If I console/inspect the input element then the checked atribute is not removed.
Additional Info: It is also not possible to check the checkbox which is in unchecked state.. So in common I couldn't make any change to checkbox..
Could anybody please help me to achieve the solution and I am got stuck for too long with this as I am new to react..
Big thanks in advance.
Issue(s)
Direct DOM mutation is an anti-pattern in react (it is outside react so react can't know to rerender).
The handler also doesn't access the checked property of the onChange event.
const { checked } = event; // <-- should be event.target
Solution
Use react state to maintain the checked status in component state.
Move the departments data into component state.
const [departments, setDepartments] = useState(departmentsData);
Move the handleJobTitle handler into the component. I suggest using a curried function that accepts the sector id, department name, and the job id, you'll need these to "trace" the path to the correct nested object. You don't need to worry about getting the checked property from event.target as you can simply invert a checked value in state via the JobTitleID key value.
The idea here is to shallow copy any nested state, into new object references for react, that is being updated.
const handleJobTitle = (sectorId, deptName, JobTitleID) => () => {
setDepartments((data) =>
data.map((sector) =>
sector.sectorId === sectorId
? {
...sector,
departments: sector.departments.map((dept) =>
dept.deptName === deptName
? {
...dept,
jobTitles: dept.jobTitles.map((job) =>
job.JobTitleID === JobTitleID
? {
...job,
[JobTitleID]: !job[JobTitleID]
}
: job
)
}
: dept
)
}
: sector
)
);
};
Now simply attach the handleJobTitle to the input's onChange handler.
<input
type="checkbox"
id={job.JobTitleID}
onChange={handleJobTitle( // <-- pass the required values
levelOne.sectorId,
levelTwo.deptName,
job.JobTitleID
)}
name={job.JobName}
checked={job[job.JobTitleID]}
/>
You need to define an Id parameter in the input tag.
<input type="checkbox" id="JobTitleCheckBox" ...... checked={job[job.JobTitleID]}/>
Then use that id to fetch the node and change its status
const handleJobTitle = (event, job) => {
document.getElementById(job.JobTitleID).checked = false;
}

State set method did not effect the main object - ReactJS

I have three components in my project, App, DataTable and Table.
The App will render a DataTable that contains the Table component. I just called DataTable with data and columns props in it.
// data sample
const tmpData = [
{
name: "Can",
age: 4,
}, {
name: "David",
age: 44,
}, {
name: "Sara",
age: 14,
}, {
name: "Hani",
age: 24,
}
]
// main columns array
const tmpColumns = [
{
title: "Name",
accessor: "name",
}, {
title: "Age",
accessor: "age",
}
]
function App() {
return (
<div className="App" style={{background: "#f0f0f0", padding: "1rem"}}>
<div>App Component:</div>
<DataTable data={tmpData} columns={tmpColumns}/>
</div>
);
}
DataTable component is just for handling selection and filter actions on my table. So, it could manipulate the data and the columns. the Table will render in it and here is the code of DataTable.
function DataTable(props) {
const [data, setData] = useState(props.data)
const [columns, setColumns] = useState(props.columns)
const [selection, setSelection] = useState([])
useEffect(() => {
// add select columns after component mount
handleColumnChange()
}, [])
// listen to selection change
useEffect(() => {
// selection change log, It change on each select.
console.log(selection);
}, [selection])
function handleRowSelect(rowName) {
const keyIndex = selection.indexOf(rowName);
console.log(selection, rowName, keyIndex);
if (keyIndex === -1)
setSelection(preSelection => ([...preSelection, ...[rowName]]))
else
setSelection(preSelection => preSelection.filter(sl => sl !== rowName))
}
function handleColumnChange() {
// add select column if not added already
if (!columns.find(col => col.accessor === 'select')) {
setColumns([
...[{
title: "Select",
accessor: "select",
// this method will execute to render checkbox on Select table
Cell: function (row) {
return <input type="checkbox"
onChange={() => handleRowSelect(row.name, selection)}
checked={selection.includes(row.name)}/>
},
}],
...columns,
])
}
}
return (
<div className="DataTable" style={{background: "#e0e0e0", padding: "1rem"}}>
<div>Data Table:</div>
<Table {...{data, columns}}/>
</div>
)
}
Table component will render columns and suitable data for them. For each column in columns array we have an item to access data (accessor) or an executable method to return custom data (Cell) and here is its code.
function Table(props) {
const [data, setData] = useState(props.data)
return (
<div className="Table" style={{background: "#d5d5d5", padding: "1rem"}}>
<div>Table</div>
<table>
<tbody>
<tr>
{props.columns.map((th, key) => (
<th key={key}>{th.title}</th>
))}
</tr>
{/* generating data rows */}
{data.map((dr, key) => (
<tr key={key}>
{columns.map((col, index) => (
<td key={index}>
{
// the "Cell" method has high priority than "accessor" selector
(col.Cell && typeof col.Cell === "function") ?
col.Cell(dr) :
dr[col.accessor]
}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
As you saw above, to handle row selection I manipulate the columns in the DataTable component by adding a new column at first index of my columns array. Everything works fine for now. But, when I try to select a row, the Call methods of the select column, could not access the selection array state of my DataTable component. and it's my problem!
Actually, on each select, the selection array must update and target checkbox must check. Apparently, there is a copy of the selection that changes (or maybe not changes).
You also could check the whole project on CodeSandbox
I have some fixes to your code, check it out on CodeSandbox.
The idea is that you don't need to put the columns to state, instead you just need to get the columns with the selection box.
I also added a simple optimization to it by implementing React.useMemo to memoized the calculated columns. It will only be re-calculated when the props.columns and selection state changes.
Hope this helps! Happy coding! :)
Ok, your tmpData passed from App to DataTable is read-only. So by design you will not see any change your data along the way.
There're couple of ways to get it working, mostly having something to do to allow your DataTable to pass the change back to App if that happens.
Step 1, you could add one prop called onRowClick on the DataTable,
<DataTable data={tmpData} onRowClick={row => { console.log(row) } />
Step 2, you need to allow your tmpData to change after the event. You are using hooks, so we can
const [tmpData, setTmpData] = useState([sampleData])
return (
<DataTable data={tmpData} onRowClick={row => {
// use setTmpData to adjust the tmpData to get a new instance of tmpData
} />
)
Of course for things with this complexity, we normally use useReducer instead of useState, since there's definitely other things that you want to do other than selecting the row :)

Displaying array data onclick in React

I would like to iterate through an array and display each element in the array every time I click a button.
so far I have this:
{this.state.users.map(function(user, index){
return <p key={ index }>{user.name} </p>;
}, this)}
This displays each users name on screen, one after each other.
How can I do it so it just shows users[0] and then moves to users[1] or even better, click removes the user at position users[0] and then a new person is at position users[0] and then if the array is empty, it displays the text 'no more users'
I know how to remove elements from arrays etc, it's just the displaying one at a time in React land which I cant do
Based on my understanding to your question may be you are trying to achieve this -
let users = [{
name: "abcd"
}, {
name: "xyz"
}, {
name: "temp"
}];
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 0
};
}
tick =() => {
let activeIndex = this.state.activeIndex;
if (activeIndex == this.props.users.length -1){
activeIndex = 0;
} else {
activeIndex++;
}
this.setState({
activeIndex
});
}
render() {
return (
< ul className = "list-group" >
< li className = "list-group-item" >
{this.props.users[this.state.activeIndex].name}
< button className = "btn btn-default" onClick={this.tick} >
show Next
< /button>
</li >
< /ul>
);
}
}
ReactDOM.render(
< Example users = {users}/ > ,
document.getElementById('test')
);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<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="test">
</div>
Keep the index of activeUser in state, then render just this one user: { this.state.users[ this.state.activeUser ].name}. Increment/decrement the activeUser on click.
demo on jsfiddle
var Users = React.createClass({
getInitialState(){
return {
users : [{ name : 'John'}, { name : 'Jane'}, { name : 'Robert' }],
activeUser : 0
};
},
next(){
/* here you should add checking if there are more users before setting the state */
this.setState({ activeUser : this.state.activeUser + 1 });
},
render: function() {
return (<div>
<p>{ this.state.users[ this.state.activeUser ].name} </p>
<span onClick={ this.next }>next</span>
</div>);
}
});
Seems like you have the pseudo code there in your question which like Rajesh asked seems to describe some kind of paginated list of users/carousel.
You'll need to define the maximum number of users that are visible in the list and then store the current topmost element index. I'd suggest storing these in state.
Then store the list of names as an array of object each containing the name details as well as a parameter that will represent whether it is visible or not.
Your initial state could look like this:
{
currentTopElement: 0,
maxNamesToShow: 1,
names: [
{name: 'John Smith', isVisible: false},
{name: 'Jane Smith', isVisible: false},
...etc.
]
}
Add a click handler on your button would be required that would increment the currentTopElement by one.
iterate through the names array setting isVisible to false unless the index matches that of currentTopElement, if it does set isVisible to true
To finish off, in the component that needs to render your names list can do so if you conditionally render a name only if its corresponding isVisible value is true:
const elementsToShow = this.state.filter( t => t.isVisible);
You can the iterate over elementsToShow like so:
elementsToShow.map((user, index) => <p key={ index }>{user.name} </p>);

Categories

Resources