js, react: cannot reenable a button that is rendered as disabled - javascript

i have a simple buttoN.
<Button id='button1' disabled onClick={() => buttonClick(false)}variant="contained">Text</Button>
This button is rendered as a grayed out version of itself, which is good.
Now, I want to enable the button. It should be:
document.getElementById("button1").disabled = false;
But nothing is enabled again.
What is the issue here?

Generally, you should avoid using document.getElementById and similar in React whenever possible.
To make your button enable, do the following:
const [isDisabled, setIsDisabled] = useState(true);
return (
<>
<Button id='button1' disabled={isDisabled} onClick={() => buttonClick(false)}variant="contained">Text</Button>
<div onClick={() => setIsDisabled(value => !value)}>toggle disabled</div>
</>
)

Related

onClick button to go back to previous page/component React

So I have this div that switch from one component to another by changing the state after clicking on a button.
{ !showForm ? (
<div className={styles.showTicketContainer}>
<div className={styles.showTicketBox}>
//some code for UI
</div>
<div className={styles.whitelistBox}>
<Button className={styles.whiteListToggleButton} shape="round" onClick={() => setShowForm(() => true)}>Whitelist</Button>
</div>
</div>
) : (
<InputEmail /> // render another component within this div after clicking the whitelist button
)}
So to visualize this is from this to this. And I would like to add one button on the email page so that I can go back to the first page.
Do I still use the same way which is:
{ !showForm ? (
//some UI code
) : (
<someComponent/>
)}
or is there any better way to do this.
you can use this:
const history = useHistory();
history.goBack();
If you're creating pages, I definitely suggest using React Router or a similar library to handle this. With React Router, you can easily "back" out to the previous page, and even the "back" button in the user's browser will work, instead of having to click a button.
This code will work.
Add them wherever required..
import { useHistory } from "react-router-dom";
function demo () {
let history = useHistory();
const goToPreviousPath = () => {
history.goBack()
}
return (
<div>
<Button
onClick={goToPreviousPath}
>
Back
</Button>
</div>
):
}

Click on a button that is moved when an input loses focus is not registered

I encountered a weird problem while creating a form with React (it is most likely not specific to React however). The form consists of an input, an error hint underneath it and a button, all vertically stacked. The input is focused on component mount, and the error hint might appear when the user clicks outside of the input. When the input is focused and the user tries to click the button, it loses the focus which may cause the hint to appear. The hint pushes the button downwards which prevents the button's click event from registering, resulting in a bad user experience.
Is there a way force the button to get clicked before the input loses focus?
I have replicated this situation here: https://jsfiddle.net/tacticalteapot/b40hLv3c/4/
Code from the fiddle:
function App() {
const ref = React.useRef(null);
const [showLabel, setShowLabel] = React.useState(false);
const [clicked, setClicked] = React.useState(false);
React.useEffect(() => {
ref.current.focus();
}, []);
return (
<div>
<input ref={ref} onBlur={() => setShowLabel(true)} />
<div>
{showLabel && <label>a label appeared that moved the button</label>}
</div>
<div>
<button onClick={() => setClicked(true)}>
CLICK ME
</button>
</div>
{clicked && <h6 style={{color: 'red'}}>CLICKED</h6>}
</div>)
}
This is happening because of the order of events. onBlur fires first which immediately shows the label. The <label> is rendered directly "on top" of the button. Your click event never registers. To test this, try setting height for your label or even wrap it in another container with a height. Alternatively, add padding to your button such that it's not fully covered by the <label> - it'll register.
Obviously there are better styling methods for this.
function App() {
const ref = React.useRef(null);
const [showLabel, setShowLabel] = React.useState(false);
const [clicked, setClicked] = React.useState(false);
React.useEffect(() => {
ref.current.focus();
}, []);
handleClick = () => {
setClicked(true);
}
return (
<div>
<input ref={ref} onBlur={() => setShowLabel(true)} />
<div className="test">
{showLabel && <label>a label appeared that moved the button</label>}
</div>
<div>
<button style={{padding: "30px"}} onClick={() => handleClick()}>
CLICK ME
</button>
</div>
{clicked && <h6 style={{color: 'red'}}>CLICKED</h6>}
</div>)
}
ReactDOM.render(<App/>, document.getElementById('app'));
I don't think there is a problem with focus. Try using Tab, button gets focused correctly.
Problem is with the UX itself and how the page is pushed down by validation messages.
It would be the best to change the UX slightly to allow the space for validation errors without pushing any elements below:
https://jsfiddle.net/eucj04np/5/
Also, remember to add aria-describedby that points to the id of the element with error validation message. This will help assistive technologies to read out the error content once element is focused.

