How to test call back refs with Enzyme and Jest - reactjs

I want to test addEventListener/RemoveEventListener for an Element.
class Image extends PureComponent {
state = {
isLoaded: false,
};
componentDidMount() {
if (!this.props.src) return;
this.imageToLoad = document.createElement('img');
this.imageToLoad.addEventListener('load', this.loadImage);
this.imageToLoad.src = this.props.src;
}
componentWillUnmount() {
if (!this.imageToLoad) return;
this.imageToLoad.removeEventListener('load', this.loadImage);
}
loadImage = () => {
this.setState({ isLoaded: true }, () => {
this.myimg.src = this.imageToLoad.src;
});
}
render() {
const { className, src, alt } = this.props;
const { isLoaded } = this.state;
if (isLoaded === false || !src) return '';
return (
<img
className={className}
ref={img => (this.myimg = img)}
src={src}
alt={alt}
/>
);
}
}
Test code:-
it('should add event listener on mount', () => {
const elementMock = { addEventListener: jest.fn() };
jest.spyOn(Image.prototype, 'myimg').mockImplementation(() => elementMock);
expect(elementMock.addEventListener).toBeCalledWith('load', expect.any(Function), false);
});
But it is not working as expected.

Related

TypeError: (0 , _reactRouterDom.withRouter) is not a function----- Getting this error after updating the application to the latest React version

