Formik: How to reset form after confirming on dialog - reactjs

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.

Related

How do you save an inputted email in one component and save it to another?

I'm trying to store an email a user has inputted to change their password on one modal and then display it on another modal. I have all the code done except this bit. I know I have to use state but am unsure of how to tackle it, especially with storing the value and displaying it on the second modal.
Here is my parent code
export default function YourDetails() {
const [mustLogin, setMustLogin] = useState(true);
const [isResettingPassword, setIsResettingPassword] = useState(false);
const [hasResetPassword, setHasResetPassword] = useState(false);
const navigate = useNavigate();
const proceedToNextStep = () => navigate('/step4');
const displayPaymentForm = () => setMustLogin(false);
const openResetPasswordModal = () => setIsResettingPassword(true);
const closeResetPasswordModal = () => setIsResettingPassword(false);
const openResetPasswordConfirmationModal = () => setHasResetPassword(true);
const closeResetPasswordConfirmationModal = () => setHasResetPassword(false);
const handleResetPassword = () => {
// send email to the back end
// close the modal
setIsResettingPassword(false);
// show the confirmation modal
openResetPasswordConfirmationModal();
};
if (mustLogin) {
return (
<>
<ResetYourPasswordModal
isOpen={isResettingPassword}
onClose={closeResetPasswordModal}
onSubmit={handleResetPassword}
/>
<ResetYourPasswordConfirmationModal
isOpen={hasResetPassword}
onClose={closeResetPasswordConfirmationModal}
/>
Here is my first modal where a user inputs their email:
interface ResetYourPasswordModalInterface {
isOpen: boolean,
onClose: () => void,
onSubmit: (emailOrAccountCode: string) => void,
}
export default function ResetYourPasswordModal(props: ResetYourPasswordModalInterface) {
const { isOpen, onClose, onSubmit } = props;
const [emailOrAccountCode, setEmailOrAccountCode] = useState('');
const handleEmailOrAccountCodeChange = (value:string) => setEmailOrAccountCode(value);
const handleSubmit = (evt: React.FormEvent) => {
evt.preventDefault();
onSubmit(emailOrAccountCode);
};
return (
<Modal isOpen={isOpen} closeModal={onClose}>
<h1>Reset your password</h1>
<p>
Enter the email address you signed up with or your account code,
and we will email you a link to reset your password.
</p>
<form onSubmit={handleSubmit}>
<TextInput
name="emailOrAccountCode"
label="Email or Account Code"
value={emailOrAccountCode}
onChange={handleEmailOrAccountCodeChange}
/>
<Button
type="submit"
colour="orange"
text="RESET PASSWORD"
/>
</form>
</Modal>
);
}
And here is the second modal that needs to display their email that was saved in the first modal:
interface ResetYourPasswordConfirmationModalInterface {
isOpen: boolean,
onClose: () => void,
}
export default function ResetYourPasswordConfirmationModal(
props: ResetYourPasswordConfirmationModalInterface,
) {
const { isOpen, onClose } = props;
const [userEmail] = useState('');
return (
<Modal isOpen={isOpen} closeModal={onClose}>
<h1>Email sent</h1>
<p>
{userEmail}
, we have sent you an email.
</p>
<p>Click the reset password button in the email to change your password.</p>
<Button
type="button"
colour="orange"
text="OK"
onClick={onClose}
/>
</Modal>
);
}
you are passing emailOrAccountCode from ResetYourPasswordModal, but in YourDetails component you can update to below code to get the emailOrAccountCode and store it in a state, so that it can be passed to ResetYourPasswordConfirmationModal
const handleResetPassword = (emailOrAccountCode) => {
// send email to the back end
// close the modal
setIsResettingPassword(false);
// show the confirmation modal
// console.log(emailOrAccountCode);
openResetPasswordConfirmationModal();
};

how to test if button click fired then it update the state or not?

//People Card
const PeopleCard: React.FC<PeopleListProps> = ({ peoples }) => {
const [showMore, setShowMore] = useState<number>(8);
const [btnHide, setBtnHide] = useState<boolean>(false);
const showMoreHandler: React.MouseEventHandler<
HTMLButtonElement
> = (): void => {
setShowMore((prevState) => prevState + 8);
if (showMore === 16) {
setBtnHide(true);
}
};
return (
<>
<PeopleCards data-testid='ples'>
{peoples.slice(0, showMore).map((people) => (
<PeopleList data-testid='people' key={people.login.uuid}>
<Avatar src={people.picture.large} alt='people' />
<PeopleWrapper>
<Title>Name: </Title>
<PeopleTitle data-testid='name' changeColor={people.name.title}>
{people.name.title.length > 4
? people.name.title.slice(0, 4)
: people.name.title}
. {people.name.first} {people.name.last}
</PeopleTitle>
</PeopleWrapper>
<PeopleDetails>
<Title>Email: </Title> {people.email}
</PeopleDetails>
<PeopleDetails>
<Title>City: </Title> {people.location.city}
</PeopleDetails>
</PeopleList>
))}
</PeopleCards>
<ShowMoreBtn
onClickHandler={showMoreHandler}
hide={btnHide}
label='show more'
/>
</>
);
};
export default PeopleCard;
//People card.test.tsx
it('should change state', async () => {
const setStateMock = jest.fn();
const useStateMock: any = (useState: any) => [useState, setStateMock];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
render(<PeopleCard peoples={peoples} />);
// const cardEle = screen.getAllByTestId('people');
const btnElement = screen.getByRole('button');
expect(btnElement).toBeInTheDocument();
fireEvent.click(btnElement);
expect(setStateMock).toHaveBeenCalledWith(16);
});
Here I tried to test if button click event fired then it will update the state or not? but I can not get it. I have two state in people card components and after show more button clicked it should update the shorMore state into 16. How to achieve that? could you please guys help me?
If you want to test that the state has changed, then you should just expect it to be called, regardless of the arguments:
//...
expect(setStateMock).toHaveBeenCalled()
To test the actual behavior besides the state change, I would go with something like the following
it('should show 16 people after clicking on "Show more"', async () => {
//Arrange
const { getByRole } = render(<PeopleCard peoples={peoples} />)
const btnElement = getByRole('button')
//Act
fireEvent.click(btnElement)
//Assert
expect(screen.getByTestId('ples').children.length).toEqual(16)
});
I'm not 100% sure if you should check for .children or .childNodes length, but you get the idea.

How to disable button based on state of other button

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>

React state not correct when function called via addEventListener

I have a React component that uses state to manage a changed variable if a form input is updated.
Assuming I've made some updates to the form, the issue I'm having is if I dispatch a click event to the onCancel function using addEventListener the value of changed is not correct but if I call onCancel from the JSX the value is correct.
Any ideas?
const Edit = (props) => {
let [changed, setChanged] = useState(false);
// Fired when a form value is updated
const onChange = (e) => {
setChanged("true");
};
// Close modal
const onCancel = () => {
console.log(changed); // This will be false when triggered from addEventListener
};
useEffect(() => {
let form = document.querySelector(".oda-edit-form");
// Close Window on click outside
form.addEventListener("click", function () {
onCancel();
});
}, []);
return (
<div>
<input type="text" onChange={(e) => onChange(e)} />
<button onClick={onCancel}>Close</button>
</div>
);
};
You need to re render your component as soon your state changes to run the onCancel() funtion.
let form = document.querySelector(".oda-edit-form");
// Close Window on click outside
form.addEventListener("click", function () {
onCancel();
});
}, [changed]); // < ----- add dependancy
Removed the addEventListener and added an onClick directly to the JSX with a custom method.
const Edit = (props) => {
let [changed, setChanged] = useState(false);
// Fired when a form value is updated
const onChange = (e) => {
setChanged("true");
};
// Close modal
const onCancel = () => {
console.log(changed); // This will be false when triggered from addEventListener
};
const onClickOutside = (e) => {
let element = e.target.querySelector('.wide-card');
let isClickInside = element.contains(event.target);
// // Close Modal
if (!isClickInside) {
onCancel();
}
};
return (
<div onClick-{(e)=>onClickOutside(e)}>
<input type="text" onChange={(e) => onChange(e)} />
<button onClick={onCancel}>Close</button>
</div>
);
};

How to update parent component with a state that has updated within a custom hook?

I'm not sure if this has been asked before. I couldn't find anything after googling.
I have a parent component which basically has a button which when clicked will open a modal.
Inside the modal, there is a form which makes a post-call to an API. If the post-call is successful, I need the modal to be closed. I'm experimenting custom hooks to achieve this.
Below is my code:
Custom Hook
type savedHook = {
saved: boolean,
loading: boolean,
error: string,
saveSearch: (search: any) => void,
showNewModal: boolean,
setShowNewModal: (boolean) => void
};
export const useSaveSearch = () : savedSearchHook => {
const [loading, setLoading] = useState(false);
const [saved, setSaved] = useState(false);
const [error, setError] = useState('');
const [showNewSaveSearch, setNewShowSearch] = useState(false);
const saveSearch = async (search: any) => {
setLoading(true);
fetch('my api', {
method: 'POST',
body: JSON.stringify(search),
headers: {
'Content-Type': 'application/json'
}
}).then((data) => {
setSaved(true);
setLoading(false);
setShowNewModal(false);
}).catch((error) => {
setError(error);
setLoading(false);
});
}
const setShowNewModal = (show: boolean) => {
setNewShowSearch(show);
}
return {
error,
loading,
saveSearch,
saved,
setShowNewModal,
showNewModal: showNewSaveSearch
}
}
Modal
export default function SaveSearch({isOpen, onDismiss}) {
const { state } = useSearch();
const [name, setName] = useState('');
const { loading, saved, error, saveSearch } = useSaveSearch();
const handleSave = () => {
saveSearch({
name,
query: state.query,
type: state.type
});
}
return (
<Modal isOpen={isOpen} onDismiss={onDismiss}>
<div>
<span>Save Search ({state.type})</span>
<IconButton styles={iconButtonStyles} iconProps={{iconName: 'Cancel'}} onClick={onDismiss} />
</div>
<div>
<TextField label="Name" autoFocus value={name} onChange={(e, value) => setName(value)} />
{loading && <Spinner size={SpinnerSize.small} />}
<DefaultButton text="Save" onClick={handleSave} iconProps={{iconName: 'Save'}} disabled={name.length === 0 || loading} />
</div>
</Modal>
)
}
Parent Component
export default function ParentComponent() {
const { showNewModal, setShowNewModal } = useSaveSearch();
return (
<div>
{<SaveSearch isOpen={showNewModal} onDismiss={() => setShowNewModal(false)} />}
<PrimaryButton text="Save Search" onClick={() => setShowNewModal(true)} iconProps={{iconName: 'Save'}} />
</div>
);
}
The problem I'm facing is, to open the modal, I'm calling setShowNewModal from the parent component which works fine. But after the save function, I'm calling setShowNewModal from the hook which doesn't get updated in the parent component.
It would be good if you could provide a working example.
Anyways, if I'm correct, the setShowNewModal(false); inside the saveSearch method of useSaveSearch custom hook should close the Modal, right?
Well, if that's the case, inside setShowNewModal you just call setNewShowSearch. Then, the value of setNewShowSearch is returned for a property named showNewModal, but inside the Modal, when you write the following line:
const { loading, saved, error, saveSearch } = useSaveSearch();
You don't consider the showNewModal property, in the deconstructor. Maybe I'm missing something in the flow: that's why I was asking for a working demo.
Anyways, going back to the issue: inside the Modal component you could just pass the onDismiss method:
const { loading, saved, error, saveSearch } = useSaveSearch(onDismiss);
And inside the useSaveSearch custom hook, just call the onDismiss parameter, which will call the callback () => setShowNewModal(false) defined in the Parent component.

Resources