Having an active state toggler on multiple buttons

I am trying to have my buttons light up once clicked, and dim when clicked twice. To indicate the user has already clicked that button.
const [isActive, setActive] = useState("false");
const handleToggle = () => {
setActive(!isActive);
};
And my buttons are toggling with the following code
<button
type="button"
value="Peperoni, "
className={isActive ? "button btn first" : "button btn firstActive"}
onClick={(event) => {
ToppingPlusMinus(event);
}}
>
Pepperoni
</button>
So far I have this working, however the buttons are all tied to the same state so when one is clicked they all light up. I am stumped on how to downsize this and make each button toggle its active class individually.
I'm aware I could just brute force it with many states for each button, but I know there is a smarter solution on there.
Thank you in advance for your time.
My full code is on a codesandbox here
You need to create a component that tracks its own state and fires the onClick
const ButtonClickable = props => {
const [isActive, setActive] = useState(false);
const handleToggle = (ev) => {
setActive(!isActive);
props.onClick && props.onClick(props.value)
};
return <button
type="button"
className={isActive ? "button btn first" : "button btn firstActive"}
onClick={handleToggle}
>
{props.value}
</button>
}
Then in the parent compoennt
<ButtonClickable onClick={ToppingPlusMinus} value="Peperoni, "/>
<ButtonClickable onClick={ToppingPlusMinus} value="Other, "/>
<ButtonClickable onClick={ToppingPlusMinus} value="Milk, "/>
Note that ToppingPlusMinus becomes ToppingPlusMinus(value)

React Material UI open modal from within autocomplete lose focus

