Changing button label on click with grommet? - reactjs

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.

Related

React.js: How to close headlessui Disclosure modal from code?

I encountered an issue trying to close the headlessui Disclosure modal inside the panel.
My goal is to have a button inside the panel which can close the modal.
The way I tried to solve this problem is doing it manually using useRef, but it works partially.
After opening the panel for the first time, you can close the modal but if you try to open it again, it doesn't work. Can't figure out how to solve this issue.
Any help will be appreciated.
Here is the codesandbox link
And here is the code
import { Disclosure } from "#headlessui/react";
import React, { useState, useRef } from "react";
import CloseIcon from "#material-ui/icons/Close";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
const App = () => {
const [isClosed, setIsClosed] = useState(false);
const modalRef = useRef(null);
const hideModalHandler = (e) => {
e.preventDefault();
modalRef.current?.click();
setIsClosed(!isClosed);
};
return (
<Disclosure>
{({ open }) => (
<div ref={modalRef}>
<Disclosure.Button>
<span>modal</span>
<ExpandMoreIcon />
</Disclosure.Button>
{!isClosed && (
<Disclosure.Panel>
<CloseIcon onClick={hideModalHandler} />
<div>name</div>
</Disclosure.Panel>
)}
</div>
)}
</Disclosure>
);
};
export default App;
I haven't used headlessui Disclosure but I see that the function hideModalHandler isn't actually hiding but toggling. Did you mean setIsClosed(true) instead of setIsClosed(!isClosed)?
Also, after a quick look at the documentation, have you tried using the close from the headlessui Disclosure? You don't need useRef
Use the state, and wrap the disclosure button into a DIV
with onClick and some ID string to identify what disclosure must be open. Something like this (works for multiple disclosures):
const [keyOfOpenDisclosure, setKeyOfOpenDisclosure] = useState('')
const toggleDisclosure = (key: string) => {
setKeyOfOpenDisclosure((prev) => (prev !== key ? key : ''))
}
...
<Disclosure>
<div onClick={() => toggleDisclosure(someId)}>
<Disclosure.Button>
Text of disclosure button
</Disclosure.Button>
</div>
<Transition
show={someId === keyOfOpenDisclosure}
...

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

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>
)
}

Radio button onChange event not firing if a new element is added

Hope you are well, I stumbled upon an issue that I do not understand and before looking any further into the (pretty convoluted) code I inherited, I want to check with you if this is an expected behaviour.
{Object.keys(locations).map((keyName) => {
const { name: territoryName, code: territoryCode } = locations[keyName];
return (
<RadioButton
key={`LGRB${territoryName}`}
style={{ margin: '10px 0' }}
onChange={() => console.log('onChange')}
checked={territoryCode === selectedTerritoryCode}
name={territoryName}
label={territoryName}
dark
rightAlign
/>
);
})}
All works fine and as expected, it renders a series of radio buttons with selectable locations, onChange triggers as expected.
If I now push a new element to locations, it correctly renders the new element but:
First time a user clicks on any of the radio buttons, the onChange event doesn't trigger
Second time a user clicks on any of the radio buttons, the onChange event now triggers
Why?
Thank you for any help
Edit:
This is the <RadioButton> component:
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { RadioButton as GrommetRadioButton } from 'grommet';
const RadioButtonWrapper = styled.div`
StyledComponentsStyles omitted
`;
const RadioButton = ({ dark, rightAlign, ...props }) => (
<RadioButtonWrapper dark={dark} rightAlign={rightAlign}>
<GrommetRadioButton {...props} />
</RadioButtonWrapper>
);
RadioButton.propTypes = {
dark: PropTypes.bool,
rightAlign: PropTypes.bool,
};
export default RadioButton;
I think there is an issue with how the state of the checked RadioButton is being handled. To remove the clutter of handling this state and these strange side-effects coming from the rendering of individual RadioButton, try to see if RadioButtonGroup is solving your problem, it will be more straightforward to control the RadioButton(s) input events and state management.
Just for a POC, here is an example code that uses RadioButtonGroup, with dynamically added RadioButton items that react (pun intended) as expected to onChange events.
import React, { useState } from "react";
import { render } from "react-dom";
import { grommet, Box, Grommet, RadioButtonGroup } from "grommet";
export const App = () => {
const [fruits, setFruits] = useState(["pear", "banana", "mango"]);
const [value, setValue] = useState(fruits[0]);
const [counter, setCounter] = useState(0);
const handleOnChange = (event) => {
console.log(event.target.value);
setValue(event.target.value);
setFruits([...fruits, "apple" + counter]);
setCounter(counter + 1);
};
return (
<Grommet theme={grommet}>
<Box pad="small">
<RadioButtonGroup
name="radio"
options={fruits}
value={value}
onChange={(event) => handleOnChange(event)}
/>
</Box>
</Grommet>
);
};
render(<App />, document.getElementById("root"));
I'm not sure if that is the answer you were hoping for, but consider it as best practice advice in case you are planning to refactor the current behavior. Good luck!

How do you use Pseudo-classes in React using css modules?

The example I worked on is the following:
I have a button component that receives the background color as props. The received color will be the background that the button must have when hovering.
Second question:
The only way to use props in css, using css modules, is to apply the inline style in the js file where you declare the component?
Below I insert a code base (in the example the background color is applied by default):
import Button from "./Button";
export default function App() {
return <Button hoverColor={"red"} />;
}
...
export default function Button({ hoverColor }) {
const buttonStyle = {
backgroundColor: hoverColor
};
return <button style={buttonStyle}>click me!</button>;
}
Thanks
You may use React useState Hook to achieve the desired functionality: (Your Button component should look like this)
import React, { useState } from "react";
export default function Button({ hoverColor }) {
const [color, setColor] = useState("");
const buttonStyle = {
backgroundColor: color
};
return (
<button
style={buttonStyle}
onMouseOver={() => setColor(hoverColor)} //set the color when user hovers over the button
onMouseOut={() => setColor("")} //set color to an empty string otherwise
>
click me!
</button>
);
}

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.

Resources