how to use onClick handler in mapping item? (React) - reactjs

I'm student studying of react.
While working on the project, I had a question.
{pages.map((page, idx) => (
<li key={page.page_id} id={`${idx + 1}`} css={CSSCarouselItem}>
<Photo onClick={() => console.log('click!')}/>
<p css={CSSPageContent}>{page.text}</p>
........
</li>
))}
and Photo components
interface PhotoProps {
.....
onClick?: () => void;
}
const Photo = ({ src, width, height, blur, custom, onClick, text, placeholder }: PhotoProps) => {
return (
<div
onClick={onClick}>
...
</div>
I have to apply onClick handler only to Photo components.
However, onClick applies only to the highest element(li components) that is mapped.
photo's onClick doesn't work.
Is this inevitable?
Thank you for reading it.

No, you can apply the onClick handler to the specific component within the mapped element. To do that, you can simply pass the onClick handler to the Photo component inside the li element:
{pages.map((page, idx) => (
<li key={page.page_id} id={`${idx + 1}`} css={CSSCarouselItem}>
<Photo onClick={() => { /* your onClick handler here */ }} />
<p css={CSSPageContent}>{page.text}</p>
........
</li>
))}
This way, the onClick handler will be passed to it, and inside component you can apply this on it's parent component. And it can be triggered when the Photo component is clicked.

Add onClick func as prop in Photo component.
{pages.map((page, idx) => (
<li key={page.page_id} id={`${idx + 1}`} css={CSSCarouselItem}>
<Photo onClick=(() => {/* your click func */})/>
<p css={CSSPageContent}>{page.text}</p>
........
</li>
))}
And add the attribute for your img (or what you have there)
function Photo({onClick}) {
<img onClick={onClick}/>
}

Related

scroll to div using useRef in map method

i am using useRef to scroll to specific div but it is not working in map method's case (probably because of id) , so can anyone tell me how to provide id. it is taking last element in map method right now.
this is element to which i want to scroll to.
{allMessages?.map((message) => (
<div
key={message.data.id}
ref={filterRef}>
<div>
<p>{message.data.text}</p>
</div>
</div>
))}
this is filtered data in which i am getting filtered messages and clicks on specific div.
{filteredMsg?.map((item) => (
<li
onClick={() => goToFilterData(item.data.id)}
key={item.data.id}
>
{item.data.text}
</li>
))}
this is what i have done with useRef yet -
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);
const goToFilterData = (id) => {
scrollToRef(filterRef);
};
Just pass an id to every element that maybe should scroll into the view.
{allMessages?.map((message) => (
// the element you want to scroll to
<div id={`message_${message.someUniqueIdentifier}`} />
))}
Pass the identifier to the scroll function.
{filteredMsg?.map((item) => (
<li onClick={() => goToFilterData(item.data.someUniqueIdentifier)}>
{item.data.text}
</li>
))}
Query the element and make it scroll into the view.
const goToFilterData = (uniqueIdentifier) => {
const element = document.getElementById('message_' + uniqueIdentifier);
element.scrollIntoView()
};
Note: ofc you could handle this with a lot of single refs and pass them, but this should just work fine.

How to fix this error in react `onClick` listener to be a function, instead got a value of `string` type

I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}

Render JSX from material-ui Button onClick

I'm trying to render JSX when a material-ui button is clicked. I'm logging to the console when clicking but cannot see any of the JSX getting rendered.
interface TileProps {
address?: string;
}
const renderDisplayer = (address: string) => {
console.log('Rendering address', address!);
if (typeof(address) == 'undefined' || address == '') {
return(<div className='error'><li>No address found</li></div>)
}
return(<AddressDisplayer address={address} />)
}
const Tile = (props: TileProps) => {
return(
<div className='tile'>
<ul>
<li>{props.address}</li>
</ul>
<Button variant='contained' onClick={() => {renderDisplayer(props.address)}}>Display</Button>
</div>
)
}
export default Tile;
I can see the console.log('Rendering address', address!); running when the button is clicked, but the JSX isn't getting rendered.
Could this be because I'm using React functional components instead of class components?
Your question is somehow unclear for me. If you want to render <div className='error'><li>No address found</li></div> based on typeof(address) == 'undefined' || address == '' condition, there is no need to click on the button and it's better to use conditional rendering. For example:
{!props.address ? (
<div className='error'><li>No address found</li></div>
) : (
<AddressDisplayer address={props.address} />
)}
But if you want to render your address component by clicking on the button, you should define a state and set it true when clicking on the button. Like this:
const [shouldShowAddress, setShouldShowAddress] = useState(false);
{shouldShowAddress && (
<>
{!props.address ? (
<div className="error">
<li>No address found</li>
</div>
) : (
<AddressDisplayer address={props.address} />
)}
</>
)}
<Button
variant="contained"
onClick={() => {
setShouldShowAddress(true)
}}
>
Display
</Button>
Please read about the Life cycle This not how react work onclick function
renderDisplayer is called and return JSX to onClick event you need to use state here to render the component with ternary oprator renderDisplayer fuction do setState so DOM will update

