Writing tests to a component using `react-hook-form` library - javascript

I have a component with react-hook-form library. This is the component:
export const SetNewPassword = () => {
// ... code
return (
<div className="section">
<div className="container">
<div className="row">
<div className="col-md-6 col-md-offset-3 text-center" style={{ textAlign: 'left' }}>
<form onSubmit={handleSubmit(onSubmit)}>
<Input
titleTranslationKey="profile.register.password"
placeholderTranslationKey="client.newPassword"
name="password"
type="password"
register={register({
required: true,
validate: {
pattern: value => passwordValid(value, email),
differsFromEmail: value => passwordDiffersFromEmail(value, email),
},
})}
validationError={errors.password}
/>
<Input
titleTranslationKey="profile.register.passwordRetype"
placeholderTranslationKey="client.newPasswordRepeat"
name="passwordRetype"
type="password"
register={register({
required: true,
validate: {
passwordRetype: value => passwordRetypeValid(value, getValues().password),
},
})}
validationError={errors.passwordRetype}
/>
<div ref={captchaRef} className="custom-checkbox" aria-live="assertive">
<div className={hasErrorClassAssigner('form-group', state.captchaError)}>
<div className="input-group">
<ReCaptcha
languageCode={langCode}
onSuccess={onCaptchaSuccess}
onExpired={onCaptchaExpired}
/>
{state.captchaError && (
<small className="help-block">
{t('client.validator.required', {
first: t('client.recaptcha'),
})}
</small>
)}
</div>
</div>
</div>
<StandardButton
className="btn main"
text={<I18nText translationKey={'profile.edit.password.set.submit'} />}
/>
</form>
</div>
</div>
</div>
</div>
);
};
Here's the <Input/> component
export const Input = ({
// ...props
}) => (
<div className={hasErrorClassAssigner('form-group', validationError)} aria-live="assertive">
<I18nText
isA="label"
className={`control-label ${!isLabelVisible && 'hidden'}`}
translationKey={titleTranslationKey}
isRtl={isRtl}
/>
<input
className="form-control"
{...{ name, type }}
placeholder={t(placeholderTranslationKey)}
ref={register}
/>
{validationError && (
<small className="help-block">
{getTranslatedValidationMessage(name, titleTranslationKey, validationError.type)}
</small>
)}
</div>
);
And here are my tests for SetNewPassword
describe('<SetNewPassword/>', () => {
const element = (
<I18nProvider i18n={fakeI18n}>
<MemoryRouter>
<SetNewPassword />
</MemoryRouter>
</I18nProvider>
);
describe('Fetching', () => {
it('should fill <Input/> fields', async () => {
const { getByPlaceholderText, container } = render(element);
// const wrapper = mount(element);
console.log(getByPlaceholderText(/New password/i).value);
await act(async () => {
fireEvent.change(getByPlaceholderText(/New password/i), {
target: { value: 'zaq1#WSX' },
});
fireEvent.submit(container.querySelector('form'));
});
console.log(getByPlaceholderText(/New password/i).value);
console.log(container.querySelector('form'));
expect(Array.from(container.querySelector('.form-control'))[0].innerText).toEqual('zaq1#WSX');
});
// it('should fetch after <StandardButton/> is pressed', () => {});
});
});
What I want to do, is test if the <Input/>'s value is being updated. But I have no idea how to test it. I tried wrapper.find('input').at(0).value, and value() and getValue(), and every other possibility I could come up with. The most irritating part is that when I check it with the browser console with $0.value works. It displays the content of the <input/> field.
How can I test the value here?

Please read this one
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
querySelector
returns only one element and you are referring to it as it is an array,
so simply change to this should solve it:
expect(container.querySelector('input.form-control').value).toEqual('email#test.com');
Bare in mind that you have two inputs

Related

Make div with react components as children disappear if I click outside of it

