How to disable button based on state of other button - reactjs

I'm trying to create 2 buttons in react app using material ui buttons with both buttons are enabled at the start of page load. When onClick on one of the button, the other button should be disabled vice versa.
Initial state
When onClick
const [btn1, setBtn1] = useState(false);
const [btn2, setBtn2] = useState(false);
const onBtn1 = () => {
setBtn1(!btn1);
};
const onBtn2 = () => {
setBtn2(!btn2);
};
}
How do i go about doing it? is there anyway to just use a single useState hook to handle 2 state buttons?

You can achieve this with only one state variable and one function
Code
const [disabledButton, setDisabledButton] = useState('');
const onButtonClick = (param) => {
setDisabledButton(param);
}
<Button onClick={() => onButtonClick('btn2')} disabled={disabledButton === 'btn1'}>
Button 1
</Button>
<Button onClick={() => onButtonClick('btn1')} disabled={disabledButton === 'btn2'}>
Button 2
</Button>

You can just enable the other button when a button is clicked:
const onBtn1 = () => {
setBtn1(prevState => !prevState);
setBtn2(false);
};
const onBtn2 = () => {
setBtn2(prevState => !prevState);
setBtn1(false);
};
in the JSX:
<button onClick={onBtn1} disabled={btn1}>btn1</button>
<button onClick={onBtn2} disabled={btn2}>btn2</button>

Change the state of other button on press.
const [btn1, setBtn1] = useState(true); //Initially both buttons are enabled
const [btn2, setBtn2] = useState(true);
const onBtn1 = () => {
setBtn1(!btn1);
setBtn2(false);
};
const onBtn2 = () => {
setBtn2(!btn2);
setBtn1(false);
};
}

you can use a single state, please refer the suggestion below:
state and event handlers-
const [activeButton, setActiveButton] = useState("None");
const onBtn1 = () => {
setActiveButton("Button1");
};
const onBtn2 = () => {
setActiveButton("Button2");
};
HTML Element part -
<Button disabled={!['None', 'Button1'].includes(activeButton)}>Button1</Button>
<Button disabled={!['None', 'Button2'].includes(activeButton)}>Button2</Button>

Related

How could I stop the notification-count to re-render?

I have a notification bell button with a dropdown. On top of it I have a small notification count.
I did the following logic to build the dropdown in React:
const [isOpen, setIsOpen] = useState(false);
const togglingMenu = () => setIsOpen(!isOpen);
const [isRed, setIsRed] = useState(true);
const toggleAlert = () => setIsRed(!isRed);
<NotificationsIcon onClick={() => { togglingMenu(); toggleAlert();}}></NotificationsIcon>
{!isOpen && (
<span><small className="notification-count">9+</small></span> )}
When I'm clicking on the bell button, the '9+' count disappears. How can I stop re-render it when I'm closing the notification dropdown?
You could use another useState like this:
const [isOpen, setIsOpen] = useState(false);
const [showNotificationCount, setShowNotificationCount] = useState(true);
const togglingMenu = () => {
if(showNotificationCount)
setShowNotificationCount(false)
setIsOpen(!isOpen);
}
const [isRed, setIsRed] = useState(true);
const toggleAlert = () => setIsRed(!isRed);
<NotificationsIcon onClick={() => { togglingMenu(); toggleAlert();}}></NotificationsIcon>
{showNotificationCount && (
<span><small className="notification-count">9+</small></span> )}

Run event handler functions synchronously after a React state change

