How to validate React-quill with React-hook-form and yup? - javascript

I used
react-quill for one of form element.
react-hook-form form validation
yup/yup resolver for validation schema
Normal inputs and textareas working as expected, but validation for react-quill is not working.
These are my code snippets.
Custom react-quill wrapper element
import React, { useRef, useState } from "react";
import PropTypes from "prop-types";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import "react-quill/dist/quill.bubble.css";
import "react-quill/dist/quill.core.css";
function Editor(props) {
const [theme, setTheme] = useState("snow");
const { id, value, inputRef, placeholder, onChange } = props;
return (
<ReactQuill
id={id}
ref={inputRef}
theme={theme}
onChange={onChange}
value={value}
modules={{
toolbar: {
...Editor.modules.toolbar,
handlers: {
// image: handleImageUpload,
},
},
...Editor.modules,
}}
formats={Editor.formats}
bounds={".app"}
placeholder={placeholder ?? ""}
/>
);
}
/*
* Quill modules to attach to editor
* See https://quilljs.com/docs/modules/ for complete options
*/
Editor.modules = {
toolbar: [
// [{ header: '1' }, { header: '2' }, { font: [] }],
// [{ size: [] }],
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
["link", "image", "video"],
["clean"],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
],
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
};
/*
* Quill editor formats
* See https://quilljs.com/docs/formats/
*/
Editor.formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"indent",
"link",
"image",
"video",
];
/*
* PropType validation
*/
Editor.propTypes = {
placeholder: PropTypes.string,
};
export default Editor;
This is form component
const validationSchema = Yup.object().shape({
companyName: Yup.string().required("Company Name is required"),
... ...
jobDescription: Yup.string().required("Job description is required"), // react-quill
... ...
howtoApply: Yup.string().required("This field is required"),
applyUrl: Yup.string().required("This field is required"),
applyEmail: Yup.string().required("This field is required"),
});
const formOptions = { resolver: yupResolver(validationSchema) };
const { register, handleSubmit, reset, control, formState } = useForm(
formOptions
);
useEffect(() => {
console.log("formState", formState); // log for form value changes
});
... ... ...
<Controller
control={control}
name="jobDescription"
// rules={{
// required: "Description must have some content.",
// validate: (value) => {
// console.log("Controller", value);
// return (
// value.split(" ").length > 10 ||
// "Enter at least 10 words in the body."
// );
// },
// }}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, isTouched, isDirty, error },
formState,
}) => (
<Editor
onChange={(description, delta, source, editor) => {
console.log(
"onChange:description",
description,
);
console.log("inputRef", ref);
onChange(description);
}}
value={value || ""}
inputRef={ref}
theme="snow"
id="jobDescription"
/>
)}
/>
I checked these issues,
https://github.com/zenoamaro/react-quill/issues/635
https://github.com/react-hook-form/react-hook-form/discussions/3372
but still not working.
Current behavior
I confirmed the changed markdown logged in console (onChange function in Editor), but can't see formState log of useEffect hook upon editor change.
(regardless of add rules prop to Controller or not)
Any help would be much appreciated
Thank you

export default function App() {
const {
register,
handleSubmit,
setValue,
watch,
formState: { errors }
} = useForm();
useEffect(() => {
register("emailContent", { required: true, minLength: 15 });
}, [register]);
const onEditorStateChange = (editorState) => {
setValue("emailContent", editorState);
};
const onSubmit = (data) => {
console.log(data);
};
const editorContent = watch("emailContent");
return (
<div className="App">
<ReactQuill
theme="snow"
value={editorContent}
onChange={onEditorStateChange}
/>
<p className="Error">{errors.emailContent && "Enter valid content"}</p>
<input type="submit" onClick={handleSubmit(onSubmit)} />
</div>
);
}
I have created a code sandbox with a working example for react-hook-form. Here is the link:
https://codesandbox.io/s/quill-with-react-hook-form-validation-cdjru

<Controller
name="desc"
control={control}
rules={{
required: "Please enter task description",
}}
theme="snow"
modules={modules}
render={({ field }) => (
<ReactQuill
{...field}
placeholder={"Write Description"}
onChange={(text) => {
field.onChange(text);
}}
/>
)}
/>;
You Can Add Validator As ur like

