Input component > feeds Forms component - how to handle submit? - reactjs

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.

Related

Use array of strings in React Hook Form

In a form that I am making the material that is being created in the form should have multiple width options that can be added. This means that I will have a text input where the user can add an option, and when this option is added, it should be added to the React Hook Form widthOptions array, without using the regular react state. How would one do this? How do you add an item to the total React Hook Form state, I only see options for just one input field corresponding to a property.
This is how i would do it using the regular React state
import { TrashIcon } from "#heroicons/react/24/outline";
import React, { useRef, useState } from "react";
const Test = () => {
const [widthOptions, setWidthOptions] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const removeWidthOption = (widthOption: string) => {
setWidthOptions(widthOptions.filter((option) => option !== widthOption));
};
const addWidthOption = (widthOption: string) => {
setWidthOptions([...widthOptions, widthOption]);
};
const editWidthOptions = (widthOption: string, index: number) => {
const newWidthOptions = [...widthOptions];
newWidthOptions[index] = widthOption;
setWidthOptions(newWidthOptions);
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => addWidthOption(inputRef?.current?.value)}>
Add Width Option
</button>
{widthOptions.map((option, index) => (
<div className="flex">
<input
type="text"
value={option}
onChange={() => editWidthOptions(option, index)}
/>
<button type="button" onClick={() => removeWidthOption(option)}>
<TrashIcon className="w-5 h-5 mb-3 text-gray-500" />
</button>
</div>
))}
</div>
);
};
export default Test;
You can just the controller component for this as for all other fields.
Since you have not shared any of you code here is a generic multi-select
<Controller
name={name}
render={({ field: { value, onChange, ref } }) => {
return (
// You can use whatever component you want here, the you get the value from the form and use onChange to update the value as you would with a regular state
<Test
widthOptions={value}
setWidthOptions={onChange}
/>
);
}}
/>;
https://react-hook-form.com/api/usecontroller/controller/
And in you Test component remove the state and get the props instead
const Test = ({widthOptions, setWidthOptions}) => {
const inputRef = useRef<HTMLInputElement>(null);
.
.
.

how to pass input value from child to parent in functional componenent

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.

react-hook-form: idiomatic approach for two-way mapping between form state and input state

I'm currently using Formik for my forms but considering switching to react-hook-form. One of my concerns is with inputs whose DOM state is not identical to the intended DOM state. One example is inputs with type="number", whose value at the DOM level is a string. So I might want to map a string to a number. In Formik:
<input
type="number"
value={formikProps.values.myNumericField ?? ''}
onChange={(event) => {
formikProps.setFieldValue(
'myNumericField',
event.currentTarget.value === '' ? null : parseInt(event.currentTarget.value)
);
}}
/>
What is the idiomatic way of doing this in react-hook-form?
You can use a controlled input instead and wrap it in react-hook-form's Controller.
First, create your usual controlled input, which takes at least 2 props, value and onChange, and notifies the parent about changes:
onst NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(({
onChange,
...inputProps
}, ref) => {
const handleChange = useCallback((e) => {
const { value } = e.target;
const parsedValue = parseInt(value, 10);
// If the value is not a number, we don't want to clear the input. Instead,
// just use the original string value and handle validation outside the input.
// You can be more strict about this by passing 0 or '' instead and changing
// the type attribute below to "number".
onChange(value === `${ parsedValue }` ? parsedValue : value);
}, [onChange]);
return (
<input
ref={ ref }
type="text"
onChange={ handleChange }
{ ...inputProps } />
);
});
To use this component with react-hook-form, simply wrap it in a Controller component, which will pass onChange, onBlur, value and ref to your component:
import ReactDOM from "react-dom";
import React, { useCallback } from "react";
import { useForm, Controller } from "react-hook-form";
const App: React.FC = () => {
const { handleSubmit, control, watch } = useForm();
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
as={NumberInput}
control={control}
name="number"
defaultValue=""
/>
<pre>{ JSON.stringify(watch(), null, " ") }</pre>
<button type="submit">Submit</button>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React.js setState not getting update wiith onclick

This is my sample code, it's very basic i just want to console fname once submit is clicked.
When i pressed first time i get an empty line but when pressed second time the button i get some empty value. I am attaching the screenshot for the console.
I dont want to change my code to a class and use some method to get the value in console.
screenshot for console output
import React,{useState} from 'react'
export const anyComponent = () => {
const [fname, setFname] = useState('')
const submit = (event) =>{
setFname({fname: [event.target.value] })
console.log(fname)
}
return(
<div>
<input name="fname" type="text" placeholder="Enter your First Name" />
<button onClick={submit}>Submit</button>
</div>
)
}
From MDN Docs:
The target property of the Event interface is a reference to the object onto which the event was dispatched.
In your case, event.target would point to the button and not input.
What you need is a reference to the input, you can use useRef hook for it like this
import React, { useState, useRef } from "react";
export default anyComponent = () => {
const inputEl = useRef(null);
const [fname, setFname] = useState("");
const submit = event => {
setFname({ fname: [inputEl.current.value] });
};
console.log(fname);
return (
<div>
<input
name="fname"
ref={inputEl}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Also, setState is asynchronous, that's why you wouldn't see the result in the console just after calling setFname. However you'd see the updated fName in console on the next render, that's why I've moved it out of the submit.
Without useRef
Alternatively, you can add onChange handler on input and update the state fname from there
function App(){
const [fname, setFname] = useState("");
const submit = () => {
console.log(fname);
};
const handleChange = ev => {
setFname({ fname: [ev.target.value] });
}
return (
<div>
<input
name="fname"
onChange={handleChange}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Your console log should be outside submit method. Or log event.target.value instead fname.

What does ref and node refer to in react-redux?

I'm learning react-redux from the docs and don't see what the below means. What is the ref part referring to? And node? This ref isn't used anywhere from I see. Does the ref refer to the child component's node (the input) on the DOM after it gets rendered? If so, why not just refer to the input directly?
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
This is a ref callback attribute, and its purpose is to gain "direct access" to the DOM element/class components. Using a ref you may focus an input box, get it's value directly or access a method of class component.
In this case it's purpose is to get/change the input's value, by assigning a reference to the input variable (the let input) - see comments in code.
let AddTodo = ({ dispatch }) => {
let input // the input variable which will hold reference to the input element
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) { // using the input variable
return
}
dispatch(addTodo(input.value)) // using the input variable
input.value = ''
}}>
<input ref={node => {
input = node // assign the node reference to the input variable
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}

Resources