I want to make a form that disappears if I click outside of it.
Form Component:
const CreateTaskPopup = (props) => {
const ref = query(collection(db, "tasks"));
const mutation = useFirestoreCollectionMutation(ref);
useEffect(() => {
const closeTaskPopup = (event) => {
if (event.target.id != "addForm") {
props.setTrigger(false)
}
}
document.addEventListener('click', closeTaskPopup);
return () => document.removeEventListener('click', closeTaskPopup)
}, [])
return (props.trigger) ? (
<>
<div className="bg-darkishGrey my-4 p-1 mx-3 ml-16 cursor-pointer block"
id="addForm">
<div className="flex justify-between">
<div className="flex break-all items-center ">
<Image src={fileIcon} className="w-6 mx-2"/>
<div>
<Formik
initialValues={{
taskName: "",
taskIsDone: false,
parentId: props.parentId ? props.parentId : "",
hasChildren: false,
}}
onSubmit={(values) => {
mutation.mutate(values);
props.setTrigger(false);
}}
>
<Form>
<div className="">
<TextInput
placeholder="Type a name"
name="taskName"
type="text"
/>
</div>
</Form>
</Formik>
</div>
</div>
<div className="flex items-center">
</div>
</div>
</div>
</>
) : null
}
export default CreateTaskPopup
Text Input Component:
import { useField } from "formik";
const TextInput = ({ label, ...props}) => {
const [field, meta] = useField(props);
return (
<div>
<label id="addForm" className="text-lightestGrey text-xl block"
htmlFor={props.id || props.name}>
{label}
</label>
<input id="addForm" className="bg-darkishGrey text-xl text-almostWhite my-2
outline-none w-10/12 rounded-sm p-1 mx-3" {...field} {...props} />
{meta.touched && meta.error ? <div>{meta.error}</div>: null}
</div>
);
};
export default TextInput;
I tried giving an id to the elements inside it but it's not the best solution as it has components from the Formik library to which I can't assign an id. I don't know what would be the best solution for this problem.

How to access function component's state variables in ReactJS?

I've been developing a website and I'm faced with an issue about accessing state varibles to a component. I've a address page(Addresses.jsx file) which has address modal. I'm also have a component which contains address modal's content (Address.jsx). I would like to access state inside Address.jsx file. How can I do that? If you can help me. I would be very appreciate.
Here is my code blocks;
Address.jsx file;
const Addresses = () => {
const [selectedAddress, setSelectedAddress] = useState();
const [modalType, setModalType] = useState();
const {
elementRef: addressModalRef,
show: showAddressModal,
hide: hideAddressModal,
} = useModal();
return (
<>
<div className="bg-light px-3 px-lg-4 py-4">
<h1 className="h4 mb-4">My Addresses</h1>
<div className="row g-3">
<div className="col-12 col-md-6 col-lg-4">
<AddressButton
onClick={() => {
setSelectedAddress(undefined);
setModalType("NewAddress");
showAddressModal();
}}
/>
</div>
{ADDRESSES.map((address, i) => (
<div className="col-12 col-md-6 col-lg-4" key={i}>
<AddressButton
{...address}
onClick={() => {
setSelectedAddress(address);
setModalType("EditAddress");
showAddressModal();
}}
/>
</div>
))}
</div>
</div>
<AddressModal
address={selectedAddress}
elementRef={addressModalRef}
hide={hideAddressModal}
modalType={modalType}
/>
</>)};
export default Addresses;
Address Modal file;
const AddressModal = ({ address, elementRef, hide, modalType }) => {
const onSaveAddress = () => {
console.log(address);
hide();
};
return (
<Modal elementRef={elementRef} centered size="lg" fullscreen>
<ModalBody>
<AddressForm address={address} /> // I would like to get this component's state in onSaveAddress funtion
</ModalBody>
<ModalFooter>
<button
type="button"
className="btn btn-primary flex-grow-1 px-5"
onClick={onSaveAddress}>
Kaydet
</button>
</ModalFooter>
</Modal>)};
export default AddressModal;
Instead of accessing child components state, which is not possible, do the following
Address Model
<AddressForm
address={address}
onSave={onSaveAddress}
/>
Address Form
const AddressForm = ({ onSave }) => {
const handleFormSubmit = () => {
onSave(formData);
};
return (
<form onSubmit={handleFormSubmit}>
</form>
);
}
Then in onSaveAddress, you will have access to the form data.

How to render a react component and replicate them from the click event