Related

problem with custom form handler in react

I'm trying to create a custom hook that'll can be used in any form but i have a problem which i am not sure of what it is. whenever i start typing into the fields it loses focus, to type into it again you'd have to click into it and it goes on like that. this isn't good and i would appreciate it if anyone can help me solve this error.
useForm.js
import { useState } from "react";
const useForm = () => {
const [values, setValues] = useState({
// contact form
});
const [errors, seterrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
// console.log(name, value);
};
const validate = (data) => {};
const handleSubmit = (e) => {
e.preventDefault();
// console.log(e);
};
// console.log(values);
return {
handleChange,
values,
handleSubmit
};
};
export default useForm;
Form.js
import InputGroup from "./InputGroup";
import useForm from "./useForm";
const Form = () => {
const fields = [
{
label: "First Name",
className: "input-group",
name: "firstName",
placeholder: "John",
type: "text"
},
{
label: "Last Name",
className: "input-group",
name: "lastName",
placeholder: "Doe",
type: "text"
},
{
label: "Email",
className: "input-group",
name: "email",
placeholder: "JohnDoe#example.com",
type: "email"
},
{
label: "Phone Number",
className: "input-group",
name: "Phone",
placeholder: "+234 (0)81 234 5678",
type: "text"
// pattern: "/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-s./0-9]*$/g"
},
{
label: "Comments",
className: "input-group",
name: "comment",
placeholder: "Message",
type: "textarea"
}
];
const { values, handleChange, handleSubmit } = useForm();
//funtion to generate id based on fields lenght
const id = () => {
const head = Date.now.toString(36);
const tail = Math.random().toString(36).substr(2);
return head + tail;
};
console.log(id);
return (
<form onSubmit={handleSubmit}>
{fields.map((field) => (
<InputGroup
key={id}
value={values}
onChange={handleChange}
{...field}
/>
))}
<button>submit</button>
</form>
);
};
export default Form;
Input
const Input = ({ ...props }) => {
return <input {...props} />;
};
export default Input;
You are not using your id function properly, you are only referring to it, not invoking it, change -
<InputGroup
key={id}
to
<InputGroup
key={id()}
or better still, a better name, using a verb would have highlighted the problem easier, as it now looks like a function and not a variable
<InputGroup
key={buildId()}
or whatever, but better still, you should use a static unique value for the key (i.e unique but would not change between renders, so using timestamps is a bad idea), so you could add a unique id to each field and use it -
const fields = [
{
id: <some unique ID>, <!---- here
label: "First Name",
className: "input-group",
name: "firstName",
placeholder: "John",
type: "text"
}
...
]
...
<InputGroup
key={field.id}

Set data from backend to text editor , then change those Data

import draftToHtml from "draftjs-to-html";
import { Editor } from "react-draft-wysiwyg";
import { EditorState, convertFromRaw, convertToRaw } from "draft-js";
const Sampleform=()=>{
const { ContextData } = useContext(SomeContext);
const [data, setData] = useState(EditorState.createEmpty());
const[loading,setLoading]=useState(false);
const [details, setDetails] = useState({
body:data,
date: null,
dataNew: true,
});
useEffect(() => {
setLoading(true);
(async () => {
if (details) {
setDetails(
details
)
}else{
try{
setDetails({...details,
body: //body deatils whick comes from backend
});
setDetails(details);
}catch(error){
console.log(error);
}
}
})
}, [])
useEffect(() => {
setLoading(true);
if (details && details.dataNew) {
try {
getNewDetails();
} catch (error) {
console.log(error);
}
}
}, [ContextData]);
const getNewDetails =()=>{
//func working
}
return(
<Formik
enableReinitialize={true}
initialValues={letterBody}
validationSchema={validationSchema}
onSubmit={(letterBody, { setSubmitting }) => {
details.body = draftToHtml(letter.getCurrentContent());
}}
>
{({
values,
handleBlur,
handleChange,
setFieldValue,
handleSubmit,
isSubmitting,
errors,
touched,
}) => (
<Form>
{/* data input here its works fine */}
<div>
<Editor
editorState={data}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={(data) => setData(data)}
value={data}
onblur={handleBlur}
toolbar={{
options: ["inline", "list", "link", "remove"],
inline: {
options: ["bold", "italic", "underline"],
bold: { className: "bordered-option-classname" },
italic: { className: "bordered-option-classname" },
underline: { className: "bordered-option-classname" },
},
}}
spellCheck={true}
/>
</div>
</Form>
)}
</Formik>
);
};
export default Sampleform;
**want to sent data to my text editor which comes from backend API, then change those data from a text editor, in here date component work well but text editor didn't make any sense, How to fix this Iam using "react-draft-WYSIWYG" text editor with formik **
I have provided a code sample above what is the best way to do the above task, with react useEffect and "react-draft-WYSIWYG" with formik , text editor value does not change my state

