How to test arrow function calling in react component with jest? - javascript

im newbie in jest/enzyme testing with react and im trying to test react Button Component by passing props and im getting this error Cannot read property 'onIncrement' of undefined.
describe("Name of the group", () => {
const counter = 'pc';
const onIncrement = jest.fn();
const props = {
onIncrement,
counter
};
it("should be clicked ", () => {
const button = shallow(<Button {...{props}}>Increment</Button>);
button.find(".increment").simulate("click");
expect(button).toHaveBeenCalledWith('pc');
});
});
import React from "react";
export const Button = ( props ) => {
return (
<button
id="inc"
className="increment"
onClick={() => props.onIncrement(props.counter)}
>
Increment
</button>
);
};

You need to change this:
export const Button = ({ props }) => {} // component has a property called props
to this:
export const Button = (props) => {}
Otherwise:
// it is not recommended
const button = shallow(<Button {...{props}}>Increment</Button>);
Edit:
This should works:
export const Button = (props) => {
return (
<button
id="inc"
className="increment"
onClick={() => props.onIncrement(props.counter)}
>
Increment
</button>
);
};
describe("Name of the group", () => {
const counter = "pc";
const props = {
onIncrement: jest.fn(),
counter,
};
it("should ", () => {
const button = shallow(<Button {...props}>Increment</Button>);
button.find(".increment").simulate("click");
expect(props.onIncrement).toHaveBeenCalledWith("pc");
});
});

Related

Passing the callback function

