How to click the event inside the Modal in react Jest - reactjs

I am new to react.
I have a Customer component which has a modal. After opening the modal there are two buttons on that Yes and cancel. Once the user clicks the yes button API will get called and then I will reload the screen. I need to test the modal's primary and secondary buttons.
export default function
Customer({customerInfo, reloadCustomerData}) {
const [visible, setVisible] = useState(false);
const [editCustomerModal, setEditCustomerModal] = useState(false);
const [customerData, setCustomerData] = useState({});
const [apiError, setApiError] = useState(null);
const[status, setStatus] = useState("");
const editCustomerRef = useRef(null)
useEffect(() => {
setCustomerData(customerInfo)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [customerInfo]);
function handleActivateAndDeactivate(status) {
setVisible(true)
setStatus(status)
}
async function updateCustomerStatus(status) {
let reqBody = {
"status": status
}
console.log("status req body", reqBody)
try {
await ApiClient.put(UrlBuilder().updateCustomer(customerData.customerId), reqBody).then(response => {
if (response.status === 200) {
setVisible(false)
reloadCustomerData()
}
})
} catch (respError) {
setApiError(respError);
}
}
return (
<div>
<Modal data-testid="testActivateAndDeactivateModal"
buttonPrimary={{
label: "Yes",
onClick: () => updateCustomerStatus(status)
}}
buttonSecondary={{
label: "Cancel",
onClick: () => setVisible(false)
}}
onDismiss={() => setVisible(false)}
title={status === 'ACTIVE' ? "Activate Customer" :"Deactivate Customer"}
visible={visible}
>
<Paragraph>{status === 'ACTIVE' ? "Are you sure you want to activate the customer?" : " Are you sure you want to de-activate the customer?\n" +
"Customer will not be able to place any orders or create any new accounts"}
</Paragraph>
</Modal>
</div>
)
}
I want to test if reloadCustomerdata is getting called or not after clicking primary button "Yes". How Can I achieve that?

Related

Set a state inside of a const

I am trying to setAddedToCard(true); when the onClick button is clicked to show that the product is added to the cart but addedToCard is only true after the button is clicked twice. This is because checkItem === true is set upon first click, then retrieved during the second click.
How do I change the code so that addedToCard is true upon first click & maintain checkItem === true if the item was successfully added to the cart?
const [addedToCard, setAddedToCard] = useState(false);
const CampaignAdminAuthToken = localStorage.getItem("CampaignAdminAuthToken");
const user = useSelector((state) => state.user);
const dispatch = useDispatch();
const cart_btn = addedToCard ? (
<Button>
<FontAwesomeIcon icon={solid("circle-check")} />
</Button>
) : (
<Button
onClick={() => {
props.addToCart(productId, totalQuantity);
dispatch(setIsUpdateCart(!user.isUpdateCart));
}}
>
{props.currencySymbol + (totalPrice ? Number(totalPrice) : 0)}
</Button>
);
useEffect(() => {
(async () => {
setTotalPrice(productPrice.toFixed(2));
if (!CampaignAdminAuthToken) {
const checkItem = await props.checkItemInCart(productId);
if (checkItem === true) {
setAddedToCard(true);
} else {
setAddedToCard(false);
}
}
})();
}, [!user.isUpdateCart, productPrice]);

React useState not updating on click its just one step behind in updating state

I set state(id) on button click and i want to use that data(id) in that state, it just showing undefined in the first click
[State Initialization]
const [id,setId] = useState("");
[Onclick of Button]
`render: (record) => (
<div>
<Button
className="details-btn-approve"
onClick={()=>{
handleApprove(record)
}}
>
Approve
</Button>
)`
[useEffect and calling on click action]
`
useEffect(()=>{
},[id]);
const handleApprove = async (record) => {
setId(record.ID);
console.log(id);
}
`
i was expecting the id to be changing every time i click button
here is the complete Code
`
import {useEffect, useState} from "react";
import {Button, Card, Input, Modal, Table} from "antd";
import axios from "axios";
import {endPoints} from "./ApiEndPoints";
function Approval() {
const [isLoading, setIsLoading] = useState(false);
const [dataSource, setDataSource] = useState([]);
const [searchedText,setSearchedText] = useState("");
const [id,setId] = useState("");
useEffect(()=>{
ViewUnApproved();
},[setDataSource])
const ViewUnApproved = async () =>{
setIsLoading(true);
const {data: response} = await axios.get(
endPoints.pendingApprovalAsset
);
if (response["status"] === 100){
setIsLoading(false)
setDataSource(response["pending_approval"]);
} else if(response["status"] === 104){
console.log('no data 🐢🐢🐢')
}
}
useEffect(()=>{
},[id])
const handleApprove = async (record) => {
setId(record.ID);
const data = {
ASSET_ID: id,
}
const {data: response } = await axios.post(
endPoints.approve,
data
);
if((response["status"] = 100)){
setIsLoading(false);
console.log(response.to_be_approved);
}
}
console.log(id);
const columns = [
{
title: "Asset",
dataIndex: "DESCRIPTION",
key: "name",
filteredValue:[searchedText],
onFilter:(value,record)=>{
return String(record.DESCRIPTION)
.toLowerCase()
.includes(value.toLowerCase()) ||
String(record.TAG_NUMBER)
.toLowerCase()
.includes(value.toLowerCase()) ||
String(record.BUILDING_NAME)
.toLowerCase()
.includes(value.toLowerCase()) ||
String(record.STATUS)
.toLowerCase()
.includes(value.toLowerCase()) ||
String(record.CONDITION)
.toLowerCase()
.includes(value.toLowerCase())
}
},
{
title: "Tag",
dataIndex: "TAG_NUMBER",
key: "tag",
},
{
title: "Location",
dataIndex: "BUILDING_NAME",
key: "location",
},
{
title: "Status",
dataIndex: "STATUS",
key: "status",
},
{
title: "Condition",
dataIndex: "CONDITION",
key: "condition",
},
{
title: "Action",
key: "",
render: (record) => (
<div>
<Button
className="details-btn-approve"
onClick={()=>{
handleApprove(record)
}}
>
Approve
</Button>
<Button
className="details-btn-reject"
id={record.ID}
type="danger"
onClick={() => {
//showModal(record);
}}
>
Reject
</Button>
</div>
),
},
];
return (
<>
<Card
title="LIST OF ASSETS TO BE APPROVED"
bordered={false}
className="table-card"
>
<Input.Search className='search-input'
placeholder ="Search here ..."
onSearch={(value)=>{
setSearchedText(value)
}}
onChange = {(e)=>{
setSearchedText(e.target.value)
}}
/>
<Table
loading={isLoading}
columns={columns}
dataSource={dataSource}
pagination={{ pageSize: 10 }}
/>
</Card>
</>
);
}
export default Approval;
`
Inside your handleApprove, method updating the state of id will only update it in the next rerender.This is usually fast enough if you need it somewhere else in the code (e.g. the code inside return()). However if you need it immediately in handleApprove you should use the value you've calculated.
const handleApprove = async (record) => {
const newId = record.ID;
setId(newId);
console.log(newId);
//do whatever you want with newId
}
state updates cause a re-render as mentioned in this answer. However the state only has the new value after the re-render, whereas your console log gets called before that re-render happens. if you wanted to just do some logic with a value you already know when you are setting that state, then just use the value you know!
Using console.log() inside your handleApprove() function won't display the last value of the ID. Indeed, setId() is an async function so console.log() can be called even if setId() is not done yet. Move your console.log() out of the function and it will display the last id.
Furthermore, you're using class component that are no longer recommended by React. Here is a proper typescript code example: sandbox. If you're not using Typescript, just get rid of all the types and it will be ok for basic JS.
In your example, you're using a <Button /> component. This usage means you should use a callback (doc here) to send the click event to the parent component. The called function should be a useCallback() function (doc here).

Click outside component React

I've use this method : Detect click outside React component
The problem is :
I have a component for a burger menu that as a state.
State determine if menu should be open or not.
The 2 components are not in the same container.
When I click burger, I just toggle state (!state).
When I click outside the menu I say state = false
But when I click the burger it now says (!state AND state = false) which cause the menu to stay open when clicking again to (try to) close it.
Burger icon code :
const onClick = () => {
setDeployMode(!deployMode);
dispatch(deployModeAsideMenu({ deployMode: !deployMode }));
};
<HamburgerButton
open={asideMenu.deployMode}
width={28.88}
height={25.27 / 1.5}
strokeWidth={3}
color={asideMenu.deployMode ? "var(--black)" : "var(--pink)"}
animationDuration={0.5}
onClick={onClick}
/>
Aside menu as a ref wrapper :
const [deployMode, setDeployMode] = useState(false);
const changeDeployMode = () => {
setDeployMode(!deployMode);
dispatch(deployModeAsideMenu({ deployMode: !deployMode }));
};
useEffect(() => {
setDeployMode(asideMenu.deployMode);
setLandscapeMode(asideMenu.landscapeMode);
}, [asideMenu]);
const useOutsideAlerter = ref => {
useEffect(() => {
const handleClickOutside = event => {
if (ref.current && !ref.current.contains(event.target) && window.innerWidth < 900) {
setDeployMode(false);
dispatch(deployModeAsideMenu({ deployMode: false }));
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
};
const wrapperRef = useRef(null);
useOutsideAlerter(wrapperRef);
// asideMenu comp
<AsideMenuContainer
ref={wrapperRef}>
// as other code here
<AsideMenuContainer />

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>

React memory leaks warning with using a stateful button in a dialog or alert in Material UI

I am using Material UI for a component library and noticed when I click a button within a Dialog or Alert (both components manage states of open/closed), I get a memory leaks warning. I am unsure of how to fix the problem here. The button component uses state to create an active class when clicked, which uses a setTimeout onClick to make the button click more visible/longer lasting in the UI.
This is the button component:
function Button({
classes,
className,
onClick,
...props
}) {
let [active, setActive] = useState(false);
let handleClick = e => {
e.persist();
setActive(true);
setTimeout(() => {
setActive(false);
}, 250);
if (typeof onClick === "function") onClick(e);
};
return (
<MuiButton
variant={finalVariant(variant)}
className={`${active ? "Mui-active" : ""} ${className}`}
classes={buttonClasses}
onClick={handleClick}
{...props}
/>
);
}
let containedStyle = color => ({
"&:active": {
backgroundColor: color.dark
},
"&.Mui-active": {
backgroundColor: color.dark
}
});
This is the memory leaks warning I get when I click a button inside either an Alert or Dialog component:
index.js:1437 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I've tried using useEffect as suggested by the warning to clear the active state but haven't had luck. Here is a demo of what happens when I use a custom button built with MUI that uses hooks to manage state when the button is used in a dialog or alert https://codesandbox.io/s/traffic-light-using-hooks-zpfrc?fontsize=14&hidenavigation=1&theme=dark
This happens because your handleClick function uses setTimeout:
let handleClick = e => {
e.persist();
setActive(true);
setTimeout(() => {
setActive(false);
}, 250);
if (typeof onClick === "function") onClick(e);
};
To update the state.
The component is getting unmounted by the parent component when onClick is called, but there is still a subscription (your timeout) kept alive.
This isn't really a big deal if it's a one-off event, like in this case. It's a warning, not an error. The main purpose of this warning is to let you know if you are keeping subscriptions or references around for a long time after something is unmounted.
There are a few work-arounds to get rid of the warning by setting a flag if the component is unmounted and, if the flag is set, not updating state, but that doesn't really solve the problem that there is a reference to a component kept around after it's unmounted.
A better way of resolving this problem would be to keep a reference to the timeout using React.useRef() and then clearing it in useEffect(), like this:
function Button({
classes,
className,
onClick,
...props
}) {
let [active, setActive] = useState(false);
+ const timeout = React.useRef(undefined);
+ React.useEffect(() => {
+ return () => {
+ if (timeout.current !== undefined) {
+ clearTimeout(timeout.current);
+ }
+ }
+ }, []);
let handleClick = e => {
e.persist();
setActive(true);
- setTimeout(() => {
+ timeout.current = setTimeout(() => {
setActive(false);
}, 250);
if (typeof onClick === "function") onClick(e);
};
return (
<MuiButton
variant={finalVariant(variant)}
className={`${active ? "Mui-active" : ""} ${className}`}
classes={buttonClasses}
onClick={handleClick}
{...props}
/>
);
}
This could be encapsulated in a hook like so:
function useSafeTimeout() {
const timeouts = React.useRef([])
React.useEffect(() => {
return () => {
timeouts.forEach(timeout => {
clearTimeout(timeout)
})
}
}, [])
return React.useCallback((fn, ms, ...args) => {
const cancel = setTimeout(fn, ms, ...args)
timeouts.current.push(cancel)
}, [])
}
And used in this manner:
function Button({
classes,
className,
onClick,
...props
}) {
let [active, setActive] = useState(false);
+ const setTimeout = useSafeTimeout();
let handleClick = e => {
e.persist();
setActive(true);
setTimeout(() => {
setActive(false);
}, 250);
if (typeof onClick === "function") onClick(e);
};
return (
<MuiButton
variant={finalVariant(variant)}
className={`${active ? "Mui-active" : ""} ${className}`}
classes={buttonClasses}
onClick={handleClick}
{...props}
/>
);
}
Here is my solution for the problem:
function Button({
classes,
className,
onClick,
...props
}) {
let [active, setActive] = useState(false);
let timeoutIds = useRef([]);
let registerTimeout = (f, ms) => {
let timeoutId = setTimeout(f, ms);
timeoutIds.current.push(timeoutId);
};
let handleClick = e => {
e.persist();
setActive(true);
if (typeof onClick === "function") onClick(e);
};
let cleanup = () => {
timeoutIds.current.forEach(clearTimeout);
};
useEffect(() => {
if (active === true) {
registerTimeout(() => setActive(false), 250);
}
return cleanup;
}, [active]);
return (
<MuiButton
variant={finalVariant(variant)}
className={`${active ? "Mui-active" : ""} ${className}`}
classes={buttonClasses}
onClick={handleClick}
{...props}
/>
);
}

Resources