I am facing issues when I try to run the test cases using jest.
Error:-
Unable to fire a "click" event - please provide a DOM element.
console:-
`If I try using queryByTestId it gives console as` :- "null"
`If I try using getElementsByClassName it gives console as`:- "HTMLCollection {}"
Code:-
TableHead Componet
const TableHead = ({
checkHeaderState,
headers,
isCheckBox,
selectAllChecked,
sortColumn,
}) => {
const [sortOrder, setSortOrder] = useState(COR_TABLE.DESCENDING);
const checkRef = useRef();
const handleSorterChange = (dataIndex, sortDirection) => {
const updatedsorterState = getsorterState(sortDirection);
setSortOrder(updatedsorterState);
sortColumn(dataIndex, updatedsorterState);
};
return (
<thead className={`${mainClass}__tableHead`}>
<tr className={`${mainClass}__headerRow`}>
{headers.map((item, i) => {
const { header, key, ref, style, sortable } = item;
return (
<Fragment key={i}>
<th ref={ref} className={`${mainClass}__sortable`} style={style}>
{isCheckBox && i === 0 && (
<input
id="selectAllCheckBox"
className={`${mainClass}__checkBox`}
type="checkbox"
onClick={selectAllChecked}
ref={checkRef}
/>
)}
<span>{header}</span>
{sortable && (
<Icon
id="sortIcon"
className={`${mainClass}__sort-icon`}
icon={SortIcon}
color={"midnightblue"}
height="16px"
onClick={() =>
handleSorterChange(key.toLowerCase(), sortOrder)
}
/>
)}
</th>
</Fragment>
);
})}
</tr>
</thead>
);
};
I have imported this component in my TableWrapper component.
Test file:-
it("Select all checkBox to be clicked", () => {
// const onChangeSpy = jest.spyOn(TableWrapper.prototype,"selectAllChecked")
const { container } = render(<TableWrapper {...props} />);
const checkBox = container.getElementsByClassName("oct-cors-TableWrapper__tableHead");
console.log("asdsa", checkBox)
// fireEvent.click(checkBox);
// expect(checkBox).toBeChecked()
const inputElement5 = container.find('.oct-cors-TableWrapper__tableHead .oct-cors-TableWrapper__headerRow .oct-cors-TableWrapper__sortable .oct-cors-TableWrapper__checkBox');
// let inputElement = container.getElementsByClassName(".oct-cors-TableWrapper__tableHead .oct-cors-TableWrapper__headerRow .oct-cors-TableWrapper__sortable .oct-cors-TableWrapper__checkBox")
// inputElement.simulate("click");
console.log("dsadaseee",inputElement);
// expect(inputElement).to.have.length(1);
});
Tried accessing the click on checkbox and sortIcon but was not able to reach
2nd option tried:-
const { queryByTestId } = render(<TableWrapper {...props} />);
const checkBox = queryByTestId("sortIcon");
console.log("asdsa", checkBox)
fireEvent.click(checkBox);
I am trying to click on checkBox and fire click event on sortIcon
TestFile Imports
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render, screen, fireEvent } from "#testing-library/react";
import TableWrapper from "./TableWrapper";
Not sure what I am doing wrong?
Can someone suggest me?
Thanks in advance
Related
In my app I want to make my element always scrolled to bottom after getting new logs.
For some reason my logsRef.current.scrollTop has value of zero all the time. My logs do show on screen and in console. I am not sure why is this not working, I've tried to use different approaches using useLyaoutEffect() but nothing made logsRef.current.scrollTop value change, it stayed zero all the time.
//my Logs.jsx component
import { useEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`}>
<div ref={logsRef}>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also, I do render my Logs.jsx in BattlePhase.jsx component where I do my attack logic on click and I save logs using useState() hook.
//parts where i do save my logs in BattlePhase.jsx
const [logs, setLogs] = useState([]);
const attackHandler = () => {
//logs where pokemon on left attacked pokemon on right
setLogs((prevLogs) => [
...prevLogs,
`${pokemonDataOne.name} attacked ${
pokemonDataTwo.name
} for ${attack.toFixed(2)} dmg`,
`${pokemonDataTwo.name} died`,
])
}
...
<Attack className={isActiveArrow}>
<Button onClick={attackHandler}>Attack!</Button>
</Attack>
Slight long shot but it's possible that the ref is attached to the wrong element. Are you sure the element with the CSS property that makes it scrollable (overflow) isn't on <Container>?
//my Logs.jsx component
import { useLayoutEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useLayoutEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`} ref={logsRef}>
<div>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also to confirm, you do need useLayoutEffect here.
The edited task reflects on browser only when I delete an existing task or add a new one.
The edited task is even reflected in the prompt as the pre-existing task, but the edited text is not reflected in the task.
import * as React from 'react';
import Card from 'react-bootstrap/Card';
import Add from './Add';
import List from './List';
import Table from 'react-bootstrap/Table';
const Main = () => {
const [listData, setListData] = React.useState([]);
const listDataMani = (text) => {
const listDataObj = {
id: listData.length + 1,
text: text,
}
const finalList = [...listData, listDataObj]
setListData(finalList);
}
const listDataDelete = (id) => {
const finalData = listData.filter(function (el) {
if (el.id === id) {
return false;
} else {
return true;
}
})
setListData(finalData);
}
const editTaskHandler = (t, li) => {
let compData = listData; // this is the function to update text
for (let i = 0; i < listData.length; i++) {
if (listData[i].id === li) {
listData[i].text = t;
} else {
return;
}
}
setListData(compData);
}
return (
<><div className='container'>
<div className='col-lg-12'>
<div className='main-component'>
<div className='title'>
<Card style={{ marginTop: "10em" }}>
<Card.Body>
<Card.Title>My Todo List</Card.Title>
<Card.Subtitle className="mb-2 text-muted">Manages Time</Card.Subtitle>
<Add listDataMani={listDataMani} />
<Table striped bordered hover>
<thead>
<tr>
<th>#</th>
<th>Task Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<List callback={listDataDelete} editTask={editTaskHandler} list={listData} />
</tbody>
</Table>
</Card.Body>
</Card>
</div>
</div>
</div>
</div></>
)
}
export default Main;
import * as React from 'react';
const List =(props)=>{
const deleteHandler =(id)=>{
props.callback(id);
}
const editRequestHandler =(data)=>{
let editedText = prompt("Edit Your Task", data.text);
props.editTask(editedText, data.id);
}
return (
<>
{props.list.map((el)=>(<tr>
<td>{el.id}</td>
<td>{el.text}</td>
<td>
<button onClick={function(){
deleteHandler(el.id)
}}>X</button>
<button onClick={()=>{editRequestHandler(el)}}>✍</button>
</td>
</tr>))}
</>
)
}
export default List;
The edited task reflects on browser only when I delete an existing task or add a new one.
The edited task is even reflected in the prompt as the pre-existing task, but the edited text is not reflected in the task.
You are modifying the internals of an object/array without changing its referencial identify.
setState operations only do anything if when React compares the old data to the new, it has changed. In the case of arrays and objects, they are compared by reference (as opposed to numbers, strings, and other primitives which are compared by value).
To set the state using a modified object, you need to reconstruct it into a new object.
Here is a demo of the issue: https://codesandbox.io/s/setstate-unchanged-h249v3?file=/src/App.js
Notice how one button prints to console, while the other doesn't.
You could try doing this:
const editTaskHandler = (t, li) => {
setListData(
listData.map((item) => {
if (item.id === li) {
return { ...item, text: t };
}
return item;
})
);
};
I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox
so I am trying to create add to faviourate button with icon.
so far I could make a logic if a user clicked the empty heart icon that it turns to be full heart icon and I was able to locate the item it was clicked on.
So far so good, my issue starts when products object recieves only the most recent item that is picked and loses the other items that are previously picked.
so for example If I want to click on 3 items to add them to faviourate, I see that the console.log(favProduct) only preserves the most recent item which is in my case number 3 and loses number 1 and 2.
My Question is How to get all Items I clicked on and not only the most recent one.
Edit This is where I get the product from, check the code below.
import React , { useState, useEffect } from 'react'
import { Row , Col } from 'react-bootstrap'
import Product from '../components/Product'
import axios from 'axios'
const HomeScreen = () =>{
const [products , setProducts] = useState([])
useEffect(()=>{
let componentMounted = true
const fetchProducts = async () =>{
const {data} = await axios.get('http://172.30.246.130:5000/api/products')
if( componentMounted){
setProducts(data)
}
}
fetchProducts()
return () =>{
componentMounted = false
}
},[])
console.log('products' , products)
return(
<>
<h2 className='my-3'>Latest Products</h2>
<Row>
{
products.map((product)=>(
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} rating = {product.rating} reviews={product.numReviews}/>
</Col>
))
}
</Row>
</>
)
}
export default HomeScreen
import React, { useState } from 'react'
const Fav = ({products}) => {
let [checked , setChecked] = useState(false)
let[favProduct ,setFavProduct] = useState([])
const toggle = ()=>{
(!checked) ? setChecked(true) : setChecked(false)
setFavProduct([...favProduct,products]) // problem is here
}
console.log(favProduct)
return (
<>
<span onClick={toggle}>
{
<i className={(checked) ? "fas fa-heart" : "far fa-heart"}></i>
}
</span>
</>
)
}
export default Fav
At the moment each of your Fav components looks like they're trying to manage the state for all of the favourites which is not a good idea.
The general idea is to make most UI components as dumb as possible (ie. just return the bare minimum given the props they're given). They can control their own state but usually you want to a parent to control the state by lifting state up, and have them pass down a handler that the dumb component can call when their listener is triggered.
In this example Fav accepts an id, a favoured, and a handleClick listener, and then just returns some JSX.
The parent component does all the state management.
const { useEffect, useState } = React;
// Accept some props
// Render the class based on the `favoured` prop
function Fav({ id, favoured, handleClick }) {
return (
<div className="icon">
<i
data-id={id}
className={favoured ? 'fa-solid fa-heart' : 'fa-regular fa-heart'}
onClick={handleClick}
> {id}</i>
</div>
);
}
function Example() {
// The parent component manages the state
const [ favourites, setFavourites ] = useState([]);
// When a favourite icon is clicked, get its id
// and if it's in the state, remove it, otherwise add it
function handleClick(e) {
const { id } = e.target.dataset;
const found = favourites.includes(id);
if (found) {
setFavourites(favourites.filter(fav => fav !== id));
} else {
setFavourites([...favourites, id]);
}
}
// In this example I'm using a loop to generate the
// the favourites, checking if the state includes the id
// (I have to coerce it to a string because that's what
// the data attributes return, and `i` will always be a number
// Pass down the handler that the favourite button
// will use to update the state
function getFavs() {
const favs = [];
for (let i = 0; i < 5; i++) {
const isFavoured = favourites.includes(i.toString());
const fav = (
<Fav
id={i}
favoured={isFavoured}
handleClick={handleClick}
/>
);
favs.push(fav);
}
return favs;
}
return (
<div>
{getFavs()}
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I execute a component and this component fills the value profilePicRef once. However, I only want to display the Upload button when profilePicRef.current.preview is also no longer zero. However, I always get the error message TypeError: Cannot read property 'preview' of undefined. My question is, how can I now say if it is undefined, then don't take it into account and if it is not zero show it.
<PhotoFileHandler ref={profilePicRef} />
{
profilePicRef.current.preview !== null &&
<button className="button is-primary is-outlined" type="butto"
onClick={() => { onClickUpload(profilePicRef);
setActiveModal(''); }}>
<i className="fas fa-file-image"></i> Upload</button>
}
PhotoFileHandler
import React, { useState, forwardRef, useImperativeHandle, } from "react";
function PhotoFileHandler(props, ref) {
const [picName, setPicName] = useState(null);
const [preview, setPreview] = useState(null);
const [isPreview, setIsPreview] = useState(true);
const fileSelectedHandler = (event) => {
....
setPicName(event.target.files[0].name);
setPreview(reader.result);
setIsPreview(true);
}
}
catch (err) {
}
};
useImperativeHandle(ref, () => ({
isPreview,
preview,
picName,
checkProfilPicture() {
if (!preview) {
setIsPreview(false);
return false;
}
else {
setIsPreview(true);
return true;
}
},
getImage() {
return preview
},
removePreview() {
setIsPreview(false)
setPreview(null);
setPicName(null);
}
}),
);
return (
<div>
<input class="file-input" type="file" name="resume" accept=".png,.jpg,.jpeg, .jfif"
onChange={fileSelectedHandler} />
</div>
);
};
// eslint-disable-next-line
PhotoFileHandler = forwardRef(PhotoFileHandler);
export default PhotoFileHandler;
Is important to do with ref?
Alternative 0: Without Ref, Parent State
Alternative with state, you can see how it works here: https://codesandbox.io/s/admiring-gates-ctw6m?file=/src/App.js) :
Include one variable "file" const [selectedFile, setSelectedFile] = React.useState(null)
Send the setter function to PhotoFileHandler, I did something like: <PhotoFileHandler onImage={setSelectedFile} />
In PhotoFileHandler I did:
const fileSelectedHandler = (event) => {
props.onImage(event.target.files[0]);
}
Alternative 1: Force update with Parent State
If you need the ref, one workaround can be the trigger it when change it, like: https://codesandbox.io/s/sleepy-butterfly-iqmw3?file=/src/App.js,
Define in your parent component one state: const [wasUpdated, setWasUpdated] = React.useState("");
Include this in your validation to show the preview and upload button:
profilePicRef.current &&
profilePicRef.current.preview !== null &&
wasUpdated && (
<AllHtml />
)
)
Send the setter to the children <PhotoFileHandler ref={profilePicRef} onChange={setWasUpdated} />
In the children component you can trigger the event after updating preview.
useEffect(() => {
props.onChange(preview);
}, [preview]);