onClick function won't fire for mapped component - javascript

Here is the relevant code:
const Members = () => {
// array of each video in selected grade
const videosMap = (videos) => {
return videos.map((video) => (
<VideoCard
key={video.id}
thumbnail={video.thumbnail}
title={video.title}
description={video.description}
onClick={() => {
handleVideoClick();
}}
/>
));
};
// updates state of shown videos & page heading
const handleGradeButtonClick = (videos, heading) => {
setShowVideos(videosMap(videos));
setVideosHeading(heading);
};
const handleVideoClick = () => {
console.log("test");
};
// controls state of which grade's videos to show
const [showVideos, setShowVideos] = useState(videosMap(kinder_videos));
// controls states heading to display depending on selected grade
const [videosHeading, setVideosHeading] = useState("Kindergarten");
const [showVideoDetails, setShowVideoDetails] = useState(null);
The handleVideoClick is the function that is not working when I click on one of the mapped VideoCard components.
Here is the full code if you want to see that:
https://github.com/dblinkhorn/steam-lab/blob/main/src/components/pages/Members.js
When I look in React DevTools at one of the VideoCard components, it shows the following:
onClick: *f* onClick() {}
If I don't wrap it in an arrow function it does execute, but on component load instead of on click. I have a feeling it has something to do with my use of .map to render this component, but haven't been able to figure it out.
Thanks for any help!

There's no problem with your mapping method, you just need to pass the onClick method as a prop to your VideoCard component :
On your VideoCard component do this :
const VideoCard = (props) => {
const { thumbnail, description, title, onClick } = props;
return (
<div className="video-card__container" onClick={onClick}>
<div className="video-card__thumbnail">
<img src={thumbnail} />
</div>
<div className="video-card__description">
<div className="video-card__title">
<h3>{title}</h3>
</div>
<div className="video-card__text">{description}</div>
</div>
</div>
);
};
export default VideoCard;

Related

how to control the state of many components

I have an api with which data comes about how many components there will be and what data will be stored in it, I generate these components through the map () method. Now I need that when I click on the image (which each component has), a class is added to this component, but at the moment I can only add an additional class to one element.
const [favorite, setFavorite] = useState();
// Function to add an additional class
async function addFavoriteChanel(index) {
if(favorite === index) {
setFavorite(0);
} else {
setFavorite(index)
}
}
// Getting data from api
async function getResponse() {
let response = await fetch('https:api.json')
let content = await response.json()
setChanelData(content.channels)
}
useEffect(() => {
getResponse();
}, [])
// visibleData it returns flickered data from api (Search engine, I did not add it here)
// ...img src={star}... Clicking on this image will add it to your favorites
<div className="container">
{Array.isArray(visibleData) ? visibleData.map((chanel, index) => {
return (
<div href={chanel.url} className="chanel__item" key={index}>
<img src={star} alt="star" onClick={() => addFavoriteChanel(index)} id={index} className={`star ${favorite === index ? 'active' : ''}`} />
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{chanel.number}. </div>{chanel.name_ru}</div>
</div>
)
}) : null}
</div>
I thought I could use an object in state and add keys for each component there, but I don’t understand how to do it correctly
I'd be happy to hear any advice. =)
You should make the state an array, that way you can add multiple channels to favourites:
const [favorite, setFavorite] = useState([]);
// Function to add an additional class
function toggleFavoriteChanel(index) {
setFavorite(prevState => {
let returnArray = prevState;
if(prevState.includes(index)){
return returnArray.splice(prevState.indexOf(index), 1)
}else{
return [...returnArray, index]
}
}
}
and then you only need to change one more thing
<img src={star} alt="star" onClick={() => toggleFavoriteChanel(index)} id={index} className={`star ${favorite.includes(index) ? 'active' : ''}`} />
now you can add and remove from favourites by pressing the image
You need to split the content of .map into separate function component and declare state there

How to show a Modal once onShowMoreClick is clicked?

<OneProfileKeyCard
title="Qualification"
showMoreText="See all qualifications"
onShowMoreClick={() => console.log('show more')}
>
Creating, communicating, and implementing the organization&apos;s vision, mission, and overall direction Leading the development and implementation of the overall organization&apos;s strategy.
</OneProfileKeyCard>
import React from 'react'
import './OneProfileKeyCard.scss'
type Props = {
title: string
showMoreText: string
onShowMoreClick: () => void
}
export const OneProfileKeyCard: React.FC<Props> = ({
title,
showMoreText,
onShowMoreClick,
children
}) => (
<div className="one-profile-key-card">
<h3>{ title }</h3>
<div>
{ children }
</div>
<button type="button" onClick={onShowMoreClick}>
{ showMoreText }
</button>
</div>
)
could anyone help me to set up a modal? Im trying to set up a modal once onShowMoreClick is clicked that would turn the children(creating, communicating, and implementing the organization...) into a modal. So far it looks like this:
You will need to have a state-managed in the parent component of where the OneProfileKeyCard child component is called from.
Something like this
const Parent = () => {
const [modalOpen, setModalOpen] = React.useState(false)
return (
<div>
<h1>Demo</h1>
<OneProfileKeyCard
title="Qualification"
showMoreText="See all qualifications"
onShowMoreClick={() => setModalOpen(!modalOpen)}>
text ... text
</OneProfileKeyCard>
</div>
)
}
I'm not sure what else is within your components, but you'll then need a way to close the model, right now I have set the showMoreClick prop to open/close, but if that should open then set it to true and do a similar pass-through for a closing false function.

how to keep the last component styles?

i'm building a to-do app using React Js . inside the task component i used a state to apply a certain styles for the completed task and it works fine . but , after i cliked any delete button the style of the completed task deleted . how can i prevent that ?
import Task from "../Task/Task";
import style from "./TasksList.module.css";
const TasksList = ({ tasks, deleteTaskHandler }) => {
return (
<div className={style.tasks}>
<div className="container">
{tasks.map((task, idx) => {
return (
<Task
task={task}
id={idx}
key={Math.random()}
deleteTaskHandler={deleteTaskHandler}
/>
);
})}
</div>
</div>
);
};
export default TasksList;
import { useState } from "react";
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler }) => {
const [isComplete, setIsComplete] = useState(false);
const markComplete = () => {
setIsComplete(!isComplete);
};
return (
<div
className={
isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
How are you maintaining the complete status of task in higher components?
Currently you are not initializing the complete state of Task.
If the task object contains the isComplete property, then you can use as shown below
const [isComplete, setIsComplete] = useState(task.isComplete);
however, you also need to update value of completed in task. So, I would suggest to have all lower components as stateless. and maintain the state at Higher component i.e. TaskList
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler, setTaskCompleteHandler }) => {
const markComplete = () => {
setTaskCompleteHandler(!task.isComplete);
};
return (
<div
className={
task.isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
Implement setTaskCompleteHandler in TaskList and pass is as prop as part of Task component reandering.
Your problem is in position of your delete button, that wrapped by div with makrComplete handler, so then you click on your delete button, markComplete fired too, so your isComplete changed and styles deleted.
To prevent this behavor, you can do a little trick with prevent default. So, try to wrap your deleteTaskHandler in another function like that:
const deleteButtonClickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
deleteTaskHandler(id)
}
and your delete button makrdown should be look like this:
<button onClick={deleteButtonClickHandler}> Delete </button>

How to focus an input in React via refs

I'm using React JS and have faced a problem.
I have a component on the page which has some inputs. When user clicks on any input a new block should be created below and the same input has to be focused at the same time.
Everything worked until I've created a show logic:
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
After that I get ×
TypeError: Cannot read property 'focus' of null
Below is my Main component where everything on the page happens.
const Sales = () => {
const [isTouched, setIsTouched] = useState(false);
const textInput = useRef(null);
function handleInput() {
setIsTouched(true);
textInput.current.focus();
}
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
return (
<main className="sales-page">
<div className="main__title">
<h2 className="main__heading">Bonuses</h2>
</div>
<div className="content-container">
<UploadForm>
<FileUploadInput
handleChange={handleInput}
placeholder="Header"/>
<FileUploadTextArea placeholder="Descr"/>
</UploadForm>
</div>
<div className="ready-container ">
{readyBlock}
</div>
</main>
)
}
const ViewModule = ({textInput}) => {
return (
<UploadForm classNames="textarea-written">
<FileUploadInput
ref={textInput}
placeholder="Заголовок"/>
<FileUploadTextArea placeholder="Descr"/>
<div className="btn-container">
<Btn classNames="cancel-btn">Cancel</Btn>
<Btn>Save</Btn>
</div>
</UploadForm>
)
}
Below is an input component:
const FileUploadInput = React.forwardRef((props, ref) => {
return (
<div className="text-input-wrapper">
<input
ref={ref}
type="text"
id="file-text-input"
name="file__upload-title"
placeholder={props.placeholder}
onClick={props.handleChange} />
</div>
)
});
I assume you want to focus the ViewModule component when its added.
The problem is that the Ref textInput is not assigned to any component before the ViewModule is added to the DOM tree. You would have to first add the ViewModule to the DOM tree on state change and then later in a useEffect hook you will find the textInput Ref properly assigned.
function handleInput() {
setIsTouched(true);
}
useEffect(() => {
if (textInput.current === null) return
if (isTouched) textInput.current.focus();
}, [isTouched])
Also you should pass the textInput Ref to ViewModule using React.forwardRef as you did for FileUploadInput.
const ViewModule = React.forwardRef((ref) => {...});
And use it like this.
const readyBlock = isTouched ? <ViewModule ref={textInput}/> : null;

ReactJs: How to pass data from one component to another?

If two or more cards from a component are selected how to pass data to a button component?
I have a landing page that holds two components; Template list and a button.
<TemplateList templates={templates} />
<MenuButton style={actionButton} onClick={onOnboardingComplete}>
Select at least 2 options
</MenuButton>
The TemplateList brings in an array of information for my Template and creates Template Cards with it. I am then able to select my cards and unselect them.
My Button when pressed just takes me to the next step of the onboarding.
I would like to know how I should approach linking these two components. My button now is gray and would like this to happen:
1. Button is gray and user cant go on to next step, until selecting two or more cards.
2. When two or more cards are selected, button turn blue and they are able to press the button to continue.
This is my Template List:
export type Template = {
title: string;
description: string;
imgURL: string;
id?: number;
};
type Props = {
templates: Template[];
};
const TemplateList = ({ templates }: Props) => {
return (
<div className={styles.scrollContainer}>
{templates.map((item) => (
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
/>
))}
</div>
);
};
export default TemplateList;
And this my Template Card:
type Props = {
title: string;
description: string;
img: string;
classNameToAdd?: string;
classNameOnSelected?: string;
};
const TemplateCard = ({ title, description, img, classNameToAdd, classNameOnSelected }: Props) => {
const { aspectRatio, vmin } = useWindowResponsiveValues();
let className = `${styles.card} ${classNameToAdd}`;
const [selected, setSelected] = useState(false);
const handleClick = () => {
setSelected(!selected);
};
if (selected) {
className += `${styles.card} ${classNameToAdd} ${classNameOnSelected}`;
}
return (
<div style={card} className={className} onClick={handleClick}>
<img style={imageSize} src={img}></img>
<div style={cardTitle}>
{title}
{selected ? <BlueCheckIcon style={blueCheck} className={styles.blueCheck} /> : null}
</div>
<div style={descriptionCard}>{description}</div>
</div>
);
};
TemplateCard.defaultProps = {
classNameOnSelected: styles.selected,
};
export default TemplateCard;
This is how it looks like now.
There are at least 3 ways to implement your requirement:
OPTION 1
Put Button component inside the <TemplateList> component
Add one useState tied to <TemplateList> component to hold the number of
selected cards
Add two new props onSelectCard and onDeselectCard to <TemplateCard> to increment/decrement newly created state by 1 for each selected/deselected item
Implement callback functions inside <TemplateList component (code below)
Call onSelectCard inside <TemplateCard> when needed (code below)
TemplateList Component
const TemplateList = ({ templates }: Props) => {
const [noOfSelectedCards, setNoOfSelectedCards] = useState(0);
handleSelect = () => setNoOfSelectedCards(noOfSelectedCards + 1);
handleDeselect = () => setNoOfSelectedCards(noOfSelectedCards - 1);
return (
<div classNae="template-list-container">
<div className={styles.scrollContainer}>
{templates.map((item) => (
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={handleSelect}
onDeselectCard={handleDeselect}
/>
))}
</div>
<MenuButton style={actionButton} onClick={onOnboardingComplete} className={noOfSelectedCards === 2 ? 'active' : ''}>
Select at least 2 options
</MenuButton>
</div>
);
};
TemplateCard Component
const TemplateCard = ({ ..., onSelectCard, onDeselectCard }: Props) => {
...
const [selected, setSelected] = useState(false);
const handleClick = () => {
if(selected) {
onDeselectCard();
} else {
onSelectCard();
}
setSelected(!selected);
};
...
};
Now, you'll have the current number of selected cards in your state noOfSelectedCards (<TemplateList> component) so you can conditionally render whatever className you want for your button or do something else with it.
OPTION 2
Using React Context to share state between components
It's okay to use React Context for such cases, but if your requirements contains other similar cases for handling/sharing states between components across the app, I suggest you take a look at the 3rd option.
OPTION 3
Using State Management like Redux to handle global/shared states between components.
This is probably the best option for projects where sharing states across the app is quite common and important. You'll need some time to understand concepts around Redux, but after you do that I assure you that you'll enjoy working with it.

Categories

Resources