I'm hoping I'm close to translating my old react hook forms to RHF7 but I'm stuck trying to share refs twice with 2 different inputs. Can someone help me finish this code...So I need a ref on both dobMonth and dobYear but I can't use the same ref twice so I need to create a second one but it won't let me.
const dobMonthInput = useRef(null)
const dobYearInput = useRef(null)
const {
register
} = useForm()
const { ref, ...rest } = register('dobMonth', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
/*const { ref, ...rest } = register('dobYear', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})*/
....
<input
name="dobDay"
type="text"
{...register('dobDay', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})}
/>
<input
name="dobMonth"
type="text"
{...rest}
ref={(e) => {
ref(e)
dobMonthInput.current = e
}}
/>
<input
name="dobYear"
type="text"
{...rest}
ref={(e) => {
ref(e)
dobYearInput.current = e
}}
/>
I eventually need to use dobMonthInput.current.focus() and dobYearInput.current.focus()
I get the error Identifier 'ref' has already been declared for obvious reasons but I don't know how to get around it.
Thanks
You have an issue with ref declaration between react hook refs which its assigned to the same input, you need to create ref but without using already assigned ref to another input.
The trick here to resolve issue:
const dopMReg = register("dobMonth");
const dopYReg = register("dobYear");
for example:
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const dobMonthInput = useRef(null);
const dobYearInput = useRef(null);
const onSubmit = (data) => {
console.log(data);
dobMonthInput.current.focus();
}
const dopMReg = register("dobMonth");
const dopYReg = register("dobYear");
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="dobMonth"
type="text"
{...dopMReg}
ref={(e) => {
dopMReg.ref(e);
dobMonthInput.current = e;
}}
/>
<input
name="dobYear"
type="text"
{...dopYReg}
ref={(e) => {
dopYReg.ref(e);
dobYearInput.current = e;
}}
/>
<button>Submit</button>
</form>
);
}
and this the demo url
you can use like this too:
const dobYearInput = useRef(null)
const {
register
} = useForm()
const { ref:refDobMonth, ...restDobMonth } = register('dobMonth', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
const { ref:refDobYear, ...restDobYear } = register('dobYear', {
required: true,
validate: (value) => validateDate(value, getValues),
onChange: (e) => onChange(e),
})
Related
I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;
I have a custom hook that returns a register method to set reference to the input field in the component.
import React, { useState, useRef, useEffect } from 'react';
import { Form } from './Form';
const useFormsio = ( STATE ) => {
const [ refs, setRefs ] = useState({});
const register = (fieldArgs) => {
const inputRef = useRef();
const key = Object.keys(fieldArgs);
//console.log(fieldArgs);
useEffect(() => {
setRefs(prevState => {
return{
...prevState,
[key]: inputRef
}
})
}, [ fieldArgs ])
return inputRef;
}
return [ Form, register ];
}
export { useFormsio };
And the component as below, It contains the input fields, using register method i am passing the field name to set the reference.
const App = () => {
const INITIAL_STATE = {
userName: '',
userEmail: ''
};
const [ Form, register ] = useFormsio(INITIAL_STATE);
return(
<Form>
<input
type = 'text'
placeholder = 'Enter your name'
name = 'userName'
data-validations = { register({ name:'userName' })} />
<input
type = 'email'
placeholder = 'Enter your email'
name = 'userEmail'
data-validations = { register({ name:'userEmail' })} />
<button
type = 'submit'>
Submit
</button>
</Form>
)
}
Why i am getting error like this?
Seems to me like you're running in an infinite loop of re-renders. You should put your register() inside a lifecycle method. Something on these lines-
useEffect(()=>{
register(fieldArgs);
},[fieldArgs])
const register = (fieldArgs) => {
const inputRef = useRef();
const key = Object.keys(fieldArgs);
//console.log(fieldArgs);
setRefs(prevState => {
return{
...prevState,
[key]: inputRef
}
})
return inputRef;
}
I need to dynamically add new input fields on button click as well as get the user input of those inputs in an array. This is what I have and Im not sure how to do the array. Also on the screen the components only update when I change the state. Not on button click.
This is what I have:
import React, { useState } from 'react'
const CreatePoll = () => {
const [formData, setFormData] = useState({
question: '',
options: ['hi', 'there']
});
const {
question,
options
} = formData;
const addOption = e => {
e.preventDefault();
options.push([''])
console.log(options.length);
}
const handleQuestionChange = (e) => setFormData({
...formData,
[e.target.name]: e.target.value
})
const handleOptionChange = e => setFormData({
...formData
// options: []
})
const handleSubmit = async e => {
e.preventDefault();
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="enter your question"
type="text"
onChange={handleQuestionChange}
name="question" />
{options.map(() => {
return (
<input
placeholder="option"
type="text"
onChange={handleOptionChange}
name="option" />
)
})}
<input type="button" value="Add new option" onClick={addOption} />
<input type="submit" value="Submit" />
</form>
)
}
export default CreatePoll
I tried when addOption button is clicked, I add to the options state an empty string. The length of the array updates but the components on the screen dont until I type in the input box and the state changes. Also I need to map the values of the input boxes to their respective place in the array. They should also be able to edit at any time. How is this done?
Several things are wrong here :
You don't set your state in addOption, don't modify direcly the state object, prefere to destructure array, modify it and set the state.
Your map function don't take any parameter, so it will be the same input names every time, use parameter and index to now which option to change in handleQuestionChange
Your addOption could be improved by using question property directly in you setFormData (it worked like you did it, but it seems to me more clean with this)
import React, { useState } from 'react';
const CreatePoll = () => {
const [formData, setFormData] = useState({
question: '',
options: ['hi', 'there'],
});
const {
question,
options,
} = formData;
const addOption = e => {
e.preventDefault();
const newOptions = [...options];
newOptions.push('');
setFormData({ ...formData, options: newOptions });
console.log(options.length);
};
const handleQuestionChange = e => {
setFormData({
...formData,
question: e.target.value,
});
};
const handleOptionChange = (e, index) => {
const newOptions = [...options];
newOptions[index] = e.target.value;
setFormData({
...formData,
options: newOptions,
});
};
const handleSubmit = async e => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder="enter your question"
type="text"
onChange={handleQuestionChange}
name="question"
/>
{options.map((opt, index) => (
<input
value={opt}
key={`option_${index}`}
placeholder="option"
type="text"
onChange={e => handleOptionChange(e, index)}
name={opt}
/>
))}
<input type="button" value="Add new option" onClick={addOption} />
<input type="submit" value="Submit" />
</form>
);
};
export default CreatePoll;
to add new options on button click you need to change this function:
const addOption = e => {
e.preventDefault();
options.push([''])
console.log(options.length);
}
to be
const addOption = e => {
e.preventDefault();
const newOptions = {...formData.options}
newOptions.concat([''])
setFormData({...formatData, options: newOptions)}
}
I want to detect which character base on user keyboard hence I use onKeyDown, but how do I stop ',' been inserted into the input element?
const [inputValue, setInputValue] = useState("");
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
console.log("do something");
}
};
const handleChange = (e: any) => {
setInputValue(e.target.value)
}
return (
<input
type="text"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
return;
}
};
Your includes doesn't work since '188' (string) isn't equal to 188 (number)
You can check the last inputed char in your handleChange function like so:
const handleChange = (e: any) => {
const value = e.target.value
if (![','].includes(value[value.length-1])) {
setInputValue(e.target.value)
}
}
You just need to check the input in handleChange to decide to update new value or not
Try the code below:
Or Codesandbox
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [inputValue, setInputValue] = useState("");
const handleChange = e => {
if (!e.target.value.includes(",")) {
setInputValue(e.target.value);
}
};
return (
<input
value={inputValue}
type="text"
onChange={handleChange}
/>
);
};
export default App;
I want to debounce Formik <Field/> but when I type in the field seems debounce does not work. Also I have tried lodash.debounce, throttle-debounce and the same result. How to solve this?
CodeSandbox - https://codesandbox.io/s/priceless-nobel-7p6nt
Snippet:
import ReactDOM from "react-dom";
import { withFormik, Field, Form } from "formik";
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => setText(text), 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
setFieldValue("textField", e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
const Enhanced = withFormik({
mapPropsToValues: () => ({
textField: ""
}),
handleSubmit: (values, { setSubmitting }) => {
setSubmitting(false);
return false;
}
})(App);
ReactDOM.render(<Enhanced />, document.getElementById("root"));
const [text, setText] = useState("");
const [t, setT] = useState(null);
const onChange = text => {
if (t) clearTimeout(t);
setT(setTimeout(() => setText(text), 750));
};
I would like to suggest to move the call inside of timeout function.
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
setText(text);
//changing value in container
setFieldValue("textField", text);
}, 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
Using Custom Hooks
This is abstracted from the answer provided by #Skyrocker
If you find yourself using this pattern a lot you can abstract it out to a custom hook.
hooks/useDebouncedInput.js
const useDebouncedInput = ({ defaultText = '', debounceTime = 750 }) => {
const [text, setText] = useState(defaultText)
const [t, setT] = useState(null)
const onChange = (text) => {
if (t) clearTimeout(t)
setT(setTimeout(() => setText(text), debounceTime))
}
return [text, onChange]
}
export default useDebouncedInput
components/my-component.js
const MyComponent = () => {
const [text, setTextDebounced] = useDebouncedInput({ debounceTime: 200 })
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={(e) => setTextDebounced(e.target.value)}
/>
<div>output: {text}</div>
</Form>
)
}
An Example Using Redux, Fetching, and Validation
Here's a partial example of using a custom hook for a debounced field validator.
Note: I did notice that Field validation seems to not validate onChange but you can expect it onBlur when you leave the field after your debounced update has executed (I did not try racing it or with a long debounce to see what happens). This is likely a bug that should be opened (I'm in the process of opening a ticket).
hooks/use-debounced-validate-access-code.js
const useDebouncedValidateAccessCode = () => {
const [accessCodeLookUpValidation, setAccessCodeLookUpValidation] = useState()
const [debounceAccessCodeLookup, setDebounceAccessCodeLookup] = useState()
const dispatch = useDispatch()
const debouncedValidateAccessCode = (accessCodeKey, debounceTime = 500) => {
if (debounceAccessCodeLookup) clearTimeout(debounceAccessCodeLookup)
setDebounceAccessCodeLookup(
setTimeout(
() =>
setAccessCodeLookUpValidation(
dispatch(getAccessCode(accessCodeKey)) // fetch
.then(() => undefined) // async validation requires undefined for no errors
.catch(() => 'Invalid Access Code'), // async validation expects a string for an error
),
debounceTime,
),
)
return accessCodeLookUpValidation || Promise.resolve(undefined)
}
return debouncedValidateAccessCode
}
some-component.js
const SomeComponent = () => {
const debouncedValidateAccessCode = useDebouncedValidateAccessCode()
return (
<Field
type="text"
name="accessCode"
validate={debouncedValidateAccessCode}
/>
)
}