I know useEffect allows you to run a function after state is updated.
However, I want to run different logic after a state change based on which different event handler causes a state change.
Context
I have a Parent component that shows or hides a child DialogModal component based on the [isDialogShown, setIsDialogShown] = useState(false) in Parent.
When isDialogShown
The Parent passes setIsDialogShown and 2 event handler callbacks to DialogModal: onDismiss (which adds focus to some element) and onConfirm (which adds focus to another element).
When onDismiss or onConfirm on the DialogModal is pressed, setIsDialogShown(false) should run first to hide the DialogModal, then run the respective callbacks to focus on differing elements of the page.
const Parent = () => {
const [isDialogShown, setIsDialogShown] = useState(false);
// These need to run after Dialog is closed.
// In other words, after isDialogShown state is updated to false.
const focusOnElementA = () => { .... };
const focusOnElementB = () => { .... };
const handleDismiss = () => {
setIsDialogShown(false);
focusOnElementA() // Needs to run after state has changed to close the modal
}
const handleConfirm = () => {
setIsDialogShown(false);
focusOnElementB() // Needs to run after state has changed to close the modal
}
return (
<>
<Button onClick={() => { setIsDialogShown(true) }>Open dialog</Button>
<DialogModal
isOpen={isDialogShown}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
What's the right pattern for dealing with this scenario?
I would use a separate state for the elements A and B to trigger them by in an additional effect. Enqueueing the toggle A/B state ensures the effect handles the update to call the focus A/B handles on the next render after the modal has closed.
const Parent = () => {
const [isDialogShown, setIsDialogShown] = useState(false);
const [toggleA, setToggleA] = useState(false);
const [toggleB, setToggleB] = useState(false);
useEffect(() => {
if (toggleA) {
focusOnElementA();
setToggleA(false);
}
if (toggleB) {
focusOnElementB();
setToggleB(false);
}
}, [toggleA, toggleB]);
const focusOnElementA = () => { .... };
const focusOnElementB = () => { .... };
const handleDismiss = () => {
setIsDialogShown(false);
setToggleA(true);
}
const handleConfirm = () => {
setIsDialogShown(false);
setToggleB();
}
return (
<>
<Button onClick={() => { setIsDialogShown(true) }>Open dialog</Button>
<DialogModal
isOpen={isDialogShown}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
A slight difference to Drew's answer but achieved using the same tools (useEffect).
// Constants for dialog state
const DIALOG_CLOSED = 0;
const DIALOG_OPEN = 1;
const DIALOG_CONFIRM = 2;
const DIALOG_CANCELLED = 3;
const Parent = () => {
// useState to keep track of dialog state
const [dialogState, setDialogState] = useState(DIALOG_CLOSED);
// Set dialog state to cancelled when dismissing.
const handleDismiss = () => {
setDialogState(DIALOG_CANCELLED);
}
// set dialog state to confirm when confirming.
const handleConfirm = () => {
setDialogState(DIALOG_CONFIRM);
}
// useEffect that triggers on dialog state change.
useEffect(() => {
// run code when confirm was selected and dialog is closed.
if (dialogState === DIALOG_CONFIRM) {
const focusOnElementB = () => { .... };
focusOnElementB()
}
// run code when cancel was selected and dialog is closed.
if (dialogState === DIALOG_CANCELLED) {
const focusOnElementA = () => { .... };
focusOnElementA()
}
}, [dialogState])
return (
<>
<Button onClick={() => { setDialogState(DIALOG_OPEN) }}>Open dialog</Button>
<DialogModal
isOpen={dialogState === DIALOG_OPEN}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
You should add another state for which element was triggered and then trigger the effect when the states change:
const [action, setAction] = useState('');
// ...code
const handleDismiss = () => {
setAction('dismiss');
setIsDialogShown(false);
}
const handleConfirm = () => {
setAction('confirm');
setIsDialogShown(false);
}
// Add dependencies to useEffect and it will run only when the states change
useEffect(() => {
if(!isDialogShown) {
if(action === 'dismiss') {
focusOnElementA()
} else {
focusOnElementB()
}
}
}, [action, isDialogShown])

Adding Two Click Handlers to 1 Function both using React hooks useState

I have a button "Add to Cart" and I would like it to do two things when clicked. I want it to add an item to the cart and I also want it to Change the text to "added" for 1 second.
The problem is if I call onClick twice the second function overrides the first.
If I put both click handlers into 1 function and then call that in 1 single onClick the only the function adding things to the cart works.
Where am I going wrong?
const [variant, setVariant] = useState({ ...initialVariant })
const [quantity, setQuantity] = useState(1)
const {
addVariantToCart,
store: { client, adding },
} = useContext(StoreContext)
const handleAddToCart = () => {
addVariantToCart(productVariant.shopifyId, quantity)
}
const text = "Add To Cart";
const [buttonText, setButtonText] = useState(text);
useEffect(() => {
const timer = setTimeout(() => {
setButtonText(text);
}, 1000);
return () => clearTimeout(timer);
}, [buttonText])
const handleClick = () => {
setButtonText("Added");
handleAddToCart();
}
return (
<>
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
Add to Cart
</button>
{!available && <p>This Product is out of Stock!</p>}
</>
you need to use the buttonText inside the button as below, however, in your code you have used the hard text Add to Cart.
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
{buttonText}
</button>

Formik: How to reset form after confirming on dialog

I want to reset form after submitting
I used resetForm() on child component, it actually worked, but when click on the button on child Component, resetForm works
const [submitting, setSubmitting] = useState(false);
const [showDialog, setShowDialog] = useState(false);
const [sendParams, setSendParams] = useState<sendParams>();
const handleSubmit = (values: FormValues) => {
setShowDialog(true);
setSendParams(values);
};
const handleConfirm = () => {
setSubmitting(true);
// send to API
sendParams && dispatch(sendRequest(sendParams));
};
return (
// child component which has forms and button to show confirmation dialog
<Page message='' submitting={submitting} onSubmit={handleSubmit} />
// when button on child component is clicked, dialog appears
// Click the button to submit on dialog
<Dialog
show={showDialog}
submitting={submitting}
onConfirm={handleConfirm}
/>
)
I tried to use resetForm like this
const [handleResetForm, setHandleResetForm] = useState<() => void>();
const handleSubmit = (values: FormValues, resetForm: () => void) => {
setShowDialog(true);
setSendParams(values);
setHandleResetForm(resetForm);
};
Sorry for the mess,
any advice would be appreciated!
Thanks
Add resetForm on submit and call it.
const checkLogin = (values, resetForm) => {
resetForm.resetForm()
}
This should resolve your issue.

how to toggle with react hooks

I have 2 buttons that can be toggled by that simple hook:
const [extendDetails, setExtendDetails] = useState(false);
const handleExtendDetails = () => setExtendDetails(!extendDetails);
const [extendPictures, setExtendPictures] = useState(false);
const handleExtendPictures = () => setExtendPictures(!extendPictures);
And these are the buttons:
<button onClick={handleExtendDetails}>Extend Details</button>
<button onClick={handleExtendPictures}>Extend Pictures</button>
Is there some sort of way to name the buttons and use e or some kind of a variable so I won't need to declare a hook for each button in case I've got 20 buttons and not just 2?
You can try using defining simple Object and target name combination.
const initialState = () => ({
extendDetails: false,
extendPictures: false
})
export default function App() {
const [toggle, setToggle] = useState(initialState())
const handleToggle = (e) => {
const { name } = e.target
setToggle({ ...toggle, [name]: !toggle[name] })
}
return (
<div>
<button name="extendDetails" onClick={handleToggle}>{toggle.extendDetails ? 'Open' : 'Close' } Extend Details</button>
<button name="extendPictures" onClick={handleToggle}>{toggle.extendPictures ? 'Open' : 'Close' } Extend Pictures</button>
</div>
);
}
Demo link is here
One option is to use an array instead:
const [toggledButtons, setToggledButtons] = useState(() => Array.from(
{ length: 20 },
() => false
));
Then you could do something like
const toggle = (i: number) => () => setToggledButtons(
toggledButtons.map((current, j) => i === j ? !current : current)
);
<button onClick={toggle(0)}>Extend Details</button>
<button onClick={toggle(1)}>Extend Pictures</button>

Resources