I have a button and for testing purposes, I want to write to the console the index of an array element. More specifically, I have a button in button.js, and that button is displayed on each array element in the IncomeOutputList array. When clicked on, I want each button to print to the console the index of the corresponding IncomeOutputList array element.
For example, by clicking on the button of the second element shown in the image below, I want the console to display index 1 (the first element is the topmost rectangle, which is a blank array element).
Here is a picture of an array element with the button, the button appears while hovering above the number for each array element:
Currently when the page renders, all of the indices of the array are displayed in console, not sure why.
I hope I made my question clear!
button.js:
import React from 'react';
const Button = ({buttonType, handler}) => (
<>
<div className="item__delete">
<button className={buttonType} onClick={handler}>
<i className="ion-ios-close-outline"></i>
</button>
</div>
</>
)
export default Button;
ValueOutput.js:
import React from 'react';
import Button from '../buttons/Button';
//move item__value element to left when hovering over it, and make delete button appear
const ValueOutput = ({type, value, handleClick}) => {
return (
<>
<div className="right clearfix">
<div className="item__value">{type} {value}</div>
<Button buttonType="item__delete--btn" handler={handleClick}/>
</div>
</>
)
}
export default ValueOutput;
IncomeOutput.js:
import React from 'react';
import ValueOutput from './ValueOutput';
const IncomeOutput = ({ desc, type,id, value, handleButton }) => {
//id = inc-{id}
return (
<>
<div className="item clearfix income" id={id}>
<div className="item__description">{desc}</div>
<ValueOutput
type={type}
value={value}
handleClick={handleButton}
/>
</div>
</>
)
}
export default IncomeOutput;
IncomeOutputList.js:
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list }) => {
const handler = (i) => {
console.log(i);
console.log('the test');
}
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index) => <IncomeOutput
id={item.id}
value={item.incomeValue}
type={item.budgetType}
desc={item.desc}
handleButton={handler(index)}
/>
)}
</div>
)
}
You are passing handler(index) as your event handler. Since that doesn't return anything you are effectively passing undefined as your handler. You will want to change your handler method to return a function:
const handler = (i) => {
return () => {
console.log(i);
console.log('the test');
};
};
You could also just wrap your call to handler in a function, buttonHandle={() => handler(index)} - This is effectively the same thing.
The problem is that the handler function is executed right away when the code is encountered.
Whenever you have () the function will execute right away when encountered. It is not waiting for the event to fire.
Here is what you can do:
handleButton={() => handler(index)}
Related
I have a button named yes in the child component where I delete a list item from array and local storage using the props.id passed from the parent component.
The problem is the item is deleted from array and local storage but is still visible on the screen until I press delete button on another item in the parent component.
when I press delete button in the parent component it opens an overlay. When I press yes button on overlay I want list item to be removed from the screen immediately.
here is the code in the child component.
import React, { useCallback, useState } from "react";
import styles from "./Delete.module.css";
function Delete(props) {
// console.log();
const store = props.store;
const [no, setNo] = useState(false);
let [deleted, setDelted] = useState(store);
console.log(deleted);
console.log("Length :" + store.length);
const noBtnHandler = () => {
console.log("clicked");
setNo(!no);
props.setDel(false);
};
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([...filteredStore]);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
return (
<div className={`${no === false ? styles.del : styles.delOn}`}>
<section>
<div>
<h3>Are you Sure ?</h3>
</div>
<div>
<button type="button" onClick={yesBtnHandler}>
{" "}
Yes{" "}
</button>
<button type="button" onClick={noBtnHandler}>
{" "}
No{" "}
</button>
</div>
</section>
</div>
);
}
export default Delete;
You are passing store from the parent component to the Delete Component and setting a new state here 'deleted'. so you are only calling the setDeleted on the Delete component which wont affect the parent component.
The correct implementation is to have the store state in the parent component if you don't already have it. It is will still be same like deleted state but possibly with a better name. Say const [store, setStore] = useState([])
Define a function to filter out a particular record just like you have in the yesBtnHandler handler. but this function will be defined in the parent component. Say as an example
const removeRecord = (id) => {
const filteredStore = store.filter((task) => task.id !== id);
localStorage.removeItem(id);
setStore(filteredStore);
}
You now need to pass the a function to the Delete Component from the parent rather than passing the whole store. Like
<Delete removeFunc= {() => { removeRecord(id) }} />
After passing this function, you need to call it in your yesBtnHandler function. Like
function Delete({removeFunc}) {
...
const yesBtnHandler = () => {
removeFunc();
setNo(!no);
};
}
Try remove the trailing ...
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([filteredStore]);
//or setDelted(filteredStore);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
my understanding of this is that you're trying to change the state of a parent component from a child component. If that's what you're intending to do then you can do the following:
Define the function Delete(id) {...} inside the parent component rather than the child component.
Next, you'll have to pass both the function and the array to your child component, something like this: <ChildComponent array={array} onDelete={Delete}, where array is the array in your parent component and Delete is the function to delete an item from the array.
Finally, in your child component, with the props passed in correctly, i.e, function ChildComponent({array, Delete}) {...}you can now have access to the array in your parent component, and actually modify it like you'd like. To fire the event listener on the yes button in the child component, do this: <button type="button" onClick={() => Delete(array.id)}> {" "} Yes{" "} </button>
I hope this will be helpful
Imagine I have a page "Parent" which conditionally renders a div "Child".
On the click of a button, "Child" opens. To close "Child" one has to click in a X button inside it.
This is how I would do it and in my opinion it looks clean.
const Parent = (props) => {
const [childVisible, setChildVisible] = useState(false);
return (
<>
{childVisible && <Child close={setChildVisible.bind(false)} />}
<button onClick={setChildVisible.bind(true)}>
Open Child
</button>
</>
)
}
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close()}> X </button>
</div>
)
}
Since react v16.13.0 react has introduced a warning Warning: Cannot update a component from inside the function body of a different component. and it seems I can't do this anymore.
What's the correct pattern now? I would rather not have a state in both components stating the same thing.
Call back was not properly added .You could do like this onClick={props.close}
While use onClick={props.close()} like this. close() function run on child mount instead of click event
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close}> X </button>
</div>
)
}
I am trying to show or hide a div in Reactjs using the state value in the CSS style option - display and I am using functions with hooks. I have a button and below the button a div. When i click the button i either want to hide or show the contents in the div based on whether it is currently shown or hidden.
This is the basic test code I have
import React, { useState } from "react";
function hide() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState("none");
return (
<form>
<button
onClick={() => {
setDp("block");
}}
>
Test
</button>
<div style={{ display: dp }}>Test</div>
</form>
);
}
export default hide;
I then use this hide component in my App.js file. When I click the button the new state is assigned but then the page re-renders and the initial state is loaded again almost immediately. How can I go by ensuring the new state is kept? Eventually I will create a function where if the div display or not based on the previous state.
The issue is that the button is inside a <form>. So any click on that button will submit the form and refresh the page.
Can I make a <button> not submit a form?
You need to add a type="button" to your <button>
import React, { useState } from "react";
function Hide() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState(false);
return (
<form>
<button
type="button"
onClick={() => setDp(!dp)}
>
Test
</button>
{dp && <div>Test</div>}
</form>
);
}
export default Hide;
Your code should be something like this, instead of using block and none as style we can use conditional JSX (which is more ideal approach) -:
function Mycomp(){
const[dp, toggleDp] = useState(false);
return(
<form>
<button onClick={()=>{toggleDp(!dp)}}>Test</button>
{dp && <div>Test</div>}
</form>
)
}
export default hide
A better implementation would be to have your state variable TRUE/FALSE value and based on it display the element using a conditional rendering, note e.preventDefault in the button handler to stop the refresh/redirect, here is a working snippet, also a codesandbox:
const { useState, useEffect } = React;
function App() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState(true);
return (
<form>
<button
onClick={(e) => {
e.preventDefault();
setDp(!dp);
}}
>
Test
</button>
{dp && <div>Test</div>}
</form>
);
}
ReactDOM.render(<App />, document.getElementById("react-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="react-root"></div>
I am really wondering why getElementById is returing null, where as same element I am able to access using get elements by className. It will be great if somebody helps me to understand what is causing this.
Please find below code, its a functional react component where i am passing handleClose function as props, which closes the modal, it just not needed so i have put only Modal component code.
import React, { useRef, useEffect } from 'react'
const A11yModal = ({ handleClose }) => {
const focusClose = useRef(null)
var closeBtn_cls = document.getElementsByClassName("btn-close")
var closeIcon=document.getElementById('btnclose')
useEffect(() => {
focusClose.current.focus();
console.log("using Id",closeIcon);
console.log("using ClassName",closeBtn_cls);
}, [])
function onKeyPressed(e) {
if (e.keyCode === 27) {
handleClose()
}
}
return (
<div className="modal display-block">
<section className="modal-main" role="dialog"
aria-modal="true" onKeyDown={(e) => onKeyPressed(e)}>
<button className="btn-close" id="btnclose" onClick={(e) => handleClose(e)} ref={focusClose}>
X
</button>
<h1 id="modal_title" className="title" >Modal</h1>
<div id="full_description" className="description" aria-describedby="full_description">
<p>Description goes here.</p>
</div>
<button className="close_btn" id="closebtn" onClick={(e) => handleClose(e)}> Close </button>
</section>
</div>
)
}
export default A11yModal
var closeBtn_cls = document.getElementsByClassName("btn-close")
At the moment this line of code finishes running, closeBtn_cls will have nothing in it (assuming this is the first render and there is nothing else with that class name on the page). But closeBtn_cls is a live HTMLCollection. This array-like object has the peculiar property that it will be dynamically changed as elements are added to the DOM. So by the time the useEffect runs, the element has been added to the page and the collection updated.
getElementById does not return an HTMLCollection, so it does not update on the fly.
While that addresses the difference, you should also know that this is not the recommended way to do things in react. In react you should use refs to get a reference to the dom element. You seem to be aware of that, as you're used refs in your example, so i recommend just deleting the code that uses getElementsByClassName and getElementById.
Ok, so the reason that you see elements while using className is that on console the elements are evaluated when you expand objects which in this case is an HTMLCollection, so even though at the initial render there is no element present, after the execution of useEffect you will display the data into the console and by that the HTMLCollection is being initialized and the reference to the array is causes the values to be seen whereas while using the id you are directly returning a single element and there is no element present at the time of initial render.
const { useRef, useEffect } = React;
const A11yModal = ({ handleClose }) => {
const focusClose = useRef(null)
var closeBtn_cls = document.getElementsByClassName("btn-close")
var closeIcon=document.getElementById('btnclose')
console.log('class value', closeBtn_cls[0]);
useEffect(() => {
focusClose.current.focus();
console.log("using Id",closeIcon);
console.log("using ClassName",closeBtn_cls);
}, [])
function onKeyPressed(e) {
if (e.keyCode === 27) {
handleClose()
}
}
return (
<div className="modal display-block">
<section className="modal-main" role="dialog"
aria-modal="true" onKeyDown={(e) => onKeyPressed(e)}>
<button className="btn-close" id="btnclose" onClick={(e) => handleClose(e)} ref={focusClose}>
X
</button>
<h1 id="modal_title" className="title" >Modal</h1>
<div id="full_description" className="description" aria-describedby="full_description">
<p>Description goes here.</p>
</div>
<button className="close_btn" id="closebtn" onClick={(e) => handleClose(e)}> Close </button>
</section>
</div>
)
}
ReactDOM.render(<A11yModal />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />
I'm making a small application in React with the PokeAPI and am having issues with using the splice() method to remove an element (pokemon) from the array (team). No matter which element I pick to remove it only removes the first element in the array.
This is the function -- which is being passed down through props -- I'm using in order to delete the item.
removePokemon = (index) => {
const team = [...this.state.team]
team.splice(index, 1)
this.setState({team})
}
And here is the Team component where it's actually being used.
import React, { Component } from 'react';
import Button from 'react-bootstrap/Button'
class Team extends Component {
render() {
return (
<div>
<h2>{this.props.trainer && <p>{this.props.trainer}'s Team</p>}</h2>
{this.props.team &&
<div>
{this.props.team.map((pokemon, i) => (
<div key={pokemon.id}>
<span className='cardHeader'>#{pokemon.id} - {pokemon.name}</span>
<img src={pokemon.sprites.front_default} alt={pokemon.name}/>
<Button onClick={this.props.removePokemon}>Remove from team</Button>
</div>
))}
</div>
}
</div>
);
}
}
export default Team;
You don't pass an argument index to your function removePokemon:
You need to edit one row:
<Button onClick={() => this.props.removePokemon(i)}>Remove from team</Button>
Since you have not passed index as argument to onClick={this.props.removePokemon}.
index inside removePokemon method refers the event object. So the code
team.splice(index, 1) evaluates to team.splice(eventObject, 1).
That's why splice is removing first element of the array.
You can change to onClick={() => this.props.removePokemon(i)}to remove the element you want.