How to test useState using enzyme/Jest - reactjs

const Avatar = (props) => {
const [hovered, setHovered] = useState('');
return (
<div className="user-avatar-wrapper">
<div
className="placeholder"
onClick={() => {
setHovered(true);
}}
/>
</div>
)
}
How do I test if hovered got changed after simulating click on ".placeholder"?

According to #forret and #colinux I should not test the state variation, but the reaction of that state change.
That was the full code:
const Avatar = (props) => {
const [hovered, setHovered] = useState('');
return (
<div className="user-avatar-wrapper">
<div
className="placeholder"
onClick={() => {
setHovered(true);
}}
/>
{hovered &&
<div className="my-hidden-element">
...something
</div>
}
</div>
)
}
Then instead of testing "hovered", I should test if .my-hidden-element is now being rendered.

Related

I want to pass data to parent component foom a child component using props

I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;

How to pass const useState("") between functions

I would like to pull the const ChatLog out of the main function Chats and insert it as a component or outside of the Chats function for now. The Problem is that the ChatLog needs the useState variables [msg, sendMsg] (..) that are called in the Chats function. How could I do this anyway? Am new to react.
function Chats() {
const [msg, sendMsg] = useState("");
const [msgs1, sendMsgAll] = useState([]);
useEffect(() => {
onValue(ref(database), (snapshot) => {
sendMsgAll([]);
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((msg) => {
sendMsgAll((oldArray) => [...oldArray, msg]);
});
}
});
}, [])
const ChatLog = () => {
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
return (
<div className="ChatView">
<p><ChatLog /></p>
<p>{ChatInput()}</p>
</div>
)
};
You can add props to ChatLog component. Check this...
const ChatLog = (props) => {
const {msg1} = props
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
However,you need add props to component when you use it. Something like this...
return (
<div className="ChatView">
<p><ChatLog msg1={msg1} /></p>
<p>{ChatInput()}</p>
</div>
)
One more thing, when you declaring a component, it has to be declared of another component. Like this
//this is ChatLog component
const ChatLog = (props)=>{
return <div/>
}
//this is Chats component
const Chats = ()=>{
return (
<div>
<ChatLog {...props}/>
</div>
)
}

Onclick function only works when I Click twice

I have this Little list that should show different modals based on what the user select , and the problem is When I select the first time it doesn't work and I have to click on it again to work .
Here is my code :
const Mylist = props => {
const [open2, setOpen2] = React.useState(false);
const [open3, setOpen3] = React.useState(false);
const handleClose2 = () => {
setOpen2(false);
};
const handleOpen2 = () => {
setOpen2(true);
};
const handleClose3 = () => {
setOpen3(false);
};
const handleOpen3 = () => {
setOpen3(true);
};
const [isActive , SetIsActive] = useState(false);
const option =["Paid" , "UnPaid"]
return (
<div>
<i class="fas fa-ellipsis-v" onClick={(e) => SetIsActive(!isActive)}></i>
{isActive && (
<div>
{option.map((option) => (
<div
onClick={(e) => {
SetIsActive(false);
{option == 'Paid' && setOpen2(true)}
{option == 'UnPaid' && setOpen3(true)}
}}
>
{option}
</div>
))}
<Modal onClose={handleClose2} open={open2} >
<div>
Content
</div>
</Modal>
<Modal onClose={handleClose3} open={open3} >
<div>
Content
</div>
</Modal>
Your code block is a bit messy with some brackets and closing tags missing (possibly some of the code was not copied in by mistake?).
In any case, I did my best at trying to fill in the missing parts and did some refactoring to your code. I hope that fixes your bug!
import React, { useState } from 'react';
const MyList = (props) => {
const [isOpen1, setIsOpen1] = useState(false);
const [isOpen2, setIsOpen2] = useState(false);
const [isActive, setIsActive] = useState(false);
const openOption = (option) => {
setIsActive(false);
if (option === "Paid") {
setIsOpen1(true);
}
if (option === "UnPaid") {
setIsOpen2(true);
}
};
const options =["Paid", "UnPaid"]
return (
<div>
<i class="fas fa-ellipsis-v" onClick={() => setIsActive(!isActive)}></i>
{isActive && (
<div>
{options.map((option) => (
<div onClick={() => openOption(option)}>
{option}
</div>
))}
</div>
)}
<Modal onClose={() => setIsOpen1(false)} open={isOpen1} >
<div>
Content
</div>
</Modal>
<Modal onClose={() => setIsOpen2(false)} open={isOpen2} >
<div>
Content
</div>
</Modal>
</div>
);
};

Page jumps when useState updates

I'm trying to build a clone of a MovieDataBase site in react, and when I update my state variable the page jumps ( it jumps up exactly 60px, for whatever reason, and then back down again each time I toggle the switch). I thought it was maybe because I'm interacting with the DOM to get the toggle switch to work, but that doesn't seem to be the issue. I've also been told that happens with styled components, which I don't think I have (still pretty new to REACT, so maybe???). Anyway - I can't figure out why this is happening. I've included the code for the component in question below.
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth + 1}px`
);
el.classList.add("selected");
};
const isMovieSelected = genre === "movie";
const isTvSelected = genre === "tv";
const movieData = "movie";
const tvData = "tv";
return (
<section className="container movie-list">
<div className="flex flex--align-center">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle
onChange={changeHandler}
selected={isMovieSelected}
data={movieData}
>
In Theaters
<div className="background"></div>
</Toggle>
<Toggle
onChange={changeHandler}
selected={isTvSelected}
data={tvData}
>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected, data } = props;
const className = selected ? "switch--option selected" : "switch--option";
return (
<div className={className}>
<h3>
<a data-genre={data} onClick={onChange} className="switch--anchor">
{children}
</a>
</h3>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path, id } = props;
return (
<div key={id} className="card">
<div className="image">
<img src={imageUri + "w500" + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

All other input data get clear when I remove an input

Hi i am working on a React application where there are four inputs.when a user add an input the element will be added to the wrapper.In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update added full component to question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rule_0,
rule_1: teamData.rule_1,
rule_2: teamData.rule_2,
rule_3: teamData.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(teamData.rules);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule-${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
console.log(updateTeamData);
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
<p className="team-agreement-rules-description">{description}</p>
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={`${data}-${idx}`}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
handleRemoveClick(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
type="text"
placeholder={`Rule ${idx + 1}`}
defaultValue={teamData.rules[idx]}
name={`rule_${idx}`}
onChange={handleChange}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;

Resources