Passing a Click Event to Child - reactjs

I have a click event to show a popup window in the parent and need to pass it down to List child and then to Item child and then to List child. When user clicks the Link component, it opens the pop up. It does not work right now, "TypeError: onCourseSelect is not a function. My code is below. Please help!
App.js
onCourseSelect=(course)=>{
this.setState({selectedCourse:course
,showPopup:true
});
};
<List course={course} onCourseSelect={this.onCourseSelect} />
List.js
const List=(course,onCourseSelect)=>{
return(
<Item key={course.ID} course={course} onCourseSelect={onCourseSelect} />)}
)}
Item.js
const Item=({course, onCourseSelect})=>{
return(<Link onCourseSelect={onCourseSelect(course)} course={course}></Link> )
}
Link.js
const Link=({course, onCourseSelect})=>{
return (
<div>
<input type="button" value="View More" onClick={onCourseSelect(course)} />
</div>
);
}

In Item.js you wrote onCourseSelect={onCourseSelect(course)} which should be rather onCourseSelect={onCourseSelect} as you just have to pass this as a prop to child component where it can be called. The course is passed as a separate prop in component.
On <input> you can then do, onClick = {()=>{ onCourseSelect(course) }}.
Writing onClick = { onCourseSelect(course) } will call the method onCourseSelect on each re-render.
App.js
onCourseSelect=(course)=>{
this.setState({
selectedCourse:course,
showPopup:true
});
};
<List course={course} onCourseSelect={this.onCourseSelect} />
List.js
const List=({course,onCourseSelect})=>{
return(
<Item key={course.ID} course={course} onCourseSelect={onCourseSelect} />)}
)}
Item.js
const Item=({course, onCourseSelect})=>{
return(<Link onCourseSelect={onCourseSelect} course={course}></Link> )
}
Link.js
const Link=({course, onCourseSelect})=>{
return (
<div>
<input type="button" value="View More" onClick={()=> onCourseSelect(course)} />
</div>
);
}

Related

React. Refresh parent component on button click

How to reload/refresh a parent component on button click.
In the code below i refresh page with javascript vanilla (window.location.reload(false)
Is there a way to reload the parent component or page without refreshing the page?
return product ? (
<div>
<div>
<img src={product.images[0]} alt={product.title} />
</div>
<div>
{product.title}
<br />
${product.price}
<br />
{product.description}
</div>
<div>
<button onClick={
() => {
setLiked(!liked)
if (favProduct) {
window.location.reload(false)
}
}
}>
<Icon size="28px" />
</button>
</div>
</div>
) : <p>Loading Product... </p>;
Not sure why you would want to do something like that, but here is one way to do it:
const Parent = () => {
const [toggleRefresh, setToggleRefresh] = useState(false)
return (
<Child setToggleRefresh={setToggleRefresh}/>
)
}
const Child = (props) => {
return (
<button onClick={
() => {
setLiked(!liked)
if (favProduct) {
//this will toggle the state of the parent, forcing a parent rerender
props.setToggleRefresh(prev => !prev)
}
}
}>
)
}

React: Child don't update parent's state

I'm new to React and I was trying to create a Modal using mui that opens and closes based on parent's state.
The problem is that the modal opens well based on state but upon closing, the modal onClose function works well, but when I click on Button that has the same with the same update state function it doesn't work.
Here's my parent component
const TableIcon = props => {
const {title,icon}=props;
// handling modal functionality
const [modalOpen, setModalOpen] = useState(false);
const handleModalOpen = () => {
setModalOpen(true);
};
const handleModalClose = () => {
setModalOpen(false);
};
console.log('modal', modalOpen)
return (
<button className={styles.button} title={title} onClick={()=>handleModalOpen()}>
{icon}
<ActionModal
open={modalOpen}
handleClose={handleModalClose}
handleOpen={handleModalOpen}
/>
</button>
)
}
and here is the modal component
const ActionModal=(props)=> {
const {open,handleOpen,handleClose}=props;
console.log(props,'modal')
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box className={styles.box}>
<div className={styles.header}>
<div className={styles.icon}>
<BsFillTrashFill/>
</div>
</div>
<div className={styles.content}>
<h2>You are about to delete a school</h2>
<h3>School Name/Tuituion</h3>
<p>This will delete the school from the database
Are you sure?</p>
</div>
<div className={styles.footer}>
<Button color='var(--unnamed-color-ffffff)' name='Cancel' onClick={handleClose}/>
<Button color='var(--unnamed-color-f53748)' name='Confirm'/>
</div>
</Box>
</Modal>
</div>
);
}
and here is the button component
const Button=(props)=>{
const {color,onClick,icon,name}=props;
return <div className={styles.container} style={{background:color}} onClick={()=>onClick()}>
{icon}
<span>{name}</span>
</div>
}
and the Button onClick works just fine
The problem is that your <ActionModal> component is inside the <button> tag. So when clicking the Cancel button in the modal you first get the call to handleModalClose, immediately followed by the call to handleModalOpen, because the "click" is getting passed to the button that opens the modal.
You need to change the code in <TableIcon> to something like:
return (
<>
<button title={title} onClick={() => handleModalOpen()}>
{icon}
</button>
<ActionModal open={modalOpen} handleClose={handleModalClose} />
</>
);
You can use this Stackblitz example.

