I want to get all elements with the classname selected from this component
function ChooseElements() {
const listItems = elementObjects.map((object) =>
<ListItem key={object.id.toString()} value={object.Element} />
);
return (
<div> <ul>
{listItems}
</ul>
<button onClick={ console.log("get all list items")}>get Elements</button>
</div>
);
}
in plain js i could use document.getElementsByClassName('selected') to get all elements
I have read somewhere that useRef should be used
to access the elements in the virtual dom, is this correct and how can I do this ?
i think you can use document.querySelectorAll('.list-item') but you need to assign a classname first. example my class is list-item so you can get the DOM elements.
You should probably see it from a different perspective. You can lift your state one level up to your <ChooseElements /> component.
This is the practice encouraged by react for similar problems. here
function ChooseElements() {
const [selectedItems, setSelectedItems] = useState([]);
const handleItemSelect = item => {
setSelectedItems([...selectedItems, item]);
}
const handleItemUnselect = item => {
setSelectedItems(selectedItems.filter(i => i !== item));
}
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
onSelect={() => handleItemSelect(object)}
onUnselect={() => handleItemUnselect(object)}
/>
);
return (
<div>
<ul>
{listItems}
</ul>
<button onClick={() => console.log(selectedItems)}>get Elements</button>
</div>
);
}
However, if ONLY AND ONLY lifting state up is not possible to you, you can use ref like this:
function ChooseElements() {
const myRef = useRef();
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
/>
);
return (
<div>
<ul ref={myRef}>
{listItems}
</ul>
<button onClick={() => {
console.log(ref.current.querySelector('.selected'));
}}>
get Elements
</button>
</div>
);
}
Related
I have a question - I have a list that is in a separate popup with a limited height. After this height, a side scroll appears. I need to scroll to a specific element automatically when that component is rendered. How to implement it? I just can't figure out how to scroll to a certain element.
Below is an example of my jsx code
<ul className={style.list}>
{itemsForRender?.length ? (
itemsForRender.map((item) => (
<li className={style.item} key={item.id}>
<button
type="button"
className={
activeItem === item.id
? `${style.button} ${style.activeClass}`
: style.button
}
onClick={() => selectItem(item.id)}
>
{item.name}
</button>
</li>
))
) : (
<p className={style.searchSubtitle}>
Just text
</p>
)}
</ul>
Use this code :
const ScrollDemo = () => {
const myRef = useRef(null)
const executeScroll = () => myRef.current.scrollIntoView()
// run this function from an event handler or an effect to execute scroll
return (
<>
<div ref={myRef}>Element to scroll to</div>
<button onClick={executeScroll}> Click to scroll </button>
</>
)
}
I had used Element.scrollIntoView() Method in React with making a reference of element i want to scroll to, here is the example:
function TestComponent() {
const testRef = useRef(null);
const scrollToElement = () => testRef.current.scrollIntoView();
// Once the scrollToElement function is run, the scroll will show the element
return (
<>
<div ref={testRef}>Element you want to view</div>
<button onClick={scrollToElement}>Trigger the scroll</button>
</>
);
}
you can use scrollIntoView() where you want scroll automatically
document.getElementById(element-id).scrollIntoView({behavior: 'smooth'})
you can use this in a useEffect() to run it when component is rendered
I hope this would be helpful. thanks
const scrollRef = useRef([]);
useEffect(() => {
// here you call the function scrollToSection and pass the id where you want to scroll
}, [])
const scrollToSection = id => {
if (scrollRef.current.length) {
scrollRef.current[id].scrollIntoView();
}
};
<ul className={style.list}>
{itemsForRender?.length ? (
itemsForRender.map((item) => (
<li className={style.item} ref={ref => (scrollRef.current[item.id] = ref)} key={item.id}>
<button
type="button"
className={
activeItem === item.id
? `${style.button} ${style.activeClass}`
: style.button
}
onClick={() => selectItem(item.id)}
>
{item.name}
</button>
</li>
))
) : (
<p className={style.searchSubtitle}>
Just text
</p>
)}
</ul>
am trying to delete am item by id but i keep get error, that each child should have a unique key after giving a it an id, what am i doing wrongly, why am i not getting the id
const TodoList = () => {
const [input, setInput] = useState("");
const [todos, setTodos] = useState([])
const handleSubmit = e => {
e.preventDefault()
setTodos([...todos, input])
setInput("")
}
const handleDelete = id => {
let item = todos.filter(todo => todo.id !== id)
console.log(item)
// setTodos(item)
}
return (
<div className='todolist'>
<h2>Todo List</h2>
<form>
<input value={input} onChange={e => setInput(e.target.value)} placeholder='write something' />
<button onClick={handleSubmit}>Add todo</button>
</form>
{todos.map(todo => (
<div key={todo.id} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(todo.id)} />
</div>
))}
</div>
);
};
export default TodoList;
From the above code, it looks like todos is an array of strings. So when you are assigning key by doing todo.id, you are assigning the key to be undefined since the id property does not exist in the string type. Change you map method like this
{todos.map((todo, i) => (
<div key={i} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(i)} />
</div>
))}
and then change on your handleDelete like
const handleDelete = id => {
const newTodos = [...todos];
newTodos.splice(id, 1);
console.log(newTodos)
setTodos(newTodos)
}
{todos.map((todo, i) => {
<div key={i} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(i)} />
</div>
})}
You can check how list and keys work in react in here
https://reactjs.org/docs/lists-and-keys.html
You have not given id to any of your todo , Pass the index of that todo instead of id , it will solve your problem
Try this sandbox code link
I hope you find this helpful.
As other users (especially TheWhiteFang) pointed out above, your todos list is an array, and your single todo item inside this array is string which you get from your input.
Alternatively, you could set your single todo item as an object instead of plain string, such as {id: 1, content: input}, for example, change the line of setTodos to:
setTodos([...todos, { id: count, content: input }]);
In this way, you could then access the id of every single todo item and access the content of the todo item via todo.content, when using a map function.
To illustrate this, you may refer to this code snippet:
https://codesandbox.io/s/todo-list-in-5min-2tomwb?file=/src/App.js
looking for advice on how to have one button that when clicked will switch the list of items to the alphabetized list of items and back when clicked again and so on. as of right now when i click the button it will show the alphabetized list but its just rendering on top of the original list already showing. not really sure on where to go from here
class MenuItems extends Component {
state = {
sortedItems: []
}
handleclick = (item) => {
this.props.deleteMenuItem(item.id);
}
menuSort = () => {
const ogList = [...this.props.menuItems]
let sortedList = ogList.sort((a, b) => a.name.localeCompare(b.name));
this.setState({sortedItems: sortedList})
};
render(){
return (
<div>
<button onClick={this.menuSort}>filter a to z</button>
{this.state.sortedItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
{this.props.menuItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
</div>
)
}
}
export default connect(null, {deleteMenuItem})(MenuItems)```
You correctly thought to keep the sorted version in the state. But you have to somehow instruct the component to render the original list or the sorted one.
So you could add a flag in the state to specify which one you want.
You could also set the sorted list in the componentDidMount lifecycle event, and updated it whenever the menuItems change through the componentDidUpdate lifecycle event.
So something like
state = {
sortedItems: [],
showSorted: false
};
toggleSort = () => {
this.setState(({ showSorted }) => ({
showSorted: !showSorted
}));
};
updateSortedItems() {
const sorted = [...this.props.menuItems].sort((a, b) =>
a.name.localeCompare(b.name)
);
this.setState({
sortedItems: sorted
});
}
componentDidMount() {
this.updateSortedItems();
}
componentDidUpdate(prevProps) {
if (this.props.menuItems !== prevProps.menuItems) {
this.updateSortedItems();
}
}
and in your render method
let itemsToShow = this.props.menuItems;
if (this.state.showSorted) {
itemsToShow = this.state.sortedItems;
}
and use the itemsToShow when you want to display them
{itemsToShow.map((item) => (
Full working example at https://codesandbox.io/s/elated-heyrovsky-m3jvv
I am trying to pass item to onClick handler but it turns out that item is an object but it's just a string.
Here's my code:
const UploadMain = () => {
return (
<Fragment>
<div className="part">
<h2 className="part-h2">category: </h2>
<Options
items={["one", "two", "three", "four"]}
name={useSelector(state => state.upload.category)}
/>
</div>
</Fragment>
);
};
const Options = props => {
let [status, setStatus] = useState(false);
return (
<div className="options-container">
<div className="options-header" onClick={() => setStatus(!status)}>
<p>{props.name}</p>
</div>
<ul className={status ? "options-ul shown-ul" : "options-ul hidden-ul"}>
{props.items.map((item, index) => {
return (
<li
key={index}
value={item}
onClick={item => {
console.log(item) //this line logs some object
}}
>
{item}
</li>
);
})}
</ul>
</div>
);
};
Object logged:
That is not a item but it is an event object. That item does not refer to item of items but it behaves like an alias to the event object.
instead change to this and put the log as is:
onClick={e => {
You can try this code.
{props.items.map((item, index) => {
return (
<li
key={index}
value={item}
onClick={e => {
console.log(item) //this line logs some object
}}
>
{item}
</li>
);
})}
All you need to do is to change the item to e(event object differenct from item)
{props.items.map((item, index) => {
return (
<li
key={index}
value={item}
onClick={item => {
console.log(item) //this line logs some object
}}
>
{item}
</li>
);
})}
If you take a closer look there, you first declare the item variable in the upper scope (declared in the map method), and then you declare another item variable in the lower scope, in the onClick handler.
To fix your issue - don't use the same variable name in the scope twice, it's bug and error prone. A linter would catch that, setup one if you don't have one already.
The variable in the onClick handler with always be the Event.
If you want to send the item variable to the onClick use:
<li
onClick={(ev) => console.log(item)}
>
{item}
</li>
Here I don't redeclare item, so it works as you want it.
Don't worry about the fact that this isn't perfectly optimised for now - you could use the useCallback hook to prevent that function from being created on every render, but it should not matter in small examples.
Pass like this onClick={()=> handleClick(item)} and define function
const handleClick = item =>{
console.log(item);
}
Improved Code
const Options = props => {
const handleClick = item =>{
console.log(item);
}
let [status, setStatus] = useState(false);
return (
<div className="options-container">
<div className="options-header" onClick={() => setStatus(!status)}>
<p>{props.name}</p>
</div>
<ul className={status ? "options-ul shown-ul" : "options-ul hidden-ul"}>
{props.items.map((item, index) => {
return (
<li
key={index}
value={item}
onClick={()=> handleClick(item)}
>
{item}
</li>
);
})}
</ul>
</div>
);
};
I want to listen for click events on an arbitrary number of elements and inside the click event handler, I want to retrieve some info about the clicked element (info which was easily accessible during element creation).
A common solution to this problem is this:
render() {
return (
<div>
{this.props.users.map(user => (
<button onClick={() => this.buttonClicked(user.email)}>
{user.name}
</button>
))}
</div>
);
}
The flaw I see in this approach is that we're creating a new function for every element. Is that a problem worth solving? If it is, how do you feel about this solution:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked}>
{user.name}
</button>
))}
</div>
);
}
buttonClicked(event) {
const { index } = event.currentTarget.dataset;
const { email } = this.props.users[index];
// ...
}
Create another component and dispatch the event from it.
class Button extends React.PureComponent{
handleOnClick = ()=>{
const {onClick, ...rest} = this.props
if(onClick typeof ==='function'){
onClick(rest)
}
}
render(){
const {name} = this.props
return (
<button onClick={this.handleOnClick}>
{name}
</button>)
}
}
...
render() {
return (
<div>
{this.props.users.map(user => (
<Button {...user} key={user.email} onClick={this.buttonClicked} />
))}
</div>
);
}
I believe it is better to use a defined function instead of anonymous/inline functions, otherwise the onClick handler only gets created during the render stage.
In your second example, you can actually bind the argument to the handler:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked.bind(this, user.email)}>
{user.name}
</button>
))}
</div>
);
}
Secondly, wanted to point out that there is no performance issue with having many handlers. React uses one event handler at the root of your document.