Here's the React component I'm creating to host Slate:
import React, { useMemo, useState } from "react";
import { Field, useField, ErrorMessage } from "formik";
import { Slate, Editable, withReact } from "slate-react";
const FormRichText = ({ label, ...props }) => {
const [field, meta] = useField(props);
const editor = useMemo(() => withReact(createEditor()), []);
const [editorContent, setEditorContent] = useState(field.value);
field.value = editorContent;
return (
<Slate
{...field}
{...props}
name="transcript"
editor={editor}
onChange={(v) => setEditorContent(v)}
>
<Editable />
</Slate>
);
};
export default FormRichText;
The issue I'm having is when I try to connect it into Formik, whatever I edit will not pass to the Formik values in handleSubmit.
<FormRichText
name="transcript"
id="transcript"
/>
I don't understand Formik which is why I'm having the issue. I believe I need to surface the value of Slate (which I've stored in a state variable) but I'm struggling to work my way through Formik to know how to do that. I assumed that if I use the userField prop I would be able to set field.value and that would get through to Formik.
This worked:
const FormRichText = ({ label, ...props }) => {
const [field, meta, helpers] = useField(props.name);
const editor = useMemo(() => withReact(createEditor()), []);
const { value } = meta;
const { setValue } = helpers;
useEffect(() => {
setValue([
{
type: "paragraph",
children: [{ text: "Type something ..." }],
},
]);
}, []);
return (
<Slate name="transcript"
value={value}
editor={editor}
onChange={(v) => setValue(v)}>
<Editable />
</Slate>
</div>
</div>
);
};
Hopefully this helps someone else.
Saying that, I still have to render content through Slate so I may be back on this thread!
Related
I want to store the information of multiple inputs entered into antd Select components in a single state variable but am having trouble getting the below to work.
This example is solved here for a form but the same solution doesn't seem to work for antd Select component. There are two inputs: a first name and a last name that I want to remember. The below code doesn't work because e doesn't have an attribute called name is what the console tells me. I also tried e.target.name and e.target.value but I get an error that e doesn't have an attribute called a target either. What is the right way to do this?
import React, { useState } from 'react';
import { Select } from 'antd';
const App = () =>{
const [varState, setVarState] = useState({firstName:'Jack', lastName:'Smith'});
const firstNameOptions = [ {label:'Jack', value:'Jack'}, {label:'Jill',value:'Jill'}, {label:'Bill',value:'Bill'} ];
const lastNameOptions = [ {label:'Smith', value:'Smith'}, {label:'Potter',value:'Potter'}, {label:'Bach',value:'Bach'} ];
const changeState = (e) => {
setVarState( prevState => ({ ...prevState, [e.name]: e.value}));
console.log(varState)
};
return ( <>
<div>
<Select name={'firstName'} defaultValue={'Pick One'} options={firstNameOptions} onChange={changeState} />
<Select name={'lastName'} defaultValue={'Pick One'} options={lastNameOptions} onChange={changeState} />
</div>
</>
);
}
export default App;
At the heart of it, it seems that I don't know how to name the Select components in such a way that their names can be passed on to the onChange handler.
More generally, given a component like antd Select, how can I figure out what the right "name field" is for this component so that it's value can be passed on to an onChange handler? For instance, what in the documentation for select gives this information?
The Select component seems to be sending the value instead of the events object. So, You can just make a closure and pass the name of the select. Also, for consoling you can make use of a useEffect which only consoles when the state has been updated. Otherwise, you could see previous state as state updates are asynchronous. Below is a working solution.
import React, { useEffect, useState } from "react";
import { Select } from "antd";
const App = () => {
const [varState, setVarState] = useState({
firstName: "Jack",
lastName: "Smith"
});
const firstNameOptions = [
{ label: "Jack", value: "Jack" },
{ label: "Jill", value: "Jill" },
{ label: "Bill", value: "Bill" }
];
const lastNameOptions = [
{ label: "Smith", value: "Smith" },
{ label: "Potter", value: "Potter" },
{ label: "Bach", value: "Bach" }
];
// for consoling when the state updates
useEffect(() => {
console.log(varState);
}, [varState.firstName, varState.lastName]);
const changeState = (value, identifier) => {
// console.log(value, identifier);
setVarState((prevState) => ({ ...prevState, [identifier]: value }));
};
return (
<>
<div>
<Select
name={"firstName"}
defaultValue={"Pick One"}
options={firstNameOptions}
onChange={(val) => changeState(val, "firstName")}
/>
<Select
name={"lastName"}
defaultValue={"Pick One"}
options={lastNameOptions}
onChange={(val) => changeState(val, "lastName")}
/>
</div>
</>
);
};
export default App;
yes, Actually antd doesn't have attribute name for input fields. antdesign directly gives the selected value, we need to do some tweeks to achieve this.
Here is the solution:
import React, { useState } from 'react';
import { Select } from 'antd';
const firstNameOptions = [ {label:'Jack', value:'Jack'}, {label:'Jill',value:'Jill'}, {label:'Bill',value:'Bill'} ];
const lastNameOptions = [ {label:'Smith', value:'Smith'}, {label:'Potter',value:'Potter'}, {label:'Bach',value:'Bach'} ];
const App = () =>{
const [varState, setVarState] = useState(null);
const changeState = (fieldName) => (value) => {
setVarState( prevState => ({ ...prevState, [fieldName]: value}));
console.log(varState)
};
return ( <>
<div>
<Select defaultValue={'Pick One'} options={firstNameOptions} onChange={changeState('firstName')} />
<Select defaultValue={'Pick One'} options={lastNameOptions} onChange={changeState('lastName')} />
</div>
</>
);
}
export default App;
I hope this helps 😊
It's been 3 months I learn ReactJS + TypeScript. My question is about to use react-hook-form (v7) for editing a form. I want to use my custom component that I created and found how to do it by myself !
Here is a part of my form provider with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import InputText from 'components/commons/form/InputText';
import { supabase } from 'configs/supabase';
const EditEducation: React.FC = () => {
const { educationId } = useParams();
const [education, setEducation] = useState<education>();
const getEducation = async (educationId: string | undefined) => {
try {
const { data, error } = await supabase
.from('tables1')
.select('data1, data2')
.eq('id', educationId)
.single();
if (error) {
seterror(error.message);
}
if (data) {
return data;
}
} catch (error: any) {
alert(error.message);
}
};
useEffect(() => {
getEducation(educationId).then((data) => {
setEducation(data);
});
// eslint-disable-next-line
}, [educationId]);
const methods = useForm();
const onSubmit = async (formData: any) => {
const updateData = {
data1 = formData.data1,
data2 = formData.data2
};
try {
setSaving(true);
const { error } = await supabase.from('educations').update(updateData);
if (error) {
seterror(error.message);
}
if (!error) {
navigate('/experiences/education');
}
setSaving(false);
} catch (error: any) {
seterror(error.message);
}
};
return (
...
<FormProvider {...methods}>
<form className="p-4" onSubmit={methods.handleSubmit(onSubmit)}>
<InputText
id="data1"
label="Data1"
placeholder="Ex: data1"
defaultValue={education?.data1}
options={{ required: 'This field is required' }}
/>
<Button type="submit">{saving ? 'Saving' : 'Save'}</Button>
</form>
</FormProvider>
...
)
};
Here is my custom component :
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
interface InputProps {
id: string;
label: string;
placeholder?: string;
defaultValue?: string;
}
const InputText: React.FC<InputProps> = ({
id,
label,
placeholder,
defaultValue,
options,
...rest
}: InputProps) => {
const {
register,
setValue,
formState: { errors }
} = useFormContext();
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
return (
<div className="">
<label htmlFor={id} className="">
{label}
</label>
<input
type="text"
placeholder={placeholder}
className=""
id={id}
defaultValue={defaultValue}
{...register(id, options)}
{...rest}
/>
{errors[id] && (
<p className="">
<span className="">*</span> {errors[id]?.message}
</p>
)}
</div>
);
};
export default InputText;
As you can see, I had use a formContext because I want to deconstruct my code into smaller components.
Now I'm having some doubts if I correctly code, specialy when I use ut editing forms : if set my default value via "defaultValue" prop, I have to submit (error show) then clique inside the input to change the state in order to clean the error in the input component.
This is why I have add the useEffect hook to clean the input validation error and it's working. What do you think about this ? Is there a better way to manage it (I think Yup it's a cleaner way to set the validation schema) ?
Thanks in advance and sorry for my rusty English. Great day to all and hope my code will help people.
Use <FormProvider {...methods}> and it's working but I do not know if it's a good way to do it.
Edit : In reality, I have to double submit to get my data so I guess it's not the correct way, any sugestions ?
Edit2 : I have found a "solution" : if I have a defaultValue in my props, I do in my component :
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
I do not think it is the better solution ...
I wrongly edited my previous answer, here is the original:
You should provide default values to useForm, not to your component (so your InputText doesn't need to know about defaultValue or setValue, it will have the correct value thanks to the register method).
To initialize the form, you can do
useForm({ defaultValues: { data1: education?.data1 } });
If the data you use to provide default values is loading after the form is initialized, you can use the reset method (see docs), which I personally put in a useEffect to watch for data update:
const Component: React.FC = ({ defaultValues }) => {
const {
register,
handleSubmit,
reset,
} = useForm({ defaultValues });
useEffect(() => {
reset(defaultValues);
}, [defaultValues, reset]);
return ...
}
On another note, you should define getEducation in the useEffect that calls it, instead of in the component, so that the method isn't declared every time your component is rendered. Snippet:
useEffect(() => {
const getEducation = () => {
...
};
getEducation();
}, [educationId]);
It's been 3 months I learn ReactJS + TypeScript. My question is about to use react-hook-form (v7) for editing a form. I want to use my custom component that I created and found how to do it by myself !
editForm.tsx
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import InputText from 'components/commons/form/InputText';
import { supabase } from 'configs/supabase';
const EditEducation: React.FC = () => {
const { educationId } = useParams();
const [education, setEducation] = useState<education>();
...
const getEducation = async (educationId: string | undefined) => {
try {
const { data, error } = await supabase
.from('tables1')
.select('data1, data2')
.eq('id', educationId)
.single();
if (error) {
seterror(error.message);
}
if (data) {
return data;
}
} catch (error: any) {
alert(error.message);
}
};
useEffect(() => {
getEducation(educationId).then((data) => {
setEducation(data);
});
// eslint-disable-next-line
}, [educationId]);
const methods = useForm();
const onSubmit = async (formData: any) => {
const updateData = {
data1 = formData.data1,
data2 = formData.data2
};
try {
setSaving(true);
const { error } = await supabase.from('educations').update(updateData);
if (error) {
seterror(error.message);
}
if (!error) {
navigate('/experiences/education');
}
setSaving(false);
} catch (error: any) {
seterror(error.message);
}
};
return (
<FormProvider {...methods}>
<form className="p-4" onSubmit={methods.handleSubmit(onSubmit)}>
<InputText
id="data1"
label="Data1"
placeholder="Ex: data1"
defaultValue={education?.data1}
options={{ required: 'This field is required' }}
/>
<Button type="submit">{saving ? 'Saving' : 'Save'}</Button>
</form>
</FormProvider>
)
...
myCustomComponent.tsx
import React, { useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
interface InputProps {
id: string;
label: string;
placeholder?: string;
defaultValue?: string;
}
const InputText: React.FC<InputProps> = ({
id,
label,
placeholder,
defaultValue,
options,
...rest
}: InputProps) => {
const {
register,
setValue,
formState: { errors }
} = useFormContext();
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
return (
<div className="">
<label htmlFor={id} className="">
{label}
</label>
<input
type="text"
placeholder={placeholder}
className=""
id={id}
defaultValue={defaultValue}
{...register(id, options)}
{...rest}
/>
{errors[id] && (<p className="">
<span className="">*</span> {errors[id]?.message}
</p>)}
</div>
);
};
export default InputText;
As you can see, I had use a formContext because I want to deconstruct my code into smaller components. Now I'm having some doubts if I correctly code, specialy when I use ut editing forms : if set my default value via "defaultValue" prop, I have to submit (error show) then clique inside the input to change the state in order to clean the error in the input component.
This is why I have add the useEffect hook to clean the input validation error and it's working. What do you think about this ? Is there a better way to manage it (I think Yup it's a cleaner way to set the validation schema) ? Thanks in advance and sorry for my rusty English. Great day to all and hope my code will help people.
Edit1 : In reality, I have to double submit to get my data so I guess it's not the correct way, any sugestions ?
Edit2 : I have found a "solution" : if I have a defaultValue in my props, I do in my custom component :
useEffect(() => {
if (defaultValue) setValue(id, defaultValue, { shouldDirty: true });
}, [defaultValue, setValue, id]);
I do not think it is the better solution ...
Edit3 : Thanks #Jérémy Rippert this is my working solution :
editForm.tsx
...
const methods = useForm();
const { reset } = methods;
useEffect(() => {
reset(education);
}, [reset, education]);
return (
<FormProvider {...methods}>
<form className="p-4" onSubmit={methods.handleSubmit(onSubmit)}>
<InputText
id="degree"
label="Degree"
placeholder="Ex: Master 2 - Design & Marketing"
options={{ required: 'This field is required' }}
/>
</form>
</FormProvider>
)
...
myCustomComponent.tsx
...
const InputText: React.FC<InputTextProps> = ({
id,
label,
placeholder,
options
}: InputTextProps) => {
const {
register,
formState: { isDirty, isValid, touchedFields, dirtyFields, errors }
} = useFormContext();
return (
<input
type="text"
placeholder={placeholder}
className={`block w-full rounded-lg border ${
errors[id] ? 'border-red-600' : 'border-gray-600'
} bg-gray-700 p-2.5 text-sm text-white placeholder-gray-400 focus:outline-none`}
id={id}
{...register(id, options)}
/>
)
...
Thanks again #Jérémy Rippert
The onClickHandler in the following code, in this component, 'SearchResult', sometimes work and sometimes not.
I can't figure out any logic that can explain why it works when it works, and why it's not working, when it's not working.
I've put a debugger inside the onClickHandler, at the beginning of it, and when it's not working, it doesn't get to the debugger at all - what indicates that the function sometimes isn't even called, and I can't figure out why.
Furthermore, I've tried to move all the code in function to the onClick, inline, but then, it's not working at all.
In addition, I've tried to use a function declaration instead of an arrow function, and it still behaves the same - sometimes it works, and sometimes it's not...
This is the site, you can see the behavior for yourself, in the search box.
This is the GitHub repository
Here you can see a video demonstrating how it's not working, except for one time it did work
Please help.
The problematic component:
import { useDispatch } from 'react-redux'
import { Col } from 'react-bootstrap'
import { getWeatherRequest } from '../redux/weather/weatherActions'
import { GENERAL_RESET } from '../redux/general/generalConstants'
const SearchResult = ({ Key, LocalizedName, setText }) => {
const dispatch = useDispatch()
const onClickHandler = () => {
dispatch({ type: GENERAL_RESET })
dispatch(
getWeatherRequest({
location: Key,
cityName: LocalizedName,
})
)
setText('')
}
return (
<Col className='suggestion' onClick={onClickHandler}>
{LocalizedName}
</Col>
)
}
export default SearchResult
This is the parent component:
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Form } from 'react-bootstrap'
import { getAutoCompleteResultsRequest } from '../redux/autoComplete/autoCompleteActions'
import { AUTO_COMPLETE_RESET } from '../redux/autoComplete/autoCompleteConstants'
import SearchResult from './SearchResult'
const SearchBox = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()
const autoComplete = useSelector((state) => state.autoComplete)
const { results } = autoComplete
const onChangeHandler = (e) => {
if (e.target.value === '') {
dispatch({ type: AUTO_COMPLETE_RESET })
setText('')
}
setText(e.target.value)
dispatch(getAutoCompleteResultsRequest(e.target.value))
}
const onBlurHandler = () => {
setTimeout(() => {
dispatch({ type: AUTO_COMPLETE_RESET })
setText('')
}, 100)
}
return (
<div className='search-box'>
<Form inline>
<div className='input-group search-md search-sm'>
<input
type='search'
name='q'
value={text}
onChange={onChangeHandler}
onBlur={onBlurHandler}
placeholder='Search Location...'
className='mr-sm-2 ml-sm-3 form-control'
/>
</div>
</Form>
<div className='search-results'>
{results &&
results.map((result) => {
return (
<SearchResult key={result.Key} {...result} setText={setText} />
)
})}
</div>
</div>
)
}
export default SearchBox
I played a bit with your code and it looks like a possible solution may be the following addition in the SearchResult.js:
const onClickHandler = (e) => {
e.preventDefault();
...
After some tests
Please remove the onBlurHandler. It seams to fire ahaed of the onClickHandler of the result.
Can you put console.log(e.target.value) inside the onChangeHandler,
press again search results and make sure that one of it doesn't working and show us the console.
In searchResult component print to the console LocalizedName as well
I am making a Shopify app using Shopify Polaris.
I used the ActionList component.
https://polaris.shopify.com/components/actions/action-list
I want to change the state value on the onAction event.
I did like this.
const [searchValue, setSearchValue] = useState('');
const handleAction = (value) => {
setSearchValue(value);
}
const a = ["A","B"];
const searchResultsMarkup = (
<ActionList
items={[
{
content: a[0],
onAction: handleAction(a[0]),
},
{
content: a[1],
onAction: handleAction(a[1]),
},
/>
);
I am a beginner in React.
So maybe a silly question.
but kindly teach me.
Thanks
You are passing it down as a prop, so you will have to change it within the Component.
import React, { useState } from 'react';
export const Parent = () => {
const [searchValue, setSearchValue] = useState('');
const handleAction = value => {
setSearchValue(value);
}
return <ActionList changeSearchVal={handleAction} />
}
export const ActionList = ({ changeSearchVal }) => {
return (
<form>
<input type="text" onChange={e => changeSearchVal(e.target.value)}/>
</form>
)
}
If you want to change the searchValue within ActionList.
I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}
You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor
Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}
!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};