getting element id in React.js JSX rendering using .map() method - reactjs

I am working on app using react,
const getButtonId=(num)=>{
let click_detect=document.getElementById('div_'+num)
console.log(click_detect)
}
this.state.users.map(i=>{
return(
<button id={'div_'+i.id} onClick={(i.id)=>this.getButtonId(i.id)}>
Click Me
</button>
)
})
So every time I clicked on the button, I kept getting null. Why is that? If I want all buttons to have different id, What should I do here?

Try this:
const getButtonId = (id) => console.log("Button ID:", id);
this.state.users.map((user, index) => (
<button key={index} onClick={() => this.getButtonId(user.id)}>
Click Me
</button>
))

Related

How to write test for a button inside a list tag? Unable to get the button element inside a ui tag?

checkResult is a helper function which is imported in my component.jsx
component.jsx
return(
<ul>
{options.map((option) => {
return (
<li key={option.value}>
<button
data-testid="unlock-btn"
onClick={() => {
checkResult()
? lunch(option.value)
: showError();
}}
>
{option.label}
</button>
</li>
);
})}
</ul>;
)
my test
import * as helper from "../helpers/checkResult";
it("fires action when lunch is clicked", async () => {
const spy = jest.spyOn(helper, 'checkResult');
let component;
await act(async()=>{
component = <component /> ;
})
await expect(screen.queryByTestId("unlock-btn"));
fireEvent.click(screen.queryByTestId("unlock-btn"));
expect(spy).toHaveBeenCalled();
});
this is the error i'm getting
Unable to fire a "click" event - please provide a DOM element.
i have also provided my getComponent Method above
You're not providing options to the component so it has nothing to render. You're also using a map to render a list of items all of which have the same id. You should do something like
map((option, index) => {
return (
<li key={option.value}>
<button
data-testid=`unlock-btn-${index}`
This way you can target each individual option by ID in your test.
Edit: Your fireEvent is not defined in your example either.
The right way would be using the aria-label and attributes to be able to select those buttons without the need of data-testid.
<button
onClick={() => { checkResult() ? lunch(option.value): showError();}}
name={option.label} // assuming the labels are unique
>
{option.label}
</button>
then:
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
it('Should do some test', ()=>{
render(<MyComponent/>)
const button = screen.getByRole('button', {name: "some-label"})
fireEvent.click(button)
expect(....).toBe(...)
}

Call component from onClick function in react js

I want to edit user details so I have
<button onClick={() => this.edit(n.id)}>Edit</button>
edit = (id) => {
<Editbranches/>
}
Here Editbranches is another component.How Can i call Component inside a user function?please Help
If you meant to render the Editbranches then:
<button onClick={() => this.setState({ edit: n.id })}>Edit</button>
{
this.state.edit && <Editbranches/>
}

React onClick() doesn't trigger when inside map function

I'm building my first react application and I've got an issue.
I want to create a simple app where I can click on a book title in a sidebar, and see a summary of the book. I'm getting the book titles and summaries through an API I've defined locally.
Right now, clicking on a book title doesn't do anything. I get an error in red in the console:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I'm calling my API like so:
const [viewingId, setViewingId] = useState()
const [bookList, setBookList] = useState([])
const [contents, setContents] = useState({})
useEffect(() => {
fetch("http://localhost:5000/books")
.then(res => res.json())
.then(res => {setBookList(res)})
}, [])
const fetchBookSummary = (id) => {
fetch(`http://localhost:5000/book/${id}`)
.then(res => res.json())
.then(res => {
setContents({...contents, [id]: res[0]})
})
}
This is how I'm defining my 'onClick' function:
const seeSummary = (id) => {
fetchBookSummary(id)
setViewingId(id)
}
And finally, my App renders like this:
return (
<div className="App">
<div className="App-Container">
<div className="BookList">
{bookList.map(book => (
<Node book={book} onClick={() => seeSummary(book.id)} />
))}
</div>
<div className="SummaryView">
{contents[viewingId]?.content?.map((summaryData => (
<Summary summaryData={summaryData}/>
)))}
</div>
</div>
</div>
);
}
I used to have just onClick={seeSummary(book.id)}, but I got a "too many re-renders" error...I'm not sure I understand what adding () => did.
I'd like to be able to see the Summary when I click on a book. Any help is appreciated!
To resolve the React key warning you just need to provide a valid React key to the elements being mapped.
{bookList.map(book => (
<Node
key={book.id} // <-- Add a unique key, the book id is good
book={book}
onClick={() => seeSummary(book.id)}
/>
))}
When you were just onClick={seeSummary(book.id)} this was invoking the callback immediately, thus causing the render looping. By adding the "() =>" you are declaring an anonymous function to invoke your seeSummary function and pass the specific book id at-the-time the node is clicked.
I don't see any overt issues with the way you've defined your onClick handler other than the fetching book details will take longer than it will take to update the summary id.
const seeSummary = (id) => {
fetchBookSummary(id); // fetching data
setViewingId(id); // will update first.
}
I suspect you haven't connected/attached the onClick prop of the Node component to any DOM element, like a button or div, etc... You need to ensure that the onClick prop is actually attached to something a user can interact with.
Example:
const Node = ({ book, onClick }) => (
<div onClick={onClick}>
{book.title} - Click me to see summary!
</div>
);

React - Browser's Back button doesn't work

In the following component, if I click on any of the two buttons, the URL in the address-bar gets changed.
In the list view if you click on the button Details the page get rendered and shows that particular item and the URL in the address bar get changed too.
And in the user view if you click on the "Back to overview" button, the page renders back to the list view and the URL gets changed again.
import React, { useState, useEffect, Fragment } from 'react'
import axios from 'axios'
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const renderItem = (item, userId) => {
const setURL = (e) => {
window.history.pushState(null, null, '/' + e)
setID(item.login.uuid)
}
const clearURL = (e) => {
window.history.back()
setID(null)
}
return (
<Fragment key={item.login.uuid}>
{userId ? (
// User view
<div>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<p>
{item.phone}
<br />
{item.email}
</p>
<button onClick={() => clearURL('/')}>
Back to overview
</button>
</div>
) : (
// List view
<li>
<img src={item.picture.large} />
<h2>
{item.name.first}
</h2>
<button onClick={() => setURL(item.login.uuid)}>
Details
</button>
</li>
)}
</Fragment>
)
}
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <div>{renderItem(user, true)}</div>
} else {
// List view
return (
<>
<ul>
{resources.map(user => renderItem(user, false))}
</ul>
</>
)
}
}
export default UserList
Everything is working fine.
However, the problem with this solution is that on user view, I cannot use the browsers back button to go back to the list view page.
Is there any way I can change this without using React Route?
So what I believe is happening is you are over-writing the 'history' of the browser. The 'history' is pretty much just a stack of paths you've been and when you click the back button it just pops the current path off the stack. I think that when your using 'window.history.pushState(null, null, '/' + e)' its setting the history = to null so there is nothing to go back to. I would recommend using react-router and just pushing a path ie. router.push('/whatever'). There are a bunch of ways to interact with the history through react without using the native history object. If you want to use the native history object https://developer.mozilla.org/en-US/docs/Web/API/History_API
Edit - I apologize your not overwritting the history, but I do still believe that the error is coming from passing a 'null' value as the 'state' param for the pushState method

How do I idiomatically turn a text component into an input component on click?

I am creating a todo app in React that is basically just a list of items, grouped by category. I want to add functionality such that when I click a single to do(which is a paragraph), it brings up an input with the current text that I can edit and save. How can I do that without manually editing the DOM?
Code:
A single todo item:
import React from 'react';
const Item = props => {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
};
export default Item;
I want to change the toggle to be a radio button and onClick to edit the todo.
sample image of todos
First of all, you will need a prop that updates item.name (this will be needed when you will edit the input)
You didn't explained well how you want it to work, so I made an example where you click on the text to edit it to a text input and also have a button to save the edit.
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
return isEditing ? (
<div>
<input
value={props.item.name}
onChange={e => props.setItemName(e.target.value)}
/>
<button onClick={() => setIsEditing(false)}>Stop Editing</button>
</div>
) : (
<div
className={`item${props.item.purchased ? " purchased" : ""}`}
onClick={() => setIsEditing(true)}
>
<p>{props.item.name}</p>
</div>
);
};
I also created a working codesanbox with the behavior you want.
You will have to maintain a state to change between the TODO Item & TODO Input. Since you are using functional component, you can use useState hook from react to maintain the state as shown below
import React, { useState } from 'react';
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
if (isEditing) {
return (
<div className={`item${props.item.purchased ? ' purchased' : ''}`}>
<input type="text/radio" value={props.item.name}>
</div>
);
} else {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
}
};
export default Item;
You might need to change the above a bit based on your application structure but this is what you need to follow.

Resources