React hook form with react-select as required field

I'm trying to validate a react-select input field with react-hook-form.(Required input) But I can't seem to do this with the code block below. Can anyone point me in the right direction?
<Controller
as={Select}
options={statusOptions}
name="status"
control={control}
className="infoItemDropdown"
rules={{ required: true }}
styles={customStyles}
/>
You can wrapper the component react-select:
import Select from 'react-select';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
const RenderSelect = (props) => {
const {
field: {
value,
onChange,
},
data,
} = props;
const [selectedOption, setSelectedOption] = useState();
const handleChange = (e) => {
onChange(e.value);
setSelectedOption(e);
};
return (
<Select
value={selectedOption}
onChange={handleChange}
options={data}
/>
);
};
RenderSelect.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
label: PropTypes.string.isRequired,
}),
).isRequired,
field: PropTypes.shape({
value: PropTypes.oneOfType([
PropTypes.string.isRequired,
PropTypes.number.isRequired,
]),
onChange: PropTypes.func.isRequired,
}),
};
RenderSelect.defaultProps = {
field: { onChange: () => {}, value: '' },
};
export default RenderSelect;
And use it in react hook form:
export default function MyForm() {
const {
handleSubmit,
formState: { isValid },
control,
} = useForm({ mode: 'onChange' });
const onSubmit = (values) =>{
console.log(values)
};
const data = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="select_something"
control={control}
rules={{ required: true }}
render={({ field }) => (<RenderSelect field={field} data={data}/>)
}
/>
<button
disabled={!isValid}
type="submit"
>
Save
</button>
</form>
);
}
This way disable the button submit if the select not has a value.

How to update the state only when user start typing in text editor?

I have below code related to text editor and i am only interested to set the state of editor when user start typing the text in editor.
Need to update the state only for below scenario:-
1 Update the state only when user start typing the text in text editor.
2.Update state when user start typing the text in text in text editor but he removed the entire text whatever he entered previously. In that case also wanted to update the state.
3.Don't want to update the state when user hasn't start typing in text editor.
I have use react quill for text editor in next JS. This is next JS project.
import {useState} from "react";
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
import "../../node_modules/react-quill/dist/quill.snow.css";
const CreatePost = () => {
const blogFormLS = () => {
if (typeof window === "undefined") {
return false;
}
if (localStorage.getItem("blog")) {
return JSON.parse(localStorage.getItem("blog"));
} else {
return false;
}
};
const [body, setBody] = useState(blogFormLS());
const [values, setValues] = useState({
error: "",
sizeError: "",
success: "",
formData: "",
title: "",
});
const handleChange = (name) => (e) => {
formData.set(name);
setValues({ ...values, [name]: value, formData, error: "" });
};
const handleBody = (e) => {
setBody(e);
formData.set("body", e);
if (typeof window !== "undefined") {
localStorage.setItem("blog", JSON.stringify(e));
}
};
return () {
<>
<form onSubmit={publishBlog}>
<div className="form-group">
<lable className="text-muted">Title</lable>
<input
type="text"
value={title}
className="form-control"
onChange={handleChange("title")}
/>
</div>
<div className="form-group">
<ReactQuill
value={body}
placeholder="Write something amazing..."
onChange={handleBody}
modules={CreatePost.modules}
formats={CreatePost.formats}
/>
</div>
</form>
</>
}
}
CreatePost.modules = {
toolbar: [
[{ header: "1" }, { header: "2" }, { header: [3, 4, 5, 6] }, { font: [] }],
[{ size: [] }],
["bold", "italic", "underline", "strike", "blockquote"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image", "video"],
["clean"],
["code-block"],
],
};
CreatePost.formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"link",
"image",
"video",
"code-block",
];
export default CreatePost;
you need to binding function on input:
<input
type="text"
value={title}
className="form-control"
onChange={() => handleChange("title")}
/>