// App.js - WEB
/* eslint-disable no-undef */
import { hot } from 'react-hot-loader/root';
import React, { Component } from "react";
import { View, Platform, Dimensions, Linking, Text } from "react-native";
import { Routes } from "react-router-dom";
import { ModalContainer } from "react-router-modal";
import IdleTimer from "react-idle-timer";
import {
BroadcastChannel
} from 'broadcast-channel';
import { withRouter } from './utils/withRouterComponent';
const routeMap = {
...CommonRouteMap,
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "#F36414",
openmodel: false,
bootboxtext: "",
modalopenhide: false,
measure: getMeasure(this.getWidth()),
workingtime: true,
userIdle: false,
maintainanceText: APP.MAINTAINANCE_TIME,
linearColors: linearColors(),
isDuplicate: false
};
this.handleWindowPerformanceEvents();
// Store the previous pathname and search strings
this.currentPathname = null;
this.currentSearch = null;
this.workingTime = dayjs(new Date()).format("HH");
}
changeBackground = (color) => {
this.setState({ backgroundColor: color });
};
handleLayout = ({ nativeEvent }) => {
const { width } = nativeEvent.layout;
const measure = getMeasure(width);
this.setState(() => ({ measure }));
};
getWidth = () => {
const { width } = Dimensions.get("window");
return width;
};
handleWindowPerformanceEvents = async () => {
let ExternalFlag;
if (Platform.OS === "web") {
ExternalFlag = sessionStorage.getItem("ExternalFlag");
} else {
ExternalFlag = await AsyncStorage.getItem("ExternalFlag");
}
let parts = window.location.pathname.split("/");
let path = parts[parts.length - 1];
if (window.performance) {
if (
performance.navigation.type === 0 &&
!window.location.pathname != "404Page"
) {
localStorage.clear();
} else if (!window.location.pathname != "404Page") {
localStorage.clear();
const pathName = window.location.pathname;
const domainName = window.location.origin;
let updatedPath = pathName.substr(0, pathName.lastIndexOf("/"));
if (paths.includes(updatedPath)) {
if (ExternalFlag == 'false') {
window.location.href = `${domainName}${updatedPath}`;
}
return;
} else {
if (ExternalFlag == 'false') {
window.location.href = `${window.location.origin}/servicing/index.html`;
}
}
}
}
};
async componentDidMount() {
const bc = new BroadcastChannel("MY-Project");
bc.onmessage = (event) => {
if (event === "newTab") {
bc.postMessage(`duplicateTab`);
this.setState({ isDuplicate: false })
}
if (event === `duplicateTab`) {
bc.close()
}
};
bc.postMessage('newTab');
this.myContext = {
...this.myContext,
isSupported: await this.isSupportedForVKYC(),
};
const windowValues = window.location;
this.navigateOptions();
if (
windowValues.pathname.includes("product") &&
windowValues.search != ""
) {
fetchUrl(windowValues, this.props);
}
this.setState({
workingtime: true,
});
this.interval = setInterval(() => this.timer(windowValues), 5000);
let machine = null;
if (Platform.OS === 'web') {
machine = sessionStorage.getItem('machine');
}
if (machine) {
this.setState({ machine: true })
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
timer = (windowValues) => {
if (
windowValues.hostname != "localhost" &&
this.state.workingtime &&
!(this.workingTime >= 6 && this.workingTime < 23)
) {
this.setState({
workingtime: true,
});
this.interval = setInterval(() => this.timer(windowValues), 5000);
}
};
navigateOptions() {
const { history } = this.props;
if (
window.location.pathname ===
`${"/" + injectUrlPaths(window.location).path + "/"}`
) {
history.push(`${"/" + injectUrlPaths(window.location).path + ""}`);
}
}
refresh() {
let clear = [
"time",
"product_name",
"payment_gateway",
"stopwatch",
"credit_opted",
"flow",
"easyPay",
"token",
];
clear.forEach((item) => {
sessionStorage.removeItem(item);
});
}
handleKeyPress = (event) => {
if (this.state.userIdle && event.keyCode === 9) {
event.preventDefault();
}
};
closebootbox = () => {
if (this.state.userIdle) {
this.refresh();
window.removeEventListener("keydown", this.handleKeyPress);
Linking.openURL(window.location.origin);
} else {
this.setState({
openmodel: false,
modalopenhide: false,
});
}
};
onIdle = async () => {
const pathName = window.location.pathname;
window.addEventListener('keydown', this.handleKeyPress);
let ExternalFlag = (Platform.OS === "web") ? sessionStorage.getItem("ExternalFlag") : await AsyncStorage.getItem("ExternalFlag");
const { history } = this.props;
let newProps = this.props.location.state;
newProps = { ...newProps, idle: true };
let redirectPath = "/servicing/index.html";
let parts = window.location.pathname.split("/");
let path = parts[parts.length - 1];
if (ExternalFlag) {
history.push({
pathname: '/servicing/redirectHome',
state: newProps
});
} else {
let machine = null;
if (Platform.OS === 'web') {
machine = sessionStorage.getItem('machine');
}
if (machine) {
window.location.href = `http://localhost:9309/SendStatus?CurrentStatus=Timeout`;
} else {
history.push({
pathname: redirectPath,
state: newProps
});
}
}
};
setTimeout = data => {
if (data.otptimer) {
this.idleTimer.pause();
} else {
this.idleTimer.resume();
}
};
clearStorage = (data) => {
data.map((item) => {
sessionStorage.removeItem(item);
});
};
render() {
const ua = window.navigator.userAgent;
return (
!this.state.isDuplicate ?
<CacheBuster enableCaching>
{({ loading, isLatestVersion, refreshCacheAndReload }) => {
if (loading) return null;
if (!loading && !isLatestVersion) {
refreshCacheAndReload();
}
return (
browserSupport(ua) && (
<LinearGradient {...this.state.linearColors}>
<IdleTimer
ref={(ref) => {
this.idleTimer = ref;
}}
element={document}
onIdle={this.onIdle}
debounce={APP.DEBOUNCE}
timeout={APP.IDLE_TIMEOUT}
/>
<View onLayout={this.handleLayout} style={styles.container}>
<script>
window.onpopstate = function() {this.props.history.go(1)};
</script>
{this.state.workingtime ? (
<AppProvider value={{ ...this.myContext, ...this.state }}>
<View style={styles.rootStyle}>
<CustomBootbox
{...this.props}
measure={this.state.measure}
bootboxtext={this.state.bootboxtext}
bootboxmodal={this.state.openmodel}
closebootbox={this.closebootbox}
hideBlackout
/>
<Routes>
{WebRoutesGenerator(
{ routeMap },
this.changeBackground
)}
</Routes>
<ModalContainer />
</View>
</AppProvider>
) : (
<AvailableTimeError
maintainanceText={this.state.maintainanceText}
/>
)}
</View>
</LinearGradient>
)
);
}}
</CacheBuster> : <View style={{ height: Dimensions.get("window").height, }}>
<View >
<Text>Previous session is already open...!</Text>
<Text><br></br> Please close the window and try again </Text>
</View>
</View>
);
}
}
export default hot((withRouter((App))))
I have a react native project and it was running in the older version of React, React Dom and rest. After updating the application to match the latest version, I am getting the above error and I couldn't find a proper solution for it.
Initially I searched withRouter imports and uses, and then created a custom hook of withRouter. Still the error persists.
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
export function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
This issue is likely surfacing as you might be using --legacy-peer-deps flag when installing node_modules. Now Remove node_modules, and just try to fix code after doing npm install. have a nice day.

React: Play, pause onclick fuctionality

I am trying to implement play/pause on button clicking. While icons change perfectly, my actual functionality fails to work. My code is the following.
Function that is triggered on click
onPlayHandler = () => {
let aud = new Audio(this.props.data[this.state.index].audio);
aud.play();
this.setState({
isPlaying: true
});
};
onPauseHandler = () => {
let aud = new Audio(this.props.data[this.state.index].audio);
aud.pause()
//aud.setAttribute("ref", `${this.myRef}`);
this.setState({
isPlaying: false
});
//this.refs.audio.pause();
//x.pause();
//console.log(aud)
};
Button that is clicked
<Button playAudio={ this.state.isPlaying ? this.onPauseHandler : this.onPlayHandler } isPlaying={ this.state.isPlaying } />
Button component
export default function IconLabelButtons(props) {
const classes = useStyles();
return (
<>
<Button
variant="contained"
color="primary"
size="large"
className={ classes.button }
startIcon={ props.isPlaying ? <PauseIcon /> : <PlayArrowIcon /> }
onClick={ props.playAudio }
>
{ props.isPlaying ? 'Pause' : 'Play' }
</Button>
</>
);
Updated question
Entire code
import React, { Component } from 'react';
import Card from './Card';
import Button from './Button';
import correct from '../../data/media/correct.wav';
import denied from '../../data/media/denied.mp3';
var _ = require('lodash');
class AudioContainer extends Component {
constructor (props) {
super(props);
this.state = {
images: [],
index: 0,
checkedItems: new Map(),
correct: false,
isPlaying: false
};
this.audio = React.createRef();
}
componentDidMount() {
const arrImages = this.props.data.map((item) => {
let arr = [];
arr.push(item.picture);
return arr;
});
//console.log(arrImages)
this.setState({
images: _.shuffle(arrImages.flat(Infinity))
});
}
showImages = () => {
this.state.images && this.state.images.map((image) => (
<div>
<figure class="image is-128x128">
<img src={ image.src } alt="" />
</figure>
<span>{ image.name }</span>
</div>
));
};
handleChange = (e) => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({ checkedItems: prevState.checkedItems.set(item, isChecked) }));
};
onCheckHandler = (e) => {
e.preventDefault();
/* function checkTrue(item) {
return item.value === true;
}
const filteredItems = this.state.checkedItems.filter(checkTrue) */
//console.log(this.state.checkedItems.value())
let arr = [];
// eslint-disable-next-line no-unused-vars
for (let [key, value] of this.state.checkedItems) {
let obj = {
value: key,
bool: value
};
arr.push(obj);
}
//console.log(arr);
let filteredArr = arr.filter((el) => {
return el.bool === true;
});
//console.log(filteredArr);
let arrWithAnswers = [];
filteredArr.map((el) => {
return arrWithAnswers.push(el.value);
});
//console.log(arrWithAnswers);
//console.log(this.props.data[this.state.index].answers.sort());
if (_.isEqual(arrWithAnswers.sort(), this.props.data[this.state.index].answers.sort())) {
let sound = new Audio(correct);
sound.play();
this.setState({
index: this.state.index + 1, checkedItems: new Map()
});
//console.log("true");
} else {
let sound = new Audio(denied);
sound.play();
}
};
update() {
const arrImages = this.props.data.map((item) => {
let arr = [];
arr.push(item.picture);
return arr;
});
//console.log(arrImages)
this.setState({
images: _.shuffle(arrImages.flat(Infinity))
});
}
onPlayHandler = () => {
let aud = new Audio(this.props.data[this.state.index].audio);
aud.play();
this.setState({
isPlaying: true
});
};
onPauseHandler = () => {
let aud = new Audio(this.props.data[this.state.index].audio);
aud.pause()
//aud.setAttribute("ref", `${this.myRef}`);
this.setState({
isPlaying: false
});
//this.refs.audio.pause();
//x.pause();
//console.log(aud)
};
render() {
//console.log(this.props.data)
//console.log(this.state.images)
//console.log(this.state.checkedItems.size);
return (
<div>
<Button playAudio={ this.state.isPlaying ? this.onPauseHandler : this.onPlayHandler } isPlaying={ this.state.isPlaying } />
<div className="columns is-vcentered is-multiline">
{ this.state.images && this.state.images.map((image, index) => (
<div className="column is-3" key={index}>
<Card image={ image.src } clickHandler={ this.handleChange } name={ image.name } />
</div>
)) }
</div>
<button className="button is-info" disabled={ this.state.checkedItems.size <= 0 } onClick={ this.onCheckHandler }>Check</button>
</div>
);
}
}
export default AudioContainer;
Your help is appreciated, thanks.
I was able to get it the way I want to, but I am not sure if it is a good practice. So what I did is the following.
let audio = new Audio()
class AudioContainer extends Component {
constructor (props) {
super(props);
this.state = {
images: [],
index: 0,
checkedItems: new Map(),
correct: false,
isPlaying: false,
error: false
};
}
onPlayHandler = () => {
audio.src = this.props.data[this.state.index].audio
audio.play();
this.setState({
isPlaying: true
});
};
onPauseHandler = () => {
audio.src = this.props.data[this.state.index].audio
//let aud = new Audio(this.props.data[this.state.index].audio);
audio.pause();
//aud.setAttribute("ref", `${this.myRef}`);
this.setState({
isPlaying: false
});
//this.refs.audio.pause();
//x.pause();
//console.log(aud)
};
I am very suspicious of declaring variable audio at the very top. So please correct me if I am wrong.

Onchange Function for a range input

I'm using React.JS for building a range input slider.The value of min , max and value have been set by {this.renderMinTotal()} and {this.renderMaxTotal()}.The main problem is that when I try to toggle it it does not move at all.I know that I should use onChange handler but I do not know what to write.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
library: null,
};
}
componentDidMount() {
fetch("/json.bc", {
method: "get"
})
.then(response => response.text())
.then(text => {
let Maindata = JSON.parse(text.replace(/\'/g, '"'));
this.setState(
state => ({
...state,
data: Maindata
}),
() => {
this.reorganiseLibrary();
}
);
})
.catch(error => console.error(error));
}
reorganiseLibrary = () => {
const { data } = this.state;
let library = data;
library = _.chunk(library);
this.setState({
library,
});
};
handlePerPage = evt =>
this.setState(
{
perPage: evt.target.value
},
() => this.reorganiseLibrary()
);
renderMinTotal = () => {
const { library } = this.state;
if (!library || (library && library.length === 0)) {
return "";
}
return library.reduce((acc, lib) => {
const libMin = Math.min(...lib.map(item => item.totalCom));
return acc === undefined ? libMin : libMin < acc ? libMin : acc;
}, undefined);
};
renderMaxTotal = () => {
const { library } = this.state;
if (!library || (library && library.length === 0)) {
return "";
}
return library.reduce((acc, lib) => {
const libMax = Math.max(...lib.map(item => item.totalCom));
return libMax > acc ? libMax : acc;
}, 0);
};
handlerChange = evt =>{
let value = evt.target.value
///what should be written here///////
}
render() {
const { library } = this.state;
return (
<div>
<div>
<input
type="range"
min={this.renderMinTotal()}
max={this.renderMaxTotal()}
value={this.renderMaxTotal()}
step="1000"
className="multirange"
onChange={this.handlerChange}
/>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('Result'))
i don't see reason to load value of your range-input from
( this.renderMaxTotal() )
try just leaving value blank, later on try f.e.
onChange={(event)=>{console.log(event.target.value)}}

How to solve: Unable to find node on an unmounted component

I am facing this this error: Unable to find node on an unmounted component in my code.
This is my set of code:
class CalendarScheduler extends Component {
state = {
viewModel: schedulerData,
showBookingDialog: false
}
handleClickOpen = () => {
// this.setState({ showBookingDialog: true });
this.setState((prevState) => {
return { showBookingDialog: !prevState.showBookingDialog };
})
};
handleClose = () => {
this.setState({ showBookingDialog: false });
};
render() {
const { viewModel } = this.state;
let schedulerData = this.state.viewModel;
schedulerData.setResources(this.props.rooms);
return (
<div>
<Scheduler schedulerData={viewModel}
prevClick={this.prevClick}
nextClick={this.nextClick}
onSelectDate={this.onSelectDate}
newEvent={this.newEvent}
/>
{this.state.showBookingDialog === true && (
<BookingDialog
open={this.state.showBookingDialog}
onClose={this.handleClose} />
)}
</div>
);
}
This is my function:
newEvent = (schedulerData, slotId, slotName, start, end, type, item) => {
this.setState({
showBookingDialog: true
});
My error message is pointing to this.setState inside newEvent.

Recaptcha Missing required parameters: sitekey

I'm integrating recaptcha to my react app and I'm having this error: "Missing required parameters: sitekey" when I use the render method of grecaptch even though I have properly included the sitekey in to the parameters.
Here is my code for reference:
class ReCaptcha extends React.Component {
componentDidMount() {
this.callbackName = 'g-lf-callback';
window[this.callbackName] = this.props.callback;
if(window.grecaptcha) {
this.renderRecaptcha();
} else {
this.loadRecaptchaScript();
}
}
componentWillUnmount() {
delete window['rconload'];
delete window[this.callbackName];
}
loadRecaptchaScript() {
window['rconload'] = () => {
this.renderRecaptcha();
};
let url = 'https://www.google.com/recaptcha/api.js?onload=rconload&render=explicit';
loadScript(url, '', 'recaptcha', true, true)
.then( () => {})
.catch( () => {});
}
renderRecaptcha = () => {
if(this.container) {
let parameters = {
sitekey: process.env.MY_SITEKEY,
size: 'invisible',
badge: null,
callback: 'g-lf-callback'
};
const recaptchaId = window.grecaptcha.render(this.container, parameters);
this.execute = () => window.grecaptcha.execute(recaptchaId);
this.reset = () => window.grecaptcha.reset(recaptchaId);
this.getResponse = () => window.grecaptcha.getResponse(recaptchaId);
}
}
onClick = () => {
this.execute();
}
render() {
let { callback, label, className, ...btnProps } = this.props;
return (
<Button
{ ...btnProps }
className={ className }
onClick={ this.onClick }
ref = { ref => this.container = ref }
>
{ label }
</Button>
)
}
}
I have also set the render parameter of recaptcha url to explicit
https://www.google.com/recaptcha/api.js?onload=rconload&render=explicit
Onload callback:
window['rconload'] = () => {
this.renderRecaptcha();
};

Resources