React JS - element focus on input not working - reactjs

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

Related

How to update a react useState while rendering the state?

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.

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

Disable all checkboxes except "checked" react hooks

I have listing of products and user can compare upto 4 products, when user checked 4 products I want to disable all checkboxes so user cannot select other product for compare until unless it uncheck one of 4 checkboxes.
const [checkedddItems, setCheckedItems] = useState({checkedItems : {}})
const handleComparePackage = (e, packageId) => {
const { id, checked } = e.target;
const updatedCheckedItems = comparedPackages.includes(packageId)? { [id]: checked } : {checkedddItems, [id] : checked }
console.log(updatedCheckedItems);
setCheckedItems({checkedItems: updatedCheckedItems})
}
{ insurancePackages.map((insPackage) => {
return (
<div className="col-lg-3 col-md-4 col-sm-6" key={insPackage.id}>
<div className="insurance-card active">
{compareSwitch &&
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
id={insPackage.id}
checked={checkedddItems[insPackage.id]}
disabled={!checkedddItems[insPackage.id]}
onChange={(e) => { handleComparePackage(e, insPackage.id) }} />
</div>
}
<div className="thumb">
<img src="/insurance/logo.svg" alt="logo" />
</div>
<div className="title">
{insPackage.company.name}
</div>
<div className="text-detail">
{insPackage.description}
<br />
<Link href="/">
<a>View Package Details</a>
</Link>
</div>
</div>
)
})
}
From what I can tell, it seems like you need some way to disable all checkboxes when the following conditions are met:
the checkbox is not checked,
the amount of total checked items > 3
This should simply turn into a simple boolean statement in the disabled attribute on the checkbox <input/>.
<input
className="form-check-input"
type="checkbox"
id={insPackage.id}
checked={checkedddItems[insPackage.id]}
disabled={!checkedddItems[insPackage.id] && checkedddItems.length > 3} // right here
onChange={(e) => { handleComparePackage(e, insPackage.id) }} />

Linting in React

I am learning React.js. I am developing an app. My code is like below
<div className="ui pagination menu">
<span
className={
this.props.page === 1
? 'disabled item pagination'
: 'item pagination'
}
onClick={() => {
if (this.props.page === 1) {
return false;
}
this.pagination(this.props.page - 1);
}}
>
❮
</span>
<div className="item">
Page {this.props.page} of {this.props.maxPages}
</div>
<span
className={
this.props.page === this.props.maxPages
? 'disabled item pagination'
: 'item pagination'
}
onClick={() => {
if (this.props.page === this.props.maxPages) {
return false;
}
this.pagination(this.props.page+1);
<h1 className="ui attached warning message table"> // Line 185
<span id="address">Addresses</span>
<span id="user_details">
Welcome, <b> { this.state.userName } </b> |
<span id="logout" onClick={this.logout}> Logout </span>
<button className="ui teal button" onClick={this.openPopup}> <i className="plus square icon" />
Add Address
</button>
</span>
</h1>
{this.props.addresses.length > 0 ? (
I am getting Warning like below
Line 185: Expected an assignment or function call and instead saw an expression no-unused-expressions
Could anyone say how can I solve the Warning ?
I think you have missed closing of onClick function before your line 185, you should do this,
<span
className={
this.props.page === this.props.maxPages
? 'disabled item pagination'
: 'item pagination'
}
onClick={() => {
if (this.props.page === this.props.maxPages) {
return false;
}
this.pagination(this.props.page+1);
}} //This is missing
> //closing of span is also missing
<h1 className="ui attached warning message table"> // line 185
I believe you are missing the closing statement for the onClick. It may be helpful to use and IDE and install prettier. This will help you see where you are missing syntax. Additionally, here is more information on the rational for the error:
http://linterrors.com/js/expected-an-assignment-or-function-call

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">

Resources