state is not updating when the component re renders?

I'm making a Nextjs flashcard app. I'm passing a deck structure like this:
const deck = {
title: 'React 101',
flashcards: [flashcardOne, flashcardTwo],
};
as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards.
The Card component is very simple and shows the front and the back of the card depending of the state front.
This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?
export default function Deck({ deck }) {
const [cardIndex, setCardIndex] = useState(0);
const { title, flashcards } = deck;
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>{title}</h1>
{cardIndex < flashcards.length ? (
<>
<div className={styles.grid}>
<Card flashcard={flashcards[cardIndex]} />
</div>
<button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
Next
</button>
</>
) : (
<>
<div>End</div>
<button>
<Link href='/'>
<a>Go to Home</a>
</Link>
</button>
<button onClick={() => setCardIndex(0)}>Play again</button>
</>
)}
</main>
</div>
);
}
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
return (
<>
{front ? (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(false)}
>
<p className={styles.front}>{flashcard.front}</p>
</div>
) : (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(true)}
>
<p className={styles.back}>{flashcard.back}</p>
</div>
)}
</>
);
}
When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.
Call setFront(true) when the flashcard prop has changed:
const [front, setFront] = useState(true);
useLayoutEffect(() => {
setFront(true);
}, [flashcard]);
I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).
You can also significantly slim down the Card JSX:
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
const face = front ? 'front' : 'back';
return (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(!front)}
>
<p className={styles[face]}>{flashcard[face]}</p>
</div>
);
}
Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).
Here's the code I used in my App.
useEffect(() => {
setFront(true);
}, [flashcard]);
This is what I have used in my Word.js file.

Button in li element to display more details

I would like to display the modal window with more details when I click on record. I'm using OfficeUI.
My parent component:
public render() {
{
return (
<div>
{this.props.items
.map((item: IListItem, i: number): JSX.Element => <ul className="list-group">
<li className="list-group-item">
<div className={styles.text}>
<p>{item.Id}</p>
</div>
<DefaultButton onClick={this.showModalEvent} text="Open Modal" />
{this.state.showPopup
? <ModalPopUpItem item={item}
showModalState={this.state.showPopup}
showModal={this.showModalEvent}/> : null
}
</li>
</ul>)}
</div>
);
}
private showModalEvent = (): void => {
this.setState({ showPopup: !this.state.showPopup });
}
My child:
export class ModalPopUpItem extends React.Component<IModalProps> {
public render() {
return (
<Modal
isOpen={this.props.showModalState}
onDismiss={this.props.showModal}
isBlocking={false}
containerClassName="ms-modalExample-container">
<div className="ms-modalExample-header">
<span>{this.props.item.date}</span>
</div>
</Modal>
);
}
}
When I click my DeafultButton on parent component it invokes and displays Modal for every item, how can I limit this to only one current clicked item. I tried with i: number, but I couldn't figure it out.
Since you are using single state for every child, so once you change this.state.showPopup to true, every modal will showed up. So you might can change the showModalEvent method.
private showModalEvent = (id: number): void => {
this.setState({ showPopupId: id });
and the render will be look like
return (
<div>
{this.props.items
.map((item: IListItem, i: number): JSX.Element => <ul className="list-group">
<li className="list-group-item">
<div className={styles.text}>
<p>{item.Id}</p>
</div>
<DefaultButton onClick={() => this.showModalEvent(item.Id)} text="Open Modal" />
{this.state.showPopupId === item.Id
? <ModalPopUpItem item={item}
showModalState={this.state.showPopup}
showModal={this.showModalEvent} /> : null
}
</li>
</ul>)}
</div>
);
Since this method only store one id at a time, this mean only one modal will showed up. If you want to show more modal at the time, you could change it to array or something.
Your parent component only has one flag to store the showPopup. This means that when you click on one of your buttons you set this flag for the whole parent component and that means that the whole list of your child components is evaluated and this.state.showPopup will be true.
You need to find a way to limit the effect of clicking the button to the item the button was clicked on.
You could, for example, not set the showPopup flag on the parent component, but on the item.
This could work, but you would have to revisit the way you include the ModalPopUpItem.
private showModalEvent = (item): void => {
item.showPopup = !item.showPopup;
}

Resources