I have a payment component and when I click a button, it should update the component state isOpen to true. This works fine in practice but when trying test it via enzyme, it wont update the prop.
The component looks like this:
class CashPayment extends Component {
state = {
isOpen: false
}
toggleModal = () => {
this.setState({ isOpen: true })
}
render() {
return (
<Mutation>
{() => (
<Fragment>
<Button
id="cash-payment-button"
onClick={this.toggleModal}
/>
<Modal
id="confirm-payment-modal"
isOpen={isOpen}
>
...
</Modal>
</Fragment>
)}
</Mutation>
)
}
}
So clicking #cash-payment-button should toggle the state isOpen which should open the modal.
In my test, I want to check the prop of my modal isOpen is set to true. But for some reason, the prop doesn't update in the test. However if I console log in my toggleIsOpen function, I can see the function gets called and the state updates.
My test is as so:
describe("Click Pay button", () => {
it("Should open confirm modal", () => {
Component = shallowWithIntl(
<CashPayment bookingData={bookingData} refetchBooking={refetchBooking} />
)
.dive()
.dive()
const button = Component.find("#cash-payment-button")
.props()
.onClick()
expect(Component.find("#confirm-payment-modal").prop("isOpen")).toEqual(true)
})
})
and the results are:
CashPayment › Click Pay button › Should open confirm modal
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
Why does the modal component props not update?
Related
I want to disable the rendering of a component inside react.js formik library
here is an example of code structure I have currently
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
{values?.show ?
(
<Text>Hello</Text>
) :
null}
<Rerenderedcomponent /> //no prop passed here
)
</formik>
And here is an example of my Rerendered component file
function Rerenderedcomponent()
{
const callingAPI = useCallback(()=>response,[])
}
export default React.memo(Rerenderedcomponent)
Now as I am clicking on the button(name showbtn) formik "show" field value is getting updated but my component(Rerenderedcomponent) is also getting rerendered & hence the api in it is getting called again
I tried by setting enableReinitialize={false} but nothing works
Is it possible to prevent this rerendering of the component(Rerenderedcomponent) on formik field update
PS:- The component should remain inside formik tag only
I prevent the component rerendering inside formik using the below workaround:
Created a new component say (Hello.js) & included the conditonal rendering(that was inside formik tag previously) inside it, like an example shown below
function Hello({show})
{
return(
<>
{show && <Text>Hello</Text>}
</>
)
}
export default React.memo(Hello);
Now I just imported & use the Hello.js component inside formik as shown below
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Hello show={values?.show}/> // Hello.js component
<Rerenderedcomponent /> //this will not rerender now
)
</formik>
Now since the component is already mounted into the DOM the rerendering will not occur on show value change
Also there is one another workaround to resolve this issue just by changing the order of components inside formik tag
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Rerenderedcomponent /> //placed above conditional rendering
{ values?.show ?
(
<Text>Hello</Text>
) :
null
}
)
I moved the rerendered component above the conditional rendering & it resolved the issue
To prevent RerenderedComponent from contacting the api every time. You must define a state in the parent component and pass it to child component:
const [apiData, setApiData] = useState(); // <===
return (
<Formik
initialValues={{ show: false }}
onSubmit={(values) => {}}
>
{({ setValues, values }) => (
<Form>
<button
type="button"
onClick={() => setValues({ show: !values.show })}
>
{values.show ? "hide" : "show"}
</button>
{values.show && (
<Rerenderedcomponent apiData={apiData} setApiData={setApiData} /> // <===
)}
</Form>
)}
</Formik>
);
And in the child component, you can check the existence of apiData and communicate with the api if needed:
function Rerenderedcomponent({ apiData, setApiData }) {
useEffect(() => {
if (!apiData) {
// fetch data here ...
setApiData('<response>');
}
}, []);
return null; // A Redact component must return a value
}
Hoc for popup does not work. Hock has a boolean state and two functions that change it. I pass them to the popup opening buttons and to the popup itself. The state changes through the console, but the popup itself does not open.
My HOC component
const Hoc = (Component) => {
const HandleChange = () => {
const [active, setActive] = useState(false);
function open() {
setActive(true)
}
function close() {
setActive(false)
}
useEffect(() => {
console.log(active)
}, [active])
return (
<Component activevvv={active} open={open} close={close} />
)
}
return HandleChange;
}
export default Hoc;
And my Button,the button works well, when you click on it, the state changes to true, I see this in the console log.
function ButtonsOur({ open }) {
return (
<div className="base__routes__button">
<Buttons className={"base__button__moreInf open-popup-exc"} Click={open} >Докладніше</Buttons>
<Buttons className={"base__button__moreInf open-popup-exc"} Click={open} >Забронювати</Buttons>
</div>
)
}
export default Hoc(ButtonsOur);
And my PopU. I pass the state of the current state to this component, and if the state is true, the popAp will be displayed. But nothing happens, although the state in the hockey changes.
function PopUpExc({activevvv, close}) {
return (
<div className={activevvv ? "popup__bg__exc active" : "popup__bg__exc"}>
<div className="popup__exc" onClick={e => e.stopPropagation()}>
<img onClick={close} src={Close} alt="close-popup" className="close-popup_exc" />
<div className="container">
<div className="row">
I have a parent component in which I'm struggling to properly open/close the child component (modal). The two code boxes below are simplified examples of my components.
EDIT: Here is a code sandbox with the following code -- there isn't an actual modal, however i've logged all of the stateful values that I assume will have an effect on this problem and you can see how they change/don't change as I hope they would.
Code Sandbox
When the parent component is open, I can click the MenuItem and I can see the state change, however the modal doesn't open unless I close the parent component temporarily and reopen it (then the parent component opens with the modal open already)
When the modal is open, and I try to close by clicking the close button (which has the state changing function from parent inside of the onClick method. this.state.showModal remains true, and doesn't change to false.
If I add a closeModal stateful value to the child component and change it during the close buttons onClick, this.state.showModal still remains true.
Thanks to whoever reaches out, and if you have any clarifying questions feel free to ask!
class Parent extends Component {
constructor(props) {
super(props);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this)
this.state = {
showModal: false
};
this.showModal = this.showModal.bind(this)
this.closeModal = this.closeModal.bind(this)
}
showModal() {
this.setState({ showModal: true });
}
closeModal() {
this.setState({ showModal: false });
}
render() {
return (
<MenuItem onClick={this.showModal}>
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
</MenuItem>
)}
const ChildComponent = ({
prop1,
isOpen,
closeModal
}) => {
const [modalOpen, setModalOpen] = useState(isOpen)
useEffect(() => {
setModalOpen(isOpen)
},[isOpen])
console.log('isopen on child', isOpen)
console.log('modalOpen', modalOpen)
return (
<div>
{modalOpen && (
<button
onClick={() => {
setModalOpen(false)
closeModal()
}}
>
{'click to close modal'}
</button>
)}
</div>
)}
)}
I figured out my problem!
In my parent component the onClick handler that sets the modal open wrapped my child component. I needed to remove it and conditionally render it separately like so:
<div>
<div onClick={this.showModal}>{"Click here to open modal"}</div>
{this.state.showModal && (
<ChildComponent
prop1={prop1}
isOpen={this.state.showModal}
closeModal={this.closeModal}
/>
)}
</div>
I am trying to move the open state for material-ui dialog to redux to prevent it from closing when a rerender occurs, but i having trouble with the dialog when a rerender occurs. Although the state is saved in redux and the dialog does stay open whenever a rerender occurs the open state stays open but the dialog does show the open animation (fading in) which is kinda annoying.
Students.js (parent component of the modal)
const Students = ({
app: { studentsPage: { savedAddDialogOpen }},
setStudentsPageAddDialogOpen}) => {
// Create the local states
const [dialogOpen, setDialogOpen] = React.useState(savedAddDialogOpen);
const dialogOpenRef = React.useRef(savedAddDialogOpen);
// Change redux dialog open
const setReduxDialogState = () => {
setStudentsPageAddDialogOpen(dialogOpenRef.current, savedAddDialogOpen);
};
// Open add student dialog
const dialogClickOpen = () => {
setDialogOpen(true);
dialogOpenRef.current = true;
setTimeout(() => setReduxDialogState(), 300);
};
// Close add student dialog
const dialogClose = () => {
setDialogOpen(false);
dialogOpenRef.current = false;
setTimeout(() => setReduxDialogState(), 300);
};
return (
<Container>
{/* Add student modal */}
<AddStudentModal dialogOpen={dialogOpen} dialogClose={dialogClose} />
</Container>
)
}
// Set the state for this component to the global state
const mapStateToProps = (state) => ({
app: state.app,
});
AddStudentModal.js
const AddStudentModal = ({
dialogOpen, dialogClose
}) => {
return (
<Dialog
open={dialogOpen}
>
{/* Lots of stuff*/}
<DialogActions>
<Button onClick={dialogClose}>
Close dialog
</Button>
</DialogActions>
</Dialog>
)
};
I hope this should be sufficient. I tried checking if the open state is actually correct when a rerender occurs and it is correct every time but it looks like the dialog is closed at a rerender no matter what the open state is and only a few ms later actually notices that it should be opened.
Any help would be really appreciated
Edit 1: Found out it has nothing to do with the open state coming from redux, if i use open={true} it still flashes, so probably a problem with material-ui itself?
Edit 2: PrivateRoute.js
const PrivateRoute = ({
auth: { isAuthenticated, loadingAuth },
user: { loggedInUser },
component: Component,
roles,
path,
setLastPrivatePath,
...rest
}) => {
useEffect(() => {
if (path !== '/dashboard' && path !== '/profile') {
setLastPrivatePath(path);
}
// Prevent any useless errors with net line:
// eslint-disable-next-line
}, [path]);
// If we are loading the user show the preloader
if (loadingAuth) {
return <Preloader />;
}
// Return the component (depending on authentication)
return (
<Route
{...rest}
render={props =>
!isAuthenticated ? (
<Redirect to="/login" />
) : (loggedInUser && roles.some(r => loggedInUser.roles.includes(r))) ||
roles.includes('any') ? (
<Component {...props} />
) : (
<NotAuthorized />
)
}
/>
);
};
// Set the state for this component to the global state
const mapStateToProps = state => ({
auth: state.auth,
user: state.user
});
I found the problem thanks to #RyanCogswell!
For anyone having the same problem here is the cause for me and the fix:
I was passing components into the Route component like this:
<PrivateRoute
exact
path={'/dashboard/students'}
component={(props) => (
<Students {...props} selectedIndex={selectedIndexSecondary} />
)}
roles={['admin']}
/>
By doing it this way i could pass props through my privateRoute function but this would also happen if you send the component this way in a normal Route component
Solution for me is just moving selectedIndexSecondary to redux and sending the component the normal way it prevented the re-mounting.
So just doing it like this will prevent this from happening.
<PrivateRoute
exact
path={'/dashboard/students'}
component={Students}
roles={['admin']}
/>
Also this will solve the localstates in your components from resseting to the default value. So for me it fixed two problems!
i have a parent component and i have called the child component in its HTML like this.
render() {
<EditBank
deal={dealDetailData && dealDetailData.id}
open={editBankModalStatus}
headerText={"Edit Bank Account"}
getInvestmentOfferingDetailsById = {() =>this.props.getInvestmentOfferingDetailsById({
id: this.props.match.params.id
})}
bankDetail={bankDetails}
toggleEditModal={() => this.handleEditModal("editBankModalStatus", {})}
/>
}
EditBank component is a modal which is only shown when editBankModalStatus is true and it is set to be true on a button click.
Now i want to set the state of EditBank only when button is clicked and whatever bankDetails has been passed to it.
I have tired componentDidMount lifecycle hook but it updated when component is rendered only.
I want to update the state of EditBank component when it is shown on screen only. Any help is appreciated.
Try do this :
render(){
return (
<div>
{editBankModalStatus === true &&
<EditBank
deal={dealDetailData && dealDetailData.id}
open={editBankModalStatus}
headerText={"Edit Bank Account"}
getInvestmentOfferingDetailsById = {() =>this.props.getInvestmentOfferingDetailsById({
id: this.props.match.params.id
})}
bankDetail={bankDetails}
toggleEditModal={() => this.handleEditModal("editBankModalStatus", {})}
/>
}
</div>
);
}
so EditBank will shown only if editBankModalStatus is true.