I have a Modelmenu that is nested in the parent component, it implements the function of opening a modal window by click. Also in this parent component there is a child component to which you need to bind the function of opening a modal window by one more element. I can do this if all the logic is in the parent component, but this is a bad practice, since I will have to duplicate the code on each page in this way. How can I do this? I'm sorry, I'm quite new, I can't understand the callback.
Parent:
const Home: NextPage = () => {
const handleCallback = (handleOpen: any) => {
}
return (
<>
<ModalMenu parentCallback={handleCallback}/>
<Slider handleCallback={handleCallback}/>
</>
)
}
Modal:
export const ModalMenu: FC = (props) => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
props.parentCallback(setPlay(!play))
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return
}
Child:
export const Slider: FC= (props) => {
return (
<>
<Image nClick={props.handleCallback}/>
</>
I did as advised in the comments using hook, it works fine, maybe it will be useful to someone. Custom hook is really convenient
export const useModal = () => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
setPlay(!play)
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return {
play, handleOpen, handleClose
}
}
If you're looking to pass down values and/or functions from a parent to two or more children, I think it's better to just have the values and functions in the parent
Parent :
const Home: NextPage = () => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
setPlay(prev => !prev)
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return (
<>
<ModalMenu handleOpen={handleOpen} handleClose={handleClose} play={play}/>
<MainSlider <ModalMenu handleOpen={handleOpen} handleClose={handleClose} play={play}/>
</>
)
}
if you want to pass an interface to the props in the children for typescript your interface will look something like this
interface iProps {
play : boolean;
handleOpen : () => void;
handleClose : () => void;
}
export const ModalMenu: FC = (props):iProps => {
// you can easily access all you want
const {handleClose, handleOpen, play} = props
return
}
export const Slider: FC= (props): iProps => {
const {handleClose, handleOpen, play} = props
return (
<>
<Image onClick={handleOpen}/>
</>

Is it possible to expose a function defined within a React function component to be called in other components?

I'm refactoring some old code for an alert widget and am abstracting it into its own component that uses DOM portals and conditional rendering. I want to keep as much of the work inside of this component as I possibly can, so ideally I'd love to be able to expose the Alert component itself as well as a function defined inside of that component triggers the render state and style animations so that no outside state management is required. Something like this is what I'm looking to do:
import Alert, { renderAlert } from '../Alert'
const CopyButton = () => (
<>
<Alert text="Text copied!" />
<button onClick={() => renderAlert()}>Copy Your Text</button>
</>
)
Here's what I currently have for the Alert component - right now it takes in a state variable from outside that just flips when the button is clicked and triggers the useEffect inside of the Alert to trigger the renderAlert function. I'd love to just expose renderAlert directly from the component so I can call it without the additional state variable like above.
const Alert = ({ label, color, stateTrigger }) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger])
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
};
export default Alert;
Though it's not common to "reach" into other components and invoke functions, React does allow a "backdoor" to do so.
useImperativeHandle
React.forwardRef
The idea is to expose out the renderAlert function imperatively via the React ref system.
Example:
import { forwardRef, useImperativeHandle } from 'react';
const Alert = forwardRef(({ label, color, stateTrigger }, ref) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger]);
useImperativeHandle(ref, () => ({
renderAlert,
}));
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
});
export default Alert;
...
import { useRef } from 'react';
import Alert from '../Alert'
const CopyButton = () => {
const ref = useRef();
const clickHandler = () => {
ref.current?.renderAlert();
};
return (
<>
<Alert ref={ref} text="Text copied!" />
<button onClick={clickHandler}>Copy Your Text</button>
</>
)
};
A more React-way to accomplish this might be to abstract the Alert state into an AlertProvider that renders the portal and handles the rendering of the alert and provides the renderAlert function via the context.
Example:
import { createContext, useContext, useState } from "react";
interface I_Alert {
renderAlert: (text: string) => void;
}
const AlertContext = createContext<I_Alert>({
renderAlert: () => {}
});
const useAlert = () => useContext(AlertContext);
const AlertProvider = ({ children }: { children: React.ReactElement }) => {
const [text, setText] = useState<string>("");
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
...
const renderAlert = (text: string): void => {
setAlertRendered(false);
setAlertVisible(false);
setText(text);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
const ele = <div>{alertRendered && <div> ..... </div>}</div>;
return (
<AlertContext.Provider value={{ renderAlert }}>
{children}
// ... portal ...
</AlertContext.Provider>
);
};
...
const CopyButton = () => {
const { renderAlert } = useAlert();
const clickHandler = () => {
renderAlert("Text copied!");
};
return (
<>
<button onClick={clickHandler}>Copy Your Text</button>
</>
);
};
...
function App() {
return (
<AlertProvider>
...
<div className="App">
...
<CopyButton />
...
</div>
...
</AlertProvider>
);
}

ReactDOM.render is not rendering element created through React.createElement

I created a modal component that utilizes React's context, and hooks for easy usage. But for some reason, the element that I want to render in the modal doesn't appear.
Here's my code for the ModalContainer which contains the provider and the modal
ModalContainer.tsx
export type ModalElement =
| React.ComponentClass<any, any>
| React.FC<any>
| JSX.Element;
const ModalContainer: React.FC = ({ children }) => {
const [visible, setVisible] = useState(false);
const modal = useRef<HTMLDivElement>(null);
const showModal = (element: ModalElement) => {
if (element && modal.current) {
if (React.isValidElement(element)) {
ReactDOM.render(element, modal.current);
} else {
const el = React.createElement(
element as React.ComponentClass<any, any> | React.FC<any>,
{}
);
ReactDOM.render(el, modal.current);
}
setVisible(true);
}
};
const dismissModal = () => {
setVisible(false);
};
return (
<ModalContext.Provider value={{ showModal, dismissModal }}>
{children}
<Modal visible={visible} onClose={dismissModal} ref={modal} />
</ModalContext.Provider>
);
};
export default ModalContainer;
useModal.ts
export const useModal = (element: ModalElement) => {
const context = useContext(ModalContext);
if (!context) {
Error(`Can't get modal context`);
}
const show = useCallback(() => {
context.showModal(element);
}, [context, element]);
const dismiss = useCallback(() => {
context.dismissModal();
}, [context]);
return [show, dismiss];
};
In a way that you'll use it as such:
const DivModal: React.FC = () => {
return <div>Test</div>
}
const Component: React.FC = () => {
const [show, dismiss] = useModal(DivModal);
const onClick = () => {
show();
}
return <button onClick={onClick}/>
}
The problem seems to happen when I create an element using React.createElement in the ModalContainer.tsx found specifically in this line:
const el = React.createElement(
element as React.ComponentClass<any, any> | React.FC<any>,
{}
);
ReactDOM.render(el, modal.current);
The modal renders properly if I replace el in the render function with a test JSX such as <div>test</div>. Can't really figure out what's wrong so I'll post it here just in case anyone knows what the problem is. Thanks!

How can I call a function from another component react

I am trying to call a function from a different component but when I console.log('hi') it appear but it didn't call the messageContext.
Here is my follwing code from Invitees.js:
const [showPreview, setShowPreview] = useState(false);
const toggleUserPreview = () => {
setShowPreview(!showPreview);
};
{showPreview && (
<ResultsWrappers togglePreview={toggleUserPreview}>
<UserPreview
userInfo={applicant}
skillStr={applicant.Skills}
togglePreview={toggleUserPreview}
/>
</ResultsWrappers>
)}
Here is the component have the function I want to call UserPreview.js:
import { useMessageContextProvider } from "../context/MessageContext";
const UserPreview = ({ userInfo, skillStr, togglePreview }) => {
const messageContextProvider = useMessageContextProvider();
const messageUser = () => {
togglePreview();
messageContextProvider.updateActiveUserToMessage(userInfo);
console.log('hi');
};
...
};
Here is my messageContext:
import { createContext, useContext, useState } from "react";
const messageContext = createContext();
export const MessageContextProvider = ({ children }) => {
const [activeUserToMessage, setActiveUserToMessage] = useState({});
const [isOpenMobileChat, toggleMobileChat] = useState(false);
const updateActiveUserToMessage = (user) => {
setActiveUserToMessage(user);
};
return (
<messageContext.Provider
value={{
updateActiveUserToMessage,
activeUserToMessage,
isOpenMobileChat,
toggleMobileChat,
}}
>
{children}
</messageContext.Provider>
);
};
export const useMessageContextProvider = () => {
return useContext(messageContext);
};
When the messageContext called it should open the chatbox like this:
The code you showing is not enough to say it for 100%, but it seems like toggleUserPreview - function called twice, so it reverted to original boolean value.
One time as <ResultsWrappers togglePreview={toggleUserPreview}/>
and second time as <UserPreview togglePreview={toggleUserPreview}/>.

"React" how to pass event to adjacent component

I need to make a click on the button in one component and on this click call a function in the adjacent one. What's the easiest way?
I implemented like this. https://stackblitz.com/edit/react-l5beyi But I think you can do it much easier. React is new to me, and this construction looks strange ...
const App = () => {
const [isAdded, setIsAdded] = useState(false);
function handleClick(status) {
setIsAdded(status)
}
return (
<div>
<ComponentFirst
HandleClick={handleClick}
/>
<ComponentSecond
isAdded={isAdded}
handleCreate={handleClick}
/>
</div>
);
}
const ComponentFirst = ({ HandleClick }) => {
return (
<button
onClick={HandleClick}
>button</button>
)
}
const ComponentSecond = (props) => {
let { isAdded, handleCreate } = props;
const result = () => {
alert('work')
console.log('work')
}
React.useEffect(() => {
if (isAdded) {
result()
handleCreate(false);
}
}, [isAdded, handleCreate]);
return (
<></>
)
}
In your (contrived, I suppose) example the second component doesn't render anything, so it doesn't exist. The work should be done by the parent component:
const App = () => {
const handleClick = React.useCallback((status) => {
alert(`work ${status}`);
// or maybe trigger some asynchronous work?
}, []);
return (
<div>
<ComponentFirst handleClick={handleClick} />
</div>
);
};
const ComponentFirst = ({ handleClick }) => {
return <button onClick={() => handleClick("First!")}>button</button>;
};
You can also use a CustomEvent to which any component can listen to.
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
return (
<div>
<ComponentFirst />
<ComponentSecond />
</div>
);
}
const ComponentFirst = () => {
const handleClick = (e) => {
// Create the event.
const event = new CustomEvent("myCustomEventName", {
detail: "Some information"
});
// target can be any Element or other EventTarget.
window.dispatchEvent(event);
};
return <button onClick={handleClick}>button</button>;
};
const ComponentSecond = (props) => {
function eventHandler(e) {
console.log("Dispatched Detail: " + e.detail);
}
//Listen for this event on the window object
useEffect(() => {
window.addEventListener("myCustomEventName", eventHandler);
return () => {
window.removeEventListener("myCustomEventName", eventHandler);
};
});
return <></>;
};

Categories