How to update a react useState while rendering the state? - reactjs

I am making a ordering app which has a state of array of object as shopping cart looks like below: (The state is initialized in App.js)
setCart([
...cart,
{
id: id,
quantity: drinkQuantity,
title: selectedProduct.caption,
title_cn: selectedProduct.caption2,
price: tempPrice * drinkQuantity,
variety: variety[varietyIndex].caption,
modCountObj: modCountObj,
uniqueCountObj: uniqueCountObj,
},
]);
Then I rendered the cart in a modal in Cart.js(passed the cart state from app.js) by mapping through the array with a counter that can control the quantity of each item
{cart?.map((item, index) => {
return (
<li class=" btn list-group-item h2 m-3" key={index}>
<div>
<div class="row fw-bold">
{chinese ? item.title_cn : item.title}
</div>
<div class="row fw-bold ">
<div className="col-4 ">{item.variety}</div>
<div className="col-4 "></div>
<div
className="col-1 btn btn-sm btn-outline-primary py-0 "
name="decreaseDrinkQuantity"
onClick={onClickDecreaseQuantity}
>
-
</div>
<div className="col-1 ">{item.quantity}</div>
<div
className="col-1 btn btn-sm btn-outline-primary py-0 "
name="increaseDrinkQuantity"
>
+
</div>
</div>
</div>
...
my onClick function:
const onIncreaseQuantity = (index) => {
setCart((prevState) =>
prevState.map((item, o) => {
if (index === o) {
return {
...item,
quantity: item.quantity + 1,
};
}
return item;
})
);
};
Error Message:
Warning: Cannot update a component (`App`) while rendering a different component (`Cart`). To locate the bad setState() call inside `Cart`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Cart (http://localhost:3000/main.77d8967….hot-update.js:29:5)
at div
at App (http://localhost:3000/static/js/bundle.js:53:80)
at QueryClientProvider (http://localhost:3000/static/js/bundle.js:56754:21)
overrideMethod # react_devtools_backend.js:4012
printWarning # react-dom.development.js:84
error # react-dom.development.js:57
warnAboutRenderPhaseUpdatesInDEV # react-dom.development.js:27489
scheduleUpdateOnFiber # react-dom.development.js:25496
dispatchSetState # react-dom.development.js:17525
onIncreaseQuantity # Cart.js:82
(anonymous) # Cart.js:123
Cart # Cart.js:102
I am guessing the problem is that I tried to change the state while rendering it but I am not sure how to solve this.
Thanks for your expertise.
At first I tried making a temp variable to store cart and change the content then setCart(tempCart) but this will increase the quantity of all elements of the whole array
let tempCart = cart;
tempCart[index].quantity += 1;
console.log("+");
setCart(tempCart);
I want to change the quantity of the item that was clicked instead of all the items in the array.
Update 30-Jan:
I have changed my screenshots to code snippets.. I am really sorry about the inconvenience and unclear. Please let me know if I can provide more info.

Related

every accordion opens when i click on any accordion : react js

every accordion opens when I click on any accordions can anyone help with this I want only one to open at a time
const [isActive, setIsActive] = useState(false);
<div className="accordion">
{Object.values(mapped).map((item, index) => (
//{predefined.map(({ date }) => (
<div className="accordion-item">
<div
className="accordion-title"
onClick={() => setIsActive(!isActive)}
>
<div>{item[0].date}</div> <div>11:45</div>
<div>painscale</div>
<div>4</div>
<div>{isActive ? "-" : "+"}</div>
</div>
{isActive && (
<div className="accordion-content">
<table>
<thead>
<tr>
<th>sdcs</th>
</tr>
</thead>
{item.map((e) => {
return (
<tr>
<td> {e.time}</td> <td>{e.d}</td>{" "}
<td> {e.scale}</td> <td>{escale.}</td>
</tr>
);
})}
</table>
</div>
)}
</div>
))}
</div>
every accordion opens when I click on any accordions can anyone help with this I want only one to open at a time
This is because the isActive state is a general one to the component and therefore would control all other elements in that component together.
However, to achieve what you're looking for, you can change that to an active current Index like so:
const [activeCurrentIndex, setActiveCurrentIndex] = useState();
const toggleShowAccordion = (id) => {
if(activeCurrentIndex === id){
setActiveCurrentIndex();
} else {
setActiveCurrentIndex(id);
}
}
//....
<div className="accordion">
{predefined.map(({ id, date, time , pain_scale, time_stamp})) => (
<div className="accordion-item" key={id}>
<div className="accordion-title" onClick={() => toggleShowAccordion(id)}>
<div>{date}</div>
<div>{isActive ? "-" : "+"}</div>
</div>
{activeCurrentIndex === id && <div className="accordion-content">{time} {pain_scale} {time_stamp}</div>}
</div>
))}
</div>
You're setting the activeCurrentIndex to that of the accordion you want to expand and you check if the activeCurrentIndex state is same as accordion's id to expand the menu otherwise, it's closed. You also need to supply a key to each element while mapping through an array

Objects are not valid as a React child when setState of an array at the end of a promise chain

I have a promise chain that creates an array. At the end of the chain, I want to copy that array to one of my state variables. However, I get "Objects are not valid as a React child"
I've tried various ways to chain the promise so that the state variable captures the array I want to put in it. If I put setState at the end of my function, it misses what I try to capture in the promise chain
addIPFSItem = () => {
var finalItems = []
var searchAddress = "0x9Cf0dc46F259542A966032c01DD30B8D1c310e05";
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
this.state.web3.eth.getAccounts((error, accounts) => {
simpleStorage.deployed().then((instance) => {
this.simpleStorageInstance = instance
return this.simpleStorageInstance.getLength(searchAddress);
}).then((accountLength) => {
var movieItems = []
var i;
//WITHIN THIS LOOP extend the chain to add to movies
for (i = 0; i < accountLength; i++) {
var p = this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress }).then((hashVal) => {
return hashVal;
})
movieItems.push(p)
}
//return items
return movieItems
}).then((temp) =>{
var i;
var indexCounter=0;
var arrayLength;
arrayLength=temp.length
for(i=0; i<arrayLength; i++){
var p = temp[i].then((temp)=>{
var ipfsPrefix = "https://ipfs.io/ipfs/";
var ipfsURL = ipfsPrefix + temp;
var movieItem = {id: indexCounter, poster_src: ipfsURL, title: "Some Title", overview: "blah blah"}
indexCounter++
return movieItem;
}).then((item)=>{
finalItems.push(item)
}).then(()=>{
if(finalItems.length == arrayLength ){
//*******************************
//Here is where I try to set state and get the error
//*******************************
this.setState({rows: finalItems})
}
})
}
return
})
})
}
I expect my row in my state to change, but I get Objects are not valid as a React child
UPDATE: here is my render() function
render() {
//Shows customer their account
var userAccount = "Your account is: " + this.state.account;
//Shows current IPFS _address
var currIPFS = "The IPFS address is: " + this.state.ipfsHash;
return (
<div className="App">
<table className="titleBar">
<tbody>
<h1>Interactive News</h1>
</tbody>
</table>
<input style={{
fontSize: 14,
display: 'block',
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 14,
width: "99%"
}} onChange={this.searchChangeHandler} placeholder="Enter address for item lookup" />
{this.state.rows}
<main className="container">
<div className="pure-g">
<div className="pure-u-1-1">
<h1>Your Image</h1>
<p>This image is stored on IPFS & The Ethereum Blockchain!!!</p>
<br />
<font size="5">
<span className="badge badge-info" dangerouslySetInnerHTML={{__html: userAccount}} />
<br />
<br />
<span className="badge badge-light" dangerouslySetInnerHTML={{__html: currIPFS}} />
<br />
</font>
<br />
<br />
<button onClick={this.addIPFSItem}
className="btn btn-info btn-sm m-1">ShowList</button>
<br />
<br />
<button onClick={this.handleFirst}
className="btn btn-info btn-sm m-1">First</button>
<button onClick={this.handleDecrement}
className="btn btn-primary btn-sm m-1"> Prev </button>
<font size="5">
<span className="badge badge-success">
{this.state.index}
</span>
</font>
<button onClick={this.handleIncrement}
className="btn btn-primary btn-sm m-1">Next</button>
<button onClick={this.handleLast}
className="btn btn-info btn-sm m-1">Last</button>
<br/>
<img src={`https://ipfs.io/ipfs/${this.state.ipfsHash}`} alt=""/>
<h2>Upload Image</h2>
<form onSubmit={this.onSubmit} >
<input type='file' onChange={this.captureFile} />
<input type='submit' />
</form>
</div>
</div>
</main>
</div>
);
}
I'm not sure if my render() function is okay. Note that when I press the button <button onClick={this.addIPFSItem} className="btn btn-info btn-sm m-1">ShowList</button>, that calls the addIPFSItem() function. I don't know if I need a componentDidMount() as it happens after the initial rendering.
React cannot render objects directly.
You need to return it as react DOM components.
Since 'this.state.rows' is an array of object you need to loop through it and wrap each object inside a meaningful DOM component eg. li or div
..
<ul>
{
this.state.rows.map((row, index) => {
return (
<li key={index}>{row.title}</li>
)
})
}
</ul>
<main className="container">
<div className="pure-g">

React JS - element focus on input not working

I'm having trouble with focusing element programmatically.
I have a ul which looks like this :
<ul className="text-left">
{Object.keys(props.characters).map((char, i) => {
return (
<li key={props.characters[char].key}>
<button type="button" className={"btn align-middle bg_" + props.characters[char].character}>
<div className={"label text-left float-left " + getFontColorFromCharacter(props.characters[char].character)}>
<img alt="" className="char-img" src={"/images/characters/" + props.characters[char].character + "_xs.png"}/>
<input className="align-middle d-none" id={props.characters[char].key + "_input"} type="text" placeholder="Nom joueur" value={props.characters[char].player_name} onChange={e => props.changePlayerNameHandler(props.characters[char],e)} onBlur={e => toggleDNone(props.characters[char].key)} onKeyDown={e => tabToNext(e)}/>
<span className="align-middle" id={props.characters[char].key + "_span"} onClick={e => toggleDNone(props.characters[char].key)} > {props.characters[char].player_name}</span>
</div>
<div className={"actions " + getFontColorFromCharacter(props.characters[char].character)}>
<span className="action">
<FontAwesomeIcon icon="times-circle" title="Supprimer" onClick={() => props.removeCharacterHandler(props.characters[char].key)}/>
</span>
</div>
</button>
</li>
);
})}
</ul>
Javascript :
//Toggle d-none class on input & span for player name edition
function toggleDNone(key) {
document.getElementById(key + "_input").classList.toggle("d-none");
document.getElementById(key + "_span").classList.toggle("d-none");
if (!document.getElementById(key + "_input").classList.contains("d-none")) {
document.getElementById(key + "_input").focus();
}
}
//When the user hit tab key, navigate to next input
function tabToNext(event){
if(event.key === "Tab")
{
var allInput = document.querySelectorAll("[id$='_input']");
var indexOfCurrent = Array.from(allInput).indexOf(event.target);
var id;
if (indexOfCurrent + 1 === Array.from(allInput).length)
{
id = allInput[0].id;
}
else
{
id = allInput[indexOfCurrent + 1].id;
}
toggleDNone(allInput[indexOfCurrent].id.replace("_input", ""));
toggleDNone(id.replace("_input",""));
}
}
When the users click on the span, the input is displayed and the focus is working. When the users hit the tab key to get to the next input, the input is displayed but the focus is not working.
I tried setting the tabIndex to -1 as I saw on a post but it didn't work.
Any ideas?
Found the solution.
The issue was that the second toggleDNone call started before the first one finished.
I just added a setTimeOut of 100ms on the second call and it worked.
Thanks all

How to iterate images on React?

Well, the question is very self-explanatory. I have this code here (inside a render, of course):
const images = [('http://placehold.it/100x100/76BD22'), ('http://placehold.it/100x100/76BD23')];
// ProductPage Views
const ProductPageView =
<section className="page-section ps-product-intro">
<div className="container">
<div className="product-intro">
<div className="product-intro__images">
<div className="product-gallery">
<ul className="product-gallery-thumbs__list">
{images.map(function(image, imageIndex){
return <li key={ imageIndex }>{image}</li>;
})}
</ul>
</div>
</div>
</div>
</div>
</section>
The thing is I don't know how to iterate those images on the array. What's wrong with my code?
Your array is an array of image URLs, not image tags. So your code is close but you need to put the image inside of an image tag inside of your list item tag.
const images = [('http://placehold.it/100x100/76BD22'), ('http://placehold.it/100x100/76BD23')];
// ProductPage Views
const ProductPageView =
<section className="page-section ps-product-intro">
<div className="container">
<div className="product-intro">
<div className="product-intro__images">
<div className="product-gallery">
<ul className="product-gallery-thumbs__list">
{images.map(function(imageSrc) {
return (
<li key={ imgSrc }>
<img src={ imgSrc } />
</li>
);
})}
</ul>
</div>
</div>
</div>
</div>
</section>
I would also recommend against using an array index as a key in general. The imgSrc is unique so it would make a good key here.
Also, make sure to include an alt attribute on the img for screen readers. You might want to make your array like this:
const images = [
{ src: 'http://placehold.it/100x100/76BD22', alt: 'Your description here 1' },
{ src: 'http://placehold.it/100x100/76BD23', alt: 'Your description here 2' }
];
// ...
{images.map(function(imageProps) {
return (
<li key={ imageProps.src }>
<img src={ imageProps.src } alt={ imageProps.alt } />
</li>
);
})}
I assume you want to display them as images, right? You should use img tag then.
<ul className="product-gallery-thumbs__list">
{images.map(function(image, imageIndex){
return <img key={ imageIndex } src={ image } />
})}
</ul>

Filter object array with pipe Angular 2

I have a
class:
export class Todo {
public id: number;
public name: string;
public isCompleted: boolean;
public dateCreated: Date;
public userName: string;
}
A service:
getTodos(): Observable < Todo[] > {
return this.http.get(this.todosUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || {};
}
In my component:
getTodos(){
this.todoService.getTodos()
.subscribe(
todos => this.todos = todos,
error => this.errorMessage = <any>error
);
}
And html file:
<div class="ui large selection animated divided list">
<a *ngFor="let todo of (todos | todoFilter:false)" class="item">
<div class="right floated content">
<div class="ui vertical animated negative button" tabindex="0">
<div class="hidden content">Delete</div>
<div class="visible content">
<i class="fa fa-trash" aria-hidden="true"></i>
</div>
</div>
</div>
<i class="minus square outline icon"></i>
<div class="content">
<div class="header">{{todo.name}}</div>
<div class="description">{{todo.dateCreated | date:"MMM-dd-yyyy"}}</div>
</div>
</a>
</div>
The problem is, when I try to use this pipe to filter the completed todos, I keep getting an error that say Cannot read property filter of undefined.
Did I do something wrong or are there any ways to filter it without using an pipe?
My pipe:
transform(allTodos: Todo[], args?: boolean){
if (allTodos === null) {
return null;
}
return allTodos.filter(todo => todo.isCompleted);
}
Thank you.
Try to replace the if (allTodos === null) to just if (!allTodos)
I think the problem is that you're getting to the .filter even while your this.todos is still empty since you're only checking that it isn't null.

Resources