How to fire onChange event and get the result JestJS - reactjs

I have an input in one of my classes which onChange updates some of the properties, according to what the user typed. So I want to call that input, give it a value, then it should go through the onChange method and then get the result from one of the properties. Here is my test case
it("test-input-value-1", async () => {
const { getByTestId } = render(
<>
<Home />
<Try typeracertext="dsa dsa"/>
</>
);
const input = getByTestId("add-word-input");
const inputWord = "For";
userEvent.type(input, 'For')
const userText = await getByTestId("userText");
const typeracertext = getByTestId("typeracertext");
await expect(userText.innerHTML).toBe(inputWord);
});
and here is what I got
I don't have an idea why the result is empty when it has to be changed into the same word "For" that the input has.
EDIT: Here is the JSX Code as requested
Home.js:
const Game = () => {
if (cantType === false) {
return (
<Try
typeracertext={typeracertext}
setWholeText={setWholeText}
setStartTyping={setStartTyping}
setEndTyping={setEndTyping}
setCountWords={setCountWords}
newGame={newGame}
/>
)
}
else {
return (
<input readOnly />
)
}
}
return (
<span data-testid="userText" className="userTextHome">{wholeText}</span><div data-testid="typeracertext">
</div>
<div data-testid="add-word-input2" className="box d">
{Game()}
</div>
...
Try.js:
//here is also the onChange method but it is not needed in this case as it is very long and I have explained what it does in the end (make a property to be equal to the input data)
return (
<div data-testid="add-word-input"><input name="add-word-input" placeholder="Message..." onChange={onChange}></input> </div>
);

You are executing user event on the div element, instead of input element.
Try moving attribute data-testid to input element in Try.js file:
return (
<div><input data-testid="add-word-input" name="add-word-input" placeholder="Message..." onChange={onChange}></input> </div>
);
Working example: Codesandbox
Input.js:
import { useState } from "react";
export const Input = () => {
const [text, setText] = useState();
const handleChange = (e) => {
setText(e.target.value);
};
return (
<>
<div>
<input
data-testid="add-word-input"
name="add-word-input"
placeholder="Message..."
onChange={handleChange}
></input>
</div>
<label data-testId="test-label">{text}</label>
</>
);
};
Input.test.js:
import { render } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import "#testing-library/jest-dom";
import { Input } from "./Input";
describe("Input", () => {
it("should change", async () => {
const { getByTestId } = render(<Input />);
let input = getByTestId("add-word-input");
expect(input).not.toBe(null);
await userEvent.type(input, "for");
let label = getByTestId("test-label");
expect(label.textContent).toBe("for");
});
});

Related

refactor debounce function to useDeferredValue hook (React)

In my application I use Redux to manage its state.
The task is when user types query in search panel application dispatches an action with payload and then request goes to API. I want to delay dispatch of an action, so when user types query the request only sends after user stops typing.
I implemented it with debounce function but kinda want to refactor in with useDeferredValue.
And that's when I found it difficult to implement this functional.
import { useState, useMemo } from 'react';
import { FormRow, FormRowSelect } from '.';
import Wrapper from '../assets/wrappers/SearchContainer';
import { useSelector, useDispatch } from 'react-redux';
import { handleChange, clearFilters } from '../features/allJobs/allJobsSlice';
export default function SearchContainer() {
const { isLoading, search, searchStatus, searchType, sort, sortOptions } =
useSelector((store) => store.allJobs);
const { jobTypeOptions, statusOptions } = useSelector((store) => store.job);
const dispatch = useDispatch();
const [localSearch, setLocalSearch] = useState('');
function onHandleSearch(e) {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}
function omHandleSubmit(e) {
e.preventDefault();
dispatch(clearFilters());
}
const debounce = () => {
let timeoutID;
return (e) => {
setLocalSearch(e.target.value);
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}, 1000);
};
};
const optimizedDebounce = useMemo(() => debounce(), []);
return (
<Wrapper>
<form className='form'>
<h4>search form</h4>
<div className='form-center'>
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={optimizedDebounce}
/>
<FormRowSelect
labelText='status'
name='searchStatus'
value={searchStatus}
handleChange={onHandleSearch}
list={['all', ...statusOptions]}
/>
<FormRowSelect
labelText='type'
name='searchType'
value={searchType}
handleChange={onHandleSearch}
list={['all', ...jobTypeOptions]}
/>
<FormRowSelect
name='sort'
value={sort}
handleChange={onHandleSearch}
list={sortOptions}
/>
<button
className='btn btn-block btn-danger'
disabled={isLoading}
onClick={omHandleSubmit}
>
clear filters
</button>
</div>
</form>
</Wrapper>
);
}
From the react website this is how it is done:
function App() {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, { timeoutMs: 2000 });
return (
<div className="App">
{/* Continue to give the current text to the input */}
<input value={text} onChange={handleChange} />
...
{/* But the list of results is allowed to be "late" in case it is not load yet */}
<MySlowList text={deferredText} />
</div>
);
}
so in your case this might be this:
import { useDeferredValue } from 'react';
export default function SearchContainer() {
const [localSearch, setLocalSearch] = useState('');
const deferredValue = useDeferredValue(localSearch, { timeoutMs: 1000 });
...
return (
...
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={e => setLocalSearch(e.target.value)}
/>
);
}

