React - two buttons - a click on one of them opens both - reactjs

I have a React button component with onClick handler and its state - stating whether it was opened on click. If I render two of such components in a wrapper container, and I click on one of them, both buttons update the state. How should I handle the state, so that only one of the buttons updates without using ids?
import React, {useState} from 'react';
const Button = (props) => {
const [open, setOpen] = useState(false);
const text = open ? 'Open' : 'Closed';
const toggleButton = () => { setOpen(!open) };
return (
<button onClick={toggleButton}>{text}</button>
)
}
// parent component
import React from 'react';
import Button from './Button'
const ButtonsWrapper = () => {
return (
<div>
<Button />
<Button />
</div>
)
}
I also tried reversing the logic and putting the state in a wrapper component, and then passing the onClick handler as a props to a button, but the sitation is the same. They both change the state at the same time when one of them is clicked.
I am using React Hooks.

My understanding is that you are saying that when you click one button both buttons seems to have their state updated, but you only want the clicked button to update its state.
(i.e. if you click Button A only that button will show 'Open' as its text, Button B will continue to show closed)
If the above is right, then your code should already do the correct thing. If it doesn't then you might have a bug elsewhere that would cause this.
If however you want to click one button and BOTH should switch state then you could achieve this by keeping track of the state in the parent component:
import React, {useState} from 'react';
const Button = (props) => {
const text = props.isOpen ? 'Open' : 'Closed';
const handleClick = () => {
// do some stuff you want each button to do individually
.....
props.onClick()
}
return (
<button onClick={handleClick}>{text}</button>
)
}
// parent component
import React from 'react';
import Button from './Button'
const ButtonsWrapper = () => {
const [buttonState, setButtonState] = useState(false)
return (
<div>
<Button onClick={() => setButtonState(!buttonState)} isOpen={buttonState} />
<Button onClick={() => setButtonState(!buttonState)} isOpen={buttonState} />
</div>
)
}

Related

React prevent unnecessary parent re-render when state updates using functional components

There are similar questions to this one available but they are either unanswered or use React class components. I am looking to use React functional components.
I have a parent component which is a webpage, it has two state parameters, the first holds a dataset and the second indicates if a modal is open. There are two child components, the first draws the data as an SVG. The second is a modal, which is used to show a form that can update the data.
My aim is that if I open the modal and edit some data, the SVG is redrawn. If I open the modal and close it again without editing data, the modal is not redrawn.
My issue is that the SVG is currently redrawn whenever the modal is open or closed, because the state of the modal is held in the parent, and updating any stateful parameter in the parent causes the it to re-render.
Here is a minimal example:
import React, { useState, useEffect } from 'react';
export function App(props) {
const [svgData, setSvgData] = useState("data");
const [show, setShow] = useState(false);
//Child that draws svgData
const SVG = () => {
useEffect(() => {
console.log("SVG got drawn");
}, [svgData]);
return (
<>
<p>The SVG</p>
</>
);
};
//Child modal. It would contain a form to update svgData
const Modal = () => {
console.log("modal showing: ", show);
return (
<button type="button" onClick={() => setShow(!show)}>
Click Me!
</button>
);
};
return (
<>
<SVG />
<Modal />
</>
);
}
Here is the working code in playcode.io
https://playcode.io/1166120
How can I update this code, so that the SVG only redraws if the data is updated, and not when the modal is updated?
You need to separate the child components and define them as their own FC otherwise they're seen as part of the parent and will get rerendered everytime the state changes. See the minimal change below.
import React, { useState, useEffect } from 'react';
//Child that draws svgData
function SVG(props) {
const { svgData } = props
useEffect(() => {
console.log("SVG got drawn");
}, [svgData]);
return (
<>
<p>The SVG</p>
</>
);
};
export function App(props) {
const [svgData, setSvgData] = useState("data");
const [show, setShow] = useState(false);
//Child modal. It would contain a form to update svgData
const Modal = () => {
console.log("modal showing: ", show);
return (
<button type="button" onClick={() => setShow(!show)}>
Click Me!
</button>
);
};
return (
<>
<SVG svgData={svgData} />
<Modal />
</>
);
}
This is the reason its better not to define components inside one another, have the child components defined outside with their respective state ....
Why ?? as on each update, the functions (components declared inside)
gets recreated and so it fires the useEffect ...
See the updated code here

can we able to specify on click on the component definition itself?

I want to create a component(comp) with onclick event handler. which should be handled by the component itself (i.e. inside the comp.js file).
if I use it inside the parent component we don't need to specify the event but it is handled by the component element(comp element).
is this possible. Any idea to develop this one.
in ParentComponent.js current behavior.
<NewComponent onClick={clickBehaviour}/>
I want like,
In NewComponent.js
const NewComponent.js = ()=>{
// Some code
const clickBehaviour = () =>{
// click behaviour
}
}
Is it possible in the current standards?
why you want to write your onClick event in parent component?
you can do it inside NewComponent.js easily.
just do this:
import React from 'react'
function NewComponent() {
const clickBehaviour = () =>{
// click behaviour
}
return (
<div onClick={clickBehaviour}>
//some jsx here
</div>
)
}
export default NewComponent
and use in anywhere you want to use without onClick event :
< NewComponent />
i cant understand well you situation but you can use forwardRef if you want (also can use old getElementById but using forwardRef is recommended).
import React, { useRef } from "react";
const NewComponent = React.forwardRef((props, ref) => (
<div onClick={() => alert("div 2 clicked")} ref={ref}>
div 2
</div>
));
export default function App() {
const compRef = useRef(null);
return (
<div>
<NewComponent ref={compRef} onClick={() => {
compRef && compRef.current && compRef.current.click();
}} />
</div>
);
}

