How do I pass input ref values from Child.js to Parent.js in functional component? I have tried using forwardRef but it can only pass one value, what I need is passing multiple input values from Child.js to Parent.js
Child.js
export default function Child() {
const id1 = useRef('')
const id2 = useRef('')
return (
<>
<input type="text" id="id1" ref={id1} />
<label for="id1"> ID 1</label>
<input type="text" id="id2" ref={id2} />
<label for="id2"> ID 2</label>
</>
)
}
Parent.js
export default function Parent() {
function onSubmit() {
console.log(## I WANT REF ID1 AND ID2 FROM CHILD.JS VALUE HERE ##)
}
return ( <>
<Child />
<button onClick={onSubmit}>
Submit
</button>
</>)
}
Child.js
export default function Child(props) {
const id1 = useRef('')
const id2 = useRef('')
props.func('My name is Dean Winchester & this is my brother Sammie');
return (
<>
<input type="text" id="id1" ref={id1} />
<label for="id1"> ID 1</label>
<input type="text" id="id2" ref={id2} />
<label for="id2"> ID 2</label>
</>
)
}
parent.js
export default function Parent() {
function onSubmit() {
console.log(## I WANT REF ID1 AND ID2 FROM CHILD.JS VALUE HERE ##)
}
const pull_data = (data) => {
console.log(data); // LOGS DATA FROM CHILD (My name is Dean Winchester... &)
}
return ( <>
<Child func={pull_data} />
<button onClick={onSubmit}>
Submit
</button>
</>)
}
Pass a callback to Child and call it in useEffect:
useEffect(() => {
onRefLoad(id1, id2);
}, [])
and save it to some kind of state in Parent to be able to use it in onSubmit
You have multiple options here, depending on your situation. For forms easiest solution would be to use FormData interface to get all values in form. You set up your onSubmit handler on form and when form will be submitted via default browser methods (pressing Enter/Return in field or pressing submit button), you can get form from event and build FormData object:
// parent
function Form({ children }) {
function handleSubmit(event) {
// disable page reload and actual submitting of form if needed
event.preventDefault();
let formData = new FormData(event.target);
}
return (
<form id="my-form-id" onSubmit={handleSubmit}>
{children}
<button type="submit">Submit</button>
</form>
);
}
However it might not work properly, if you use some non standard elements. In this case you can set up some kind of onChange handler to get data from child and save it in parent state. When button will be pressed you will get data from local (in parent) state and use it.
Related
I am missing how to bring the input value out of the child component.
I have an Input component that I want to re-use, there I can see what happens onChange, and I see every change on the input field.
Then on the parent component, Form, where I use <Input />, I have the rest of the form with the Submit button. At this level, I want to handle onSubmit, but I cannot see/console the value of the input here. I can only see it from the child.
Any ideas about what I am doing wrong?
Input.js - here I can see the input value onChange
function Input(props) {
const { label, name, value } = props;
const handleChange = (event) => {
const updateForm = {...Form};
console.log("change:", updateForm)
updateForm[label] = event.target.value;
}
return (
<label>
{label}
<input name={name} value={value} onChange={handleChange}></input>
</label>
)
}
export { Input }
Forms.js - here I cannot get access to the input value and submit/handle it
function Form(props) {
const handleSubmit = (event) => {
event.preventDefault();
console.log(Input.value);
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
<Input label={props.label} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
I have that structure because I am defining what I want in my Form on the main HomePage component:
function Home() {
return (
<>
.......
<Section withForm label={["Name"]} {...homeObjFive}/>
<Section withForm label={"Phone"} {...homeObjOne}/>
.......
</>
)
}
This is the perfect case to use the useRef function from react.
In Form.js
import React, { useRef } from 'react'
create a reference constant and pass it as a prop into the input component. and change the input value that is handled in the onSubmit function to the reference
Also in Form.js (changes are made to the submit function)
function Form(props) {
const { inputValue } = useRef(); // added
const handleSubmit = (event) => {
event.preventDefault();
console.log(inputValue); // changed
console.log(props.label.value)
alert(`form is: ${event.target.input}`);
}
return (
<>
<form onSubmit={handleSubmit}>
{/* added the inputValue prop to the input component */}
<Input label={props.label} inputValue={inputValue} />
<input type="submit" value="Submit"></input>
</form>
</>
)
}
and now inside of the Input component set the input elements reference to the inputValue prop. you will no longer need a onChange function as reacts useRef function is updated automatically
In Input.js
function Input(props) {
return (
<label>
{props.label}
<input name={props.name} value={props.value} ref={props.inputValue}></input>
</label>
)
}
export { Input }
Suppose you have a form with two inputs, name and email (these are the id props of the inputs). You can extract the form values like this:
const handleSubmit = (event) =>
{
event.preventDefault()
const data = new FormData(event.currentTarget)
const name = data.get('name')
const email = data.get('email')
// do something with the data
}
You can read more about FormData here.
I want to set up a custom Form component using react-hook-form that can handle fields that are potentially nested or wrapped in other elements. My approach is to go through the component tree and recursively pass register and errors (returned by useForm()) to all leaf nodes that are input fields.
For example, a simple sign-up form. The first and last name fields are wrapped in a div that styles them to be on the same line. The form looks like this:
<Form
onSubmit={onSubmit}
styles={["login_form"]}
showButton
buttonText='SIGN UP'>
// These are the wrapped fields
<div className='signup_name_field'>
<TextInput name={"fname"} label={"first"} />
<TextInput name={"lname"} label={"last"} />
</div>
<TextInput name={"email"} label={"email"} />
<TextInput password={true} name={"password"} label={"password"} />
<TextInput
password={true}
name={"passwordConfirm"}
label={"confirm password"}
/>
</Form>
I created a custom Form component following react-hook-form's example but added a recursive function to handle nested or wrapped fields:
const recursiveInjectProps: any = (children: any) => {
return React.Children.map(children, (child) => {
if (child.props.children) {
recursiveInjectProps(child.props.children);
return child;
} else {
if (child.props.name) {
return React.createElement(child.type, {
...{
...child.props,
register: register,
key: child.props.name,
},
});
} else {
return child;
}
}
});
};
return (
<form className={styles.join(" ")} onSubmit={handleSubmit(onSubmit)}>
{recursiveInjectProps(children)}
{renderButton()}
</form>
);
And TextInput looks like this:
const checkRegister = () => {
if (register) {
return (
<div className='login_field'>
<input
className='login_input'
name={name}
placeholder={label}
ref={register(validation)}
type={password ? "password" : "text"}
{...rest}
/>
<label htmlFor={name} className='login_label' />
{errors && errors[name] && errors[name].message}
</div>
);
} else {
return <div>no dice</div>;
}
};
return checkRegister();
The issue is that recursiveInjectProps() is failing to inject register and errors for children that are more than one layer deep (so, the name fields that are wrapped in the div).
I know this because when it renders I see "no dice" where the name fields should be.
Would really appreciate any help with this.
Perhaps you should use context API: https://react-hook-form.com/api#useFormContext
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input name="test" ref={register} />;
}
The scenario is an unknown number of input boxes are created with their own send button. A user enters some value and with onclick this value and a URI associated with that input is sent to a function which concatenates the two values and opens in the browser. I have done this in plain JS and in Angular 9 but I cannot figure out how to do this in React. I am only five days in my React adventure and I suspect my approach is incorrect.
In Angular I would create a reference to the input box and in my onclick I would add reference.value. How can I do that in React?
This is most likely a duplicate but I've been unable to find a QA that fits my use case.
Stackblitz starter app, right now it just passes a string.
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick(data){
alert(data)
}
render() {
return (
<div>
<form>
<input placeholder='enter data' />
</form>
<br />
<botton className='btn' onClick={() => this.handleClick('test')}>Click</botton>
</div>
);
}
}
You can do so by using React.useRef:
import React from "react";
export default function App() {
const ref = React.useRef();
const handleClick = (data) => {
alert(data)
}
return (
<div>
<form>
<input ref={ref} placeholder='enter data' />
</form>
<br />
<botton className='btn' onClick={() => handleClick(ref.current.value)}>Click</botton>
</div>
);
}
See example on codesandbox.
Although, I would go with a different approach (get the value from the method and not from the call itself:
import React from "react";
export default function App() {
const ref = React.useRef();
const handleClick = () => {
if (ref) {
alert(ref.current.value);
}
};
return (
<div>
<form>
<input ref={ref} placeholder="enter data" />
</form>
<br />
<botton className="btn" onClick={handleClick}>
Click
</botton>
</div>
);
}
This code example is minimal representation of what the structure is
Main component where we group all other components:
<Form>
<FormGroup>
<RadioGroup>
<RadioButton/>
<RadioButton/>
</RadioGroup>
</FormGroup>
<FormGroup>
<TextInput />
</FormGroup>
<Button>Submit Form</Button>
</Form>
The goal is to create a reference to every TextInput in the FormGroup or to every RadioButton in RadioGroup or even FormGroup, so lets go further down the components, for now Form and FormGroup are empty components rendering children:
const Form: React.FunctionComponent<Props> = ({ children }) => {
return (
<form>
{children}
</form>
);
};
const FormGroup: React.FunctionComponent<Props> = ({ children }) => {
// WE WANT TO ACCESS REF HERE, with React.Children.map every child's ref is always null
return (
{children}
);
};
To keep it simple the RadioGroup also just rendering children
const RadioGroup: React.FunctionComponent<Props> = ({ children }) => {
// WE WANT TO ACCESS REF HERE, with React.Children.map every child's ref is always null
return (
{children}
);
};
And we are getting to the main point, the Child we want to create a reference to, in this example the RadioButton component
class RadioButton extends Component<{props}, state> {
this.state = {
inputRef: React.createRef()
};
handleClick() {
WE CAN ACCESS THE REF HERE
// this.state.inputRef.current
}
render() {
return (
<div> // putting the ref here also doesnt work in parent components
<label>
<input
ref={this.state.inputRef}
onChange={() => this.handleClick()}
/>
</label>
</div>
);
}
};
I would suggest you to use react-hook-form if you are working with form.
react-hook-form
I'd like to react rerender component after every state edit.
App component:
let [cur1, setCur1] = useState('USD')
let [cur2, setCur2] = useState('EUR')
let [result, setResult] = useState(0)
let currenciesArr = [cur1, cur2]
async function getRate(e) {
e.preventDefault()
setCur1(cur1 = e.target.cur1.value)
setCur2(cur2 = e.target.cur2.value)
let amount = e.target.amount.value
const api_url = await fetch(`https://free.currconv.com/api/v7/convert?q=${cur1}_${cur2}&compact=ultra&apiKey=${API_KEY}`)
const data = await api_url.json()
await setResult(convert(amount, data))
}
I have used Context.Provider for rerender, but it doesn't work.
return (
<Context.Provider value={{currenciesArr}}>
<div>
<Choose getRate={getRate} chooseCur={chooseCur} chooseCur2={chooseCur2}/>
<ShowRate currencies={currenciesArr} result={result}/>
</div>
</Context.Provider>
)
Component that need to rerender
function Choose(props) {
const cProps = useContext(Context)
console.log(cProps.currenciesArr);
return(
<div>
<div>
<button onClick={ props.chooseCur } name='RUB'>RUB</button>
<button onClick={ props.chooseCur } name='AUD'>AUD</button>
</div>
<div>
<button onClick={ props.chooseCur2 } name='EUR'>EUR</button>
<button onClick={ props.chooseCur2 } name='GBP'>GBP</button>
</div>
<form onSubmit={props.getRate}>
{cProps.currenciesArr.map((item,i) => {
return(
<input type='text' key={i} name={'cur'+(i+1)} defaultValue={item}></input>
)
})
}
<input type='text' name='amount' defaultValue='1'></input>
<button onClick={(e)=>{console.log(e.target)}} ></button>
</form>
</div>
)
}
Button with prop props.chooseCur setting state in App component
function chooseCur(e) {
e.preventDefault()
setCur1(e.target.name)
}
function chooseCur2(e) {
e.preventDefault()
setCur2(e.target.name)
}
and i'd like to "choose" component will rerender after setState.
First currenciesArr should be part of the state as const [currenciesArr, setCurrenciesArr] = useState([cur1, cur2])
Next, you need to call setCurrenciesArr in your chooseCur2 functions. I used a restructuring assignment to get the value of name inside the function. Hooks are called when the event loop is complete. See Capbase Medium post for more information on hooks and the event loop.
In choose.js
You need to use value in your input instead of defaultValue and set it as readonly to prevent receiving a warning about setting the value.
Default value provides the value if none is present.
See the following codesandbox for a working version.
https://codesandbox.io/s/long-rain-8vyuh