How to fire a setState function in react from child componenet to parent componenet with data from a button click

I have a parent componenet called FormLeadBuilderEdit, and it is use useState hook I pass the setState(setCards in my case) function down to to my child componenet called Card. For some reason in the child componenet when I call setCard("vale") the state doesnt update. So I am not sure what I am doing wrong.
Any help would be great
Thanks
FormLeadBuilderEdit Component
import React, { useState, useEffect } from "react";
import { Card } from "./Card";
const FormLeadBuilderEdit = ({ params }) => {
const inputs = [
{
inputType: "shortText",
uniId: Random.id(),
label: "First Name:",
value: "Kanye",
},
{
inputType: "phoneNumber",
uniId: Random.id(),
label: "Cell Phone Number",
value: "2813348004",
},
{
inputType: "email",
uniId: Random.id(),
label: "Work Email",
value: "kanye#usa.gov",
},
{
inputType: "address",
uniId: Random.id(),
label: "Home Address",
value: "123 White House Avenue",
},
{
inputType: "multipleChoice",
uniId: Random.id(),
label: "Preferred Method of Contact",
value: "2813348004",
multipleChoice: {
uniId: Random.id(),
options: [
{
uniId: Random.id(),
label: "Email",
checked: false,
},
{
uniId: Random.id(),
label: "Cell Phone",
checked: false,
},
],
},
},
{
inputType: "dropDown",
uniId: Random.id(),
label: "How did you find us?",
value: "2813348004",
dropDown: {
uniId: Random.id(),
options: [
{
uniId: Random.id(),
label: "Google",
},
{
uniId: Random.id(),
label: "Referral",
},
],
},
},
];
const [cards, setCards] = useState([]);
setCards(inputs)
return (
<>
<Card
key={card.uniId + index}
index={index}
id={card.uniId}
input={card}
setCards={setCards}
params={params}
cards={cards}
/>
</>
);
};
export default FormLeadBuilderEdit;
Cart Component
import React, { useRef } from "react";
import { Random } from "meteor/random";
export const Card = ({ setCards, cards }) => {
const addOption = () => {
const newCards = cards;
newCards.map((card) => {
if (card.inputType === "multipleChoice") {
card.multipleChoice.options.push({
uniId: Random.id(),
label: "test",
checked: false,
});
}
});
console.log(newCards);
setCards(newCards);
return (
<>
<button onClick={addOption} type="button">
Add Option
</button>
</>
);
};
React uses variable reference as a way to know which state has been changed and then triggers re-renders.
So one of the first thing you would like to know about state is that "Do not mutate state directly".
Reference: https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
Instead, produce a new state that contains changes (which has different variable reference) :
const addOption = () => {
const newCards = cards.map((card) => {
if (card.inputType === "multipleChoice") {
const newOption = {
uniId: Random.id(),
label: "test",
checked: false,
};
card.multipleChoice.options = [...card.multipleChoice.options, newOption];
}
return card;
});
setCards(newCards);
// setCards(cards); <- this refer to the current `cards` which will not trigger re-render
};
As pointed out by one of the users, you are passing an empty cards array on which you are performing map operation which not surprisingly returns an empty array itself, hence you are not getting any state changes.
The logic of passing the setCards is correct.
Here is a small example where state changes are taking place and also showing.
import React, { useState } from "react";
const App = () => {
const [cards, setCards] = useState([]);
return (
<>
<Card
setCards={setCards}
cards={cards}
/>
<p>{cards.toString()}</p>
</>
);
};
const Card = ({ setCards, cards }) => {
const addOption = () => {
setCards(["1","2"]);
};
return (
<>
<button onClick={addOption} type="button">
Add Option
</button>
</>
);
};
export default App;
Screenshot:
Codesandbox Link

Categories