I want to make multiple copies of Form2 React Component with click event, but the code not working as I want, I am new in react so can anybody help me.
const Comform = () => {
const Form2 = () => {
return(
<div className="card" id='form-card2'>
<input className="form-check-input" type="checkbox" value="" id="options-check" onClick={Optionscheck} />
</div>
);
}
const Replica = () {
<Form2/>
}
return(
<button type="button" className="btn btn-success" onClick={Replica}>Add</button>
);
}
Keep a state as the replica counter and render the number of items you want using Array.from and Array.prototype.map.
Try like this:
const Form2 = () => {
return (
<div className="card" id="form-card2">
<input
className="form-check-input"
type="checkbox"
value=""
id="options-check"
// onClick={Optionscheck}
/>
abcd
</div>
);
};
const Replica = () => {
const [replicaCount, setReplicaCount] = React.useState(0);
return (
<div>
<button
type="button"
className="btn btn-success"
onClick={() => setReplicaCount((prev) => prev + 1)}
>
Add
</button>
{Array.from({ length: replicaCount }).map((_, index) => (
<Form2 key={index}/>
))}
</div>
);
};
ReactDOM.render(<Replica />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Summary
You would have to create a handler for the replica button and a state that tracks how often the button was clicked, so that you are able to render as much form items as the button was clicked.
Example
import { useState } from "react";
import "./styles.css";
const FormItem = () => (
<div className="formItem">
<label>Check me</label>
<input type="checkbox" />
</div>
);
const ReplicaButton = ({ addFormItem }) => (
<button onClick={addFormItem}>Add more</button>
);
export default function Form() {
const [formCount, setFormCount] = useState(1);
const addFormItem = () => setFormCount(formCount + 1);
return (
<div>
<form className="formius">
{Array(formCount)
.fill(null)
.map(() => (
<FormItem />
))}
</form>
<ReplicaButton addFormItem={addFormItem} />
</div>
);
}

Warning: Functions are not valid as a React child- List items does not show up

When i compile the app i get this warning in the console:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
My App.js:
import "./App.css";
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoList: [],
activeItem: {
id: null,
title: "",
completed: false,
},
editing: false,
};
this.fetchTasks = this.fetchTasks.bind(this);
}
componentWillMount() {
this.fetchTasks();
}
fetchTasks() {
console.log("Fetching...");
fetch("http://127.0.0.1:8000/api/task-list/")
.then((response) => response.json())
.then((data) =>
this.setState({
toDoList: data,
})
);
}
render() {
var tasks = this.state.toDoList;
return (
<div className="container">
<div id="task-container">
<div id="form-wrapper">
<form id="form">
<div className="flex-wrapper">
<div style={{ flex: 6 }}>
<input
className="form-control"
type="text"
name="title"
placeholder="Add task"
/>
</div>
<div style={{ flex: 1 }}>
<input
className="btn btn-warning"
id="submit"
type="submit"
name="Add"
/>
</div>
</div>
</form>
</div>
<div className="list-wrapper">
{
(tasks.map = (task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})
}
</div>
</div>
</div>
);
}
}
export default App;
Basically i'm trying to list the items in the api list but i'm missing something. Anyone help me with it?
(tasks.map = (task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})
Should be:
(tasks.map(task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})

How to filter data using react?

I have created search filter but I am not able to type anything in search input why so ? I have created searchTermChanged method but why is it not working ? When user types in input field the projects should get filtered based on title.
Code:
import Projects from '../../data/projects';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
projects: Projects
}
}
searchTermChanged = (event) => {
this.setState({ projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(this.state.search.toLowerCase()) > -1 )
})
}
render() {
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.searchTermChanged(e.target.value)}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
You need to make sure you're making correct use of the state.
import Projects from '../../data/projects';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
projects: Projects
}
}
searchTermChanged = (search) => {
this.setState({
//Update the search state here.
search,
//Use the current search state to filter
projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(search.toLowerCase()) > -1 )
}
);
}
render() {
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.searchTermChanged(e.target.value)}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
I think if you don't need to change the projects you can also do the bellow to simplify your logic:
constructor(props) {
super(props);
this.state = {
search: ''
}
}
render() {
let {search} from this.state;
let myProjects = projects.filter((p) => {
p.title.toLowerCase().indexOf(search.toLowerCase) > -1
});
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.setState({search: e.target.value})}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{myProjects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
You need to user Projects variable directly to filter otherwise filter changes will search on existing state. You need to set search value to refect what is your input
searchTermChanged = (event) => {
console.log(event);
this.setState({
projects: Projects.filter(val =>
val.title.toLowerCase().indexOf(event.toLowerCase()) > -1 ),
search: event <-- here
})
}
stackblitz: https://stackblitz.com/edit/react-fyf7fr
You are not changing the state of "search".
Assuming u have an input like this:
<input type="text" id="whatever" className="whatever" onChange={(event) => props.searchTermChanged(e.target.value)} />
you can change your method searchTermChanged
searchTermChanged = (value) => {
this.setState({search: value});
this.setState({ projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(value.toLowerCase()) > -1 )
});
}
The reason why u use "value" instead of "this.state.search" here "indexOf(value.toLowerCase())" its because setState is asynchronous and you can reach that piece of code with state outdated. And you are sure that "value" has the right value.

Categories