How to change specific item on state of array in React?

I am creating a todolist with react and context API. As a default, when item is created "isDone" key of array item is false. When I click the completeAll button, I want to make all task's "isDone" true.
import './FormInput.scss';
import List from '../List/List';
import Footer from '../Footer/Footer';
import {MainContext, useContext} from "../../context";
function FormInput() {
const {taskList, SetTaskList} = useContext(MainContext);
const submitTask = (e) => {
e.preventDefault();
SetTaskList((prev) => [...prev,{"task":e.target.task.value,"isDone":false}])
console.log(e.target.task.value);
}
const CompleteAll = (e) =>{
SetTaskList((prev) => {
const list = prev.map((item) => item.isDone===true)
return{
list
}
})
}
return (
<div className="form-input">
<h1>TODOS</h1>
<div className="form-top">
<button id="completeAll" onClick = { e => CompleteAll(e)}>❯</button>
<form onSubmit = {(e) => submitTask(e)}>
<input type="text" name="task" id="taskInfo" placeholder="What needs to be done?"/>
</form>
</div>
<List/>
{ taskList[0] ? <Footer/> : ""}
</div>
);
}
export default FormInput;
Here is the code. I try to code completeAll function but it set the tasklist to a single true, false value.
You can use spread operator to do it.
const CompleteAll = (e) => {
SetTaskList((prev) => {
return prev.map((item) => ({ ...item, isDone: true }));
});
};

get value from input on button click and enter key press react

I have a react component
import React from 'react';
import classes from './Input.module.css';
import PlayCircleFilledWhiteIcon from '#material-ui/icons/PlayCircleFilledWhite';
export const Input = ({ trackHandler }) => {
const handleTrack = (e) => {
if(e.key === 'Enter') {
trackHandler(e.target.value);
e.target.value = '';
}
}
return (
<>
<div className = {classes.forma}>
<input
type="text"
maxLength = '30'
placeholder = 'Enter tracker name'
onKeyPress = {e => handleTrack(e)}
className = {classes.inputText}
/>
<PlayCircleFilledWhiteIcon className = {classes.btnSubmit}/>
</div>
</>
)
}
Function trackHandler pass the value from input to another component.
I need to pass this value in two ways: by press key 'Enter' on keyboard or click on button. I've realised first way but I need to create both of them.
Thanks in advance for helping.
You can do something like this. Use useState hook to store the input value and create a common function which will be called on button click and on enter key press.
import React, { useState } from "react";
import "./style.css";
const Input = ({}) => {
const [val, setVal] = useState("");
const handleTrack = () => {
if (val.length !== 0) {
// Do something with value
console.log("got this:", val);
}
};
const handleKeyPress = e => {
if (e.key === "Enter") {
handleTrack();
}
};
return (
<div>
<input
value={val}
onChange={e => {
setVal(e.target.value);
}}
onKeyPress={handleKeyPress}
/>
<button
onClick={() => {
handleTrack();
}}
>
Click
</button>
</div>
);
};
export default function App() {
return (
<div>
<Input />
</div>
);
}
Stackblitz: https://stackblitz.com/edit/react-vane9t
You can use useRef as ref property on input.
const inputRef = useRef(null)
Then you get access to input value something like this:
inputRef.target.value
If this not work for first you should log the inputRef to the console which is the exact property what you need.