How to prevent Carousel from rerendering

I am using React Grid Carousel in my app and have an images grid and a function that if an image in that grid is clicked it opens up a modal.
Here is some important bits of the code:
Parent component:
function parentComponent () {
return (
<section>
<div>
<h1>
Images
</h1>
</div>
<ChildComponent />
</section>
);
Child component:
function childComponent () {
const handleModal = useCallback(
e => {
setImageId(e.target.id);
setModalOpen(true);
},
[modalOpen, imageId]
);
}
const data = sectionData.map((data, i) => {
return data;
});
const items = data[0].map((item, i) => {
return (
<div id={i} onClick={e => handleModal(e)}>
<Image width={200} height={200} alt={item.name} />
</div>
);
});
return (
<Carousel
cols={2}
rows={3}
showDots
>
{items.map((item, i) => (
<Carousel.Item key={i}>
{item}
</Carousel.Item>
))}
</Carousel>
{modalOpen && <MemberModal/>}
);
The problem I am having is that if I am in let's say, page 2 of the modal, once I click in an image, the carousel goes back to the first page as the result of the rerender caused by the state change that happens on the handleModal.
I have tried React.memo this component and the parent too just in case and also using useCallback but nothing seems to work. I just simply cannot stop the rerender.
Any idea on how to accomplish stoping the rerender or sorting the issue at all?
React re-renders children components automatically after a state change. If your state is inside MyComponent it will re-render everything inside the return function.
But if you wrap the Carousel part of your component in memo, it will compare its props and if the props have not changed, it wont re-render the component.
const Example = ({ items }) => {
return (
<>
<Carousel
cols={2}
rows={3}
showDots
>
{items.map((item, i) => (
<Carousel.Item key={i}>
{item}
</Carousel.Item>
))}
</Carousel>
</>
);
};
export default React.memo(Example);

Share state between components in React

I'm trying to make a dashboard using React. There are few components: App, Block and other child components, let's call them Content.
Block is a simple bootstrap card with title, classes and some css.
In App I call Block and pass in the Content components.
But in some Content components there are functions, which can change Block component (e.g. add or remove classes).
Now I use states in App and Content components to change Block, but I don't think this is the right approach.
Adding setState in Block component is impossible, as I know, because there is no way to change props.
How can I change Block states from within Content components?
Example:
function App() {
return (
<div>
<Block
id="users-component"
title="Users Table"
classes=[]
content={
<MyTable class="users" />
}
/>
<Block
id="status-component"
title="Status Component"
classes=[]
content={
<Status class="status" />
}
/>
<Block
id="bdays-component"
title="Bdays Component"
classes=[]
content={
<Bdays class="bdays" />
}
/>
</div>
)
}
function Block(props) {
return (
<div id={props.id} className={props.classes.join(" ")}>
<h2>{props.title}</h2>
{props.content}
</div>
)
}
function MyTable(props) {
return (
<table className={props.class}></table>
)
}
function Status(props) {
const handleClick = (title) => {
changeTitle(title) // changes title in Block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newTitle")}>New Title</button>
</div>
)
}
function Bdays(props) {
const handleClick = (class) => {
addNewClass(class) // add new class to array "classes" in it's block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newClass")}>New Class</button>
</div>
)
}
P.S. Sorry for my English)
Problem is solved by moving Content components into Block body and using React.cloneElement. Now, it's look like this:
function App() {
return (
<div>
<Block
id="users-component"
title="Users Table"
>
<MyTable class="users" />
</Block>
<Block
id="status-component"
title="Status Component"
>
<Status class="status" />
</Block>
<Block
id="bdays-component"
title="Bdays Component"
>
<Bdays class="bdays" />
</Block>
</div>
)
}
function Block(props) {
[classes, addClasses] = useState([""])
[title, renewTitle] = useState(props.title)
return (
<div id={props.id} className={classes.join(" ")}>
<h2>{props.title}</h2>
{React.cloneElement(children, {addClasses, renewTitle})}
</div>
)
}
function MyTable(props) {
return (
<table> </table>
)
}
function Status(props) {
const handleClick = (title) => {
props.renewTitle(title) // changes title in Block
}
return (
<div>
<button onClick={() => handleClick("newTitle")}>New Title</button>
</div>
)
}
function Bdays(props) {
const handleClick = (class) => {
props.addClasses([class]) // add new class to array "classes" in it's block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newClass")}>New Class</button>
</div>
)
}
Doing it that way, you won't be able to share state between those components, since they're siblings (although I might get corrected on that)
You could instead, make myTable to be a child of Block, and pass a useState function TO myTable as a prop, that way you can change the state of Block from myTable
This is a sample implementation
import { useState } from "react";
export default function App() {
return (
<Block
id="users-component"
content={
<MyTable class="users" />
}
/>
);
}
function Block(props) {
const [headerText, setHeaderText] = useState("Marco!")
return (
<div id={props.id}>
<h1> {headerText} </h1>
<MyTable changeHeaderText = {setHeaderText} />
</div>
)
}
function MyTable(props) {
return (
<table className={props.class}>
<tbody>
<tr>
<td>
I'm a table and I can change my parent's header text!
</td>
</tr>
<tr>
<td>
<button
onClick = {() => props.changeHeaderText("polo!")} >Click me!</button>
</td>
</tr>
</tbody>
</table>
)
}

Trigger a slide right animation after rendering a component through history.push

I have this component that redirects the user upon a click, to another path, associated with a component.
return (
<Item
key={id}
onClick={() =>
history.push({
pathname: '/path/login',
state: {
requiredAction: name,
},
})
}
description={description}
/>
);
my login component as follows:
export default withRouter(function FormCard(props) {
let { location } = props;
return (
<div className="text-center">
{location.state.imageData && (
<Image className={location.state.imageData} />
)}
<div className="heading mb-5 pt-2">
<FormattedMessage defaultMessage="Access your account" />
)}
</div>
<div>
<input
className="email-form"
type="email"
inputMode="email"
onChange={changeEmail}
/>
</div>
</div>
);
});
Route is defined as follows:
<Route
exact
path="/path/login"
render={() => <FormCard {...this.props} />}
/>
==========================
Is is possible that I add some logic on history.push, after the initial click that triggers an animation to slide right the login component, instead of simply displaying it?
Yes, you can add some logic on history.push. I suggest that you listen in your /path/login for the passed state, like this:
Example Component (In your case the /path/login one):
const App = (props) => {
const {location} = props;
useEffect(()=>{
if(location.state.PASSED_VAL){
// Do something
}
},[location.state])
}
return (...)
}

Resources