I'm using the material-ui lib and I need to have an autocomplete where each item inside that autocomplete is clickable and opens a modal.
The structure in general is:
const ModalBtn = () => {
...
return (
<>
<button ... (on click set modal to open)
<Modal ...
</>
);
}
const AutoCompleteWithBtns = () => {
return (
<Autocomplete
renderTags={(value, getTagProps) =>
value.map((option, index) => <ModalBtn />)
}
...
/>
);
}
Note that the ModalBtn is a component that cannot be divided into two components of Button and Modal.
The issue is that when you click on the button inside the modal - the focus is kept inside the autocomplete, and the modal will never gets the focus (if I have an input inside the modal - I can't write anything inside).
Tried all the standard autocomplete/modal focus-related props (disableEnforceFocus, disableEnforceFocus, etc...) but nothing works.
Here is a working codesandbox example. As you can see - if you click on the button that is not inside the autocomplete component - everything works (you can add text inside the input field). If you click on the button that is inside the autocomplete - the input field inside the modal is not editable (you lose focus).
Here is an example of the issue:
The problem with having the Modal rendered from within the Autocomplete is that events propagate from the Modal to the Autocomplete. In particular, click and mouse-down events are both handled by Autocomplete in a manner that causes problems in your case. This is primarily logic intended to keep focus in the right place as you interact with different parts of the Autocomplete.
Below (from https://github.com/mui-org/material-ui/blob/v4.9.11/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js#L842) is the portion of the Autocomplete code that is getting in your way:
// Prevent input blur when interacting with the combobox
const handleMouseDown = (event) => {
if (event.target.getAttribute('id') !== id) {
event.preventDefault();
}
};
// Focus the input when interacting with the combobox
const handleClick = () => {
inputRef.current.focus();
if (
selectOnFocus &&
firstFocus.current &&
inputRef.current.selectionEnd - inputRef.current.selectionStart === 0
) {
inputRef.current.select();
}
firstFocus.current = false;
};
The default browser behavior when a mouse down event occurs on a focusable element is for that element to receive focus, but the mouse-down handler for Autocomplete calls event.preventDefault() which prevents this default behavior and thus prevents a focus change from the mouse-down event (so focus stays on the Modal itself as indicated by its blue focus outline). You can however successfully move focus to the Modal's TextField using the tab key, since nothing is preventing that mechanism of focus change.
The Autocomplete click handler is keeping focus on the input of the Autocomplete even if you click some other part of the Autocomplete. When your Modal is open, the effect of this is that when you click in the Modal, focus is moved briefly to the Autocomplete input element, but the focus is immediately returned to the Modal due to its "enforce focus" functionality. If you add the disableEnforceFocus property to the Modal, you'll see that when you click in the Modal (e.g. on the TextField) the cursor remains in the input of the Autocomplete.
The fix is to make sure that these two events do NOT propagate beyond the Modal. By calling event.stopPropagation() for both the click and mouse-down events on the Modal, it prevents the Autocomplete functionality for these two events from being executed when these events occur within the Modal.
<Modal
onClick={event => event.stopPropagation()}
onMouseDown={event => event.stopPropagation()}
...
Related answer: How can I create a clickable first option in Material UI Labs Autocomplete
The problem in your code was that the Modal was rendered from within the tag of the AutoComplete component, which was not ok because of the visibility of the components, the hierarchy of the components was the issue.
The fix is to move the Modal within the FixedTags component and pass the open handler to the ModalBtn in the renderTags prop;
I've updated your sandbox with a working variant HERE
The changes are below
const ModalBtn = ({ handleOpen }) => (
<button type="button" onClick={handleOpen}>
Open Modal (not working)
</button>
);
const FixedTags = function() {
const classes = useStyles();
const [modalStyle] = React.useState(getModalStyle);
const [open, setOpen] = React.useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<Autocomplete
multiple
options={autoCompleteItems}
getOptionLabel={option => option.title}
defaultValue={[autoCompleteItems[1], autoCompleteItems[2]]}
renderTags={(value, getTagProps) =>
value.map((option, index) => <ModalBtn handleOpen={handleOpen} />)
}
style={{ width: 500 }}
renderInput={params => (
<TextField
{...params}
label="Fixed tag"
variant="outlined"
placeholder="items..."
/>
)}
/>
<Modal open={open} onClose={handleClose}>
<div style={modalStyle} className={classes.paper}>
<h2 style={{ color: "red" }}>This one doesn't work</h2>
<p>Text field is not available</p>
<TextField label="Filled" variant="filled" /> <br />
<br />
<br />
<FixedTags label="Standard" />
</div>
</Modal>
</>
);
};

Select and change a specific element in ReactJS

I am trying to toggle the colours of two buttons in ReactJS. I can set the active state property of the selected button OK, but I can't work out how to change the style of another button (calcY) based on my selection (of calcX).
The code is brittle but I am pretty new to react and any pointers on best practices would be appreciated. PS also I am using react-bootstrap for the Form and buttons.
const MyForm = React.createClass({
handleChange(event, attribute) {
let eventValue = event.target.value;
if (attribute === 'calcX'){
this.setState({active: true});
this.setState({bsStyle: 'info'});
let calcXBtn = ReactDOM.findDOMNode(this.refs.calcBtnGroup.refs.calcX);
calcXBtn.setState({bsStyle: 'default'});
}
...
}
render() {
return (
<Form onSubmit={this.handleSubmit} horizontal>
<FormGroup>
<ButtonGroup ref="calcBtnGroup">
<Button active className='btn btn-info' ref="calcX" onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
<Button className='btn btn-default' ref="calcY" onClick={(event) => this.handleChange(event, 'calcY')}>Calculate Y</Button>
</ButtonGroup>
...
);
}
});
module.exports = MyForm;
You can set the className or style based of an element (or subcomponent) on the state of your component. It's nice to use a ternary operator and ES6 template literals here.
<Button ref="calcX" className=`btn ${this.state.active ? 'btn-info' : 'btn-default'}` onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
What this does, is setting a className based on the state of your component. The <Button> component always has a btn className. If state.active is true, the class btn-info will be added. Otherwise btn-default will be added.
So the only thing you have to do now, is set the state in your handleChange method and the classNames will be rendered appropriately.
Edit: it's not really necessary to use refs here. It's almost never necessary to use refs. You want use React events (onChange, onSubmit, etc.) to set input values on the state, and render those values in the value in your inputs. These are called controlled components. You can read more about it in the official documentation: https://facebook.github.io/react/docs/forms.html

Categories

Resources