How do I edit form data in a React function component?

I'm trying to set a form field value with useState.
The settings.values.apiKey variable has a value, but the textarea element is empty. What's wrong with my useState?
I tried to change value={apiKey} to value={settings.values.apiKey} and then the value is displayed, but then I can't change the value of the field. When I try to enter something, it always shows the original value.
App.js
const App = () => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
useEffect(() => {
const getSettings = async () => {
const settingsFromServer = await fetchSettings()
setSettings(settingsFromServer)
}
getSettings()
}, [])
const fetchSettings = async () => {
const res = await fetch('http://127.0.0.1/react-server/get.php')
return await res.json()
}
const saveSettings = async (settings) => {
}
return (
<div className="container">
<Header />
<Settings
settings={settings}
saveSettings={saveSettings}
/>
<Footer />
</div>
);
}
export default App;
Settings.js:
import { useState } from 'react';
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
const onSubmit = (e) => {
e.preventDefault()
saveSettings({ apiKey})
}
return (
<div>
<form className='add-form' onSubmit={onSubmit}>
<div className='form-control'>
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type='submit' value='Save settings' className='mt15' />
</form>
</div>
)
}
export default Settings
It looks like by mistake you have used apiKey in App.js file as your state variable. It should be replaced by settings.
const [settings, setSettings] = React.useState();
The above code would make value={apiKey} work properly for textarea in Settings.js file.
And, then onChange will also start working properly.
UPDATE
In addition to the above mentioned error, in case settings props is undefined in Settings.js, this might cause your code to break at useState. So, instead put a check for settings values in useEffect and then set the value. The code would look like this or you can check the codesandbox link here for working demo.
Settings.js
import { useEffect, useState } from "react";
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState();
useEffect(() => {
if (settings?.values?.apiKey) {
setApiKey(settings.values.apiKey);
}
}, [settings]);
const onSubmit = (e) => {
e.preventDefault();
saveSettings({ apiKey });
};
return (
<div>
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type="submit" value="Save settings" className="mt15" />
</form>
</div>
);
};
export default Settings;
App.js
const [settings, setSettings] = useState()
const saveSettings = async (settings) => {
setSettings(settings);
}

How do I get the text value of an input using only function components in React?

import Layout from "components/Layout"
import { useState } from "react";
export async function getServerSideProps(context) {
const res = await fetch(`${process.env.NEXT_API_URL}/kana-terms/all`)
const data = await res.json()
return {props: {data}}
}
function checkAnswer(event) {
if (event.key === "Enter") {
console.log("Enter key was pressed");
}
}
export default function Hiragana(props) {
const [remainingTerms, setRemainingTerms] = useState(props.data);
return (
<Layout>
<h1>Hiragana</h1>
<div className="bg-light border w-100">
<h2>{remainingTerms[0].hiraganaText}</h2>
<input type="text" onKeyUp={(event) => {checkAnswer(event, )}} />
</div>
</Layout>
)
}
I want to pass the text value of the <input> element to the checkAnswer() function.
How do I do that in React using only function components?
All the answers I can find through Google use class components.
I'm also using Next.js... if that matters.
Put the input value into state, then pass the stateful value into the checkAnswer call:
const [value, setValue] = useState('');
and
<input
type="text"
value={value}
onChange={e => { setValue(e.currentTarget.value); }}
onKeyUp={(event) => {checkAnswer(event, value)}}
/>

Resources