React functional component able to change its state, is possible?

I want to create a React component for a Dialog.
I want to use Bootstrap Modal component.
I use TypeScript.
This is the usage I plan to use:
<Dialog icon="edit" title="Edit the item" buttons={...} onSave={...} >
... put here the content of the dialog
</Dialog>
Now, I started to write the code to make the Modal visible (pseudo-code):
interface Props {
// open by:
buttonText: OpenIcons,
title: string,
isOpen?: boolean
}
export const Dialog:FC<Props> = (props) => {
let {buttonText, title, isOpen, children} = props
return <>
{button && <button className="btn btn-sm">{buttonText}}</button> }
{ isOpen && <div>
{title}
<div>{children}</div>
<buttons>...</buttons>
</div>
}
</>
}
The problem is: how can I manage to change the isOpen property to true and made the Dialog component reflect this state?
Yes, I can use another component in the page (button text, icon, link...) that manage to change that state and pass it to the Dialog component, but I don't want to.
I want the Dialog component to self-manage its icon/button/link trigger.
The only solution I can think about is create another component inside the Dialog component, a sort of wrapper.
It has to manage the DialogButton (or whatever) and a new DialogInternal component that are not exported, so that the developer just see the exorted Dialog component.
I hope this idea is clear.
There is any better way (simpler, cleaner) to achieve my goal?
Here is your code using a useState hook
export const Dialog:FC<Props> = (props) => {
const [open, setOpen] = React.useState(false);
let {buttonText, title, children} = props
const handleClick = () => setOpen(!open);
return <>
{button && <button className="btn btn-sm" onClick={handleClick}>{buttonText}}</button> }
{ open && <div>
{title}
<div>{children}</div>
<button onClick={handleClick}>Close</button>
</div>
}
</>
}
You can use react hooks like useState and useEffect for maintaining a state in functional components
Ex:
for Usestate
const [isOpen, setIsOpen] = React.useState(initial value);
for useEffect
React.useEffect(()=>{},[dependencies]);

How to pass ref const from parent to child component in react functional component?

I am working on the Reactjs project and I want to open a dialog box which is a global component from a cart page.
This is cart page when I click on cart delete button then it shows dialog current is null.
/**
* Cart Page
*/
import React, { Fragment } from 'react';
import { Button,Box} from '#material-ui/core';
import { ConfirmationDialog } from 'components/GlobalComponents';
import IntlMessages from 'util/IntlMessages';
function Cart(props){
const [anchorEl,setAnchorEl] = React.useState(null);
const [item,setItem] = React.useState('');
const dialog = React.useRef(null);
const onDeleteCartItem = (item) => {
// setItem(item);
console.log(dialog);
// dialog.current.open();
}
const deleteCartItem = (popupResponse) => {
console.log(popupResponse);
}
return (
<div className="cart-page white-btn-color">
<Button
className="cart-delete"
onClick={() => onDeleteCartItem(cart)}
>
<Box component="span" className="material-icons-outlined">delete</Box>
</Button>
<ConfirmationDialog
ref={dialog}
onConfirm={(res) => deleteCartItem(res)}
/>
</div>
)
}
export default Cart;
Confirmation Dialog:
import React from 'react';
import { Dialog, DialogContent, DialogActions, Button, Box, Typography } from '#material-ui/core';
function ConfirmationDialog(props) {
const [open,setOpen] = React.useState(false);
//Define function for open confirmation dialog box
const openDialog = () => {
setOpen(true);
};
//Define function for close confirmation dialog box and callback for delete item
const closeDialog = (isTrue) => {
setOpen(false);
props.onConfirm(isTrue)
};
console.log(props);
return (
<Dialog
open={open}
ref={props.ref}
onClose={()=>closeDialog()}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
<Box textAlign="center" pt={2}>
<Typography variant="h5">
Are you sure you want to delete this product ?
</Typography>
</Box>
</DialogContent>
</Dialog >
);
}
export { ConfirmationDialog };
I am trying with the above-mentioned code this is not working please tell me where I`m wrong.
Thanks
There are a few things going wrong. Firstly, the ref prop has a special meaning and is not passed to the component unless you use React.forwardRef().
Then, according to the docs you should not use ref as a means to expose an API, so to speak.
My preferred method is to define the dialog way up in the component tree and expose the open/close functions via a context. You can consume those methods with useContext() or even create your own useDialog() which internally calls useContext() (in the file that declares the dialog) with the local context object, so you don't have to export the context object.

Changing button label on click with grommet?

Does anyone have any insight into changing button label on click when using grommet UI and styled components? Is it easier to just use a custom button rather than grommets?
Here are a few examples on how to make the Button label change onClick action:
import React, { useState } from "react";
import { render } from "react-dom";
import { grommet, Box, Button, Grommet, Text } from "grommet";
const App = () => {
const [label, setLabel] = useState(1);
const [name, setName] = useState("shimi");
const flipName = name => {
return name === "shimi" ? setName("shai") : setName("shimi");
};
return (
<Grommet theme={grommet}>
<Box pad="small" gap="small" width="small">
// label is a number that is being increased on every click event
<Button
label={label}
onClick={() => {
setLabel(label + 1);
}}
/>
// label string is controlled with external logic outside of the button.
<Button
label={<Text weight="400">{name}</Text>}
onClick={() => {
flipName(name);
}}
/>
</Box>
</Grommet>
);
};
render(<App />, document.getElementById("root"));
In addition to the examples above, in Grommet, you don't have to use the label prop and you can leverage the Button children to control the way your Button display.

Resources