Here are the codes
import React from "react";
var newForm = React.createClass({
handleSubmit: function (e, text) {
e.preventDefault();
console.log(text);
},
render: function () {
return (
<form onSubmit={this.handleSubmit("react!!!")}>
<input type="text"/>
</form>
);
}
)};
module.exports = newForm;
What I want to achieve with this is that when I submit the form, I want the string "react!!!" to be printed out in the console and prevent the default event from taking place at the same time.
Obviously, passing in the argument to "handleSubmit" function breaks the code.
Is there anyway to pass in an argument from onSubmit event to the functions attached to this event?
You can curry the function
handleSubmit: text => event => {
event.preventDefault()
console.log(text)
}
<form onSubmit={this.handleSubmit('react!!!')}>
...
Equivalent ES5 code
handleSubmit: function(text) {
return function(event) {
event.preventDefault()
console.log(text)
}.bind(this)
}
Your code is weird though because you're mixing ES6 and ES5 styles. If you're using ES6 imports, you might as well use ES6 in the rest of your code
import {Component} from 'react'
class NewForm extends Component {
handleSubmit (text) {
return event => {
event.preventDefault()
console.log(text)
}
}
render() {
return (
<form onSubmit={this.handleSubmit('react!!!')}>
<input type="text">
</form>
}
}
}
export default NewForm
Or better, since your Component doesn't use any state, you can use a Stateless Functional Component
import {Component} from 'react'
const handleSubmit = text=> event=> {
event.preventDefault()
console.log(text)
}
const NewForm = props=> (
<form onSubmit={handleSubmit('react!!!')}>
<input type="text">
</form>
)
export default NewForm
Yes, but you shouldn't do it. What you are currently doing is saying 'set the onSubmit handler to being the result of calling this.handleSubmit('react!!!')'. What would work instead is to go:
<form onSubmit={e => this.handleSubmit(e, "react!!!")}>
which is of course just the short form of saying:
<form onSubmit={function(e) { this.handleSubmit(e, "react!!!"); }}>
However, although this will work, what this is doing is defining a new anonymous function every time render is called. Render can be called often, so this will end up with a bunch of new functions being created all the time, which can cause perf issues as they need to be garbage collected and cleaned up.
What is better is to have a function which is capable of finding the data you need on its own. There are 2 different ways which I use to address this:
Either store the relevant data in props, so that it can be pulled out from this.props
Or, store the relevant data in the DOM (using HTML 5 data attributes), and then pull them out using javascript. Since you are given the event target in the event handler that is called, you can use it to pull out any data attributes you have stored. For example you might declare your form like this:
<form data-somefield={something.interesting} onSubmit={this.handleSubmit}>
which you could access in handleSubmit by going:
handleSubmit: function(e) {
console.log(e.target.dataset.somefield);
}
The caveat there being that a) storing data in the DOM like this doesn't feel very idiomatic React, and b) if you use this for onClick events with nested elements, you might find that you have to climb up the DOM hierarchy to find where your data is stored. As an example if you have a div with a data attribute and an onClick but it contains a span and an image, when you click the image it's possible that is what the onClick handler will see as it's target element, meaning you'd have to go up to the parent to find the div with the data attribute you're looking for.
So it's probably best to just include the data you want in your component's props and pull it out from there in your event handler.
Related
Background
So I have a simple example, a Form component (Parent) and multiple Input components (Children). Each individual Input component will have an useState hook to initialize and manage its value's state.
Issue
As with most forms, I would like to submit all of the data to a backend for processing. However, the issue is that I cannot retrieve the state of value from each Child Input component.
// app.jsx
import Form from "./Form";
export default function App() {
return <Form />;
}
// Form.jsx
import React from "react";
import Input from "./Input";
const handleSubmit = (e) => {
e.preventDefault();
console.log("Wait, how do I retreive values from Children Inputs?");
};
const Form = () => {
console.log("Form render");
return (
<form onSubmit={handleSubmit}>
Sample Form
<Input initial="username" name="user" />
<Input initial="email" name="email" />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
// Input.jsx
import React from "react";
import useInputValue from "./useInputValue";
const Input = ({ name, initial }) => {
const inputState = useInputValue(initial);
console.log(`${name}'s value: ${inputState.value}`);
return <input {...inputState} />;
};
export default Input;
Plausible Solution
Of course, I can lift the Input states up to the Form component, perhaps in an obj name values. However, if I do that, every time I change the Inputs, the Form will re-render, along with all of the Inputs.
To me, that is an undesirable side-effect. As my Form component gets bigger, this will be more costly to re-render all inputs (inside the form) every time one input changes.
Because of that, I would like to stick with my decision of having each individual input manage its own state, that way if one input changes, not all other input will re-render along with the Parent component.
Question
If each of the Child components manages its own state, could the Parent component access the value of that state and do actions (like form submission)?
Update
Many answers and comments mentioned that this is premature optimization and the root of all known evil; which I agree with, but to clarify, I am asking this question with a simple example because I wanted to find a viable solution to my current and more complex project. My web app has a huge form (where all states are lifted to the form level), which is getting re-rendered at every change in its inputs. Which seemed unnecessary, since only one input is changed at a time.
Update #2
Here is a codesandbox example of the issue I am having. There are two forms, one with all states managed by individual Child input components, and the other has all states lifted up in the form level. Try entering some values and check the console for log messages. One can see that with all states lifted to the form level, every change will cause both inputs to be re-rendered.
I think yes, you can share state. Also there are 3 options:
I recommend you to use such library as Formik. It will help you in your case.
You can share state using useState() hook as props.
Use such tools as Redux Toolkit (if we are speaking about memoisation), useContext() and etc.
If the thing you want is getting final values from input, assign ref to each input and access using emailRef.current.value in the submit function.
import { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
const [value, setValue] = useState('');
return <input ref={ref} value={value} onChange={(e) => {setValue(e.target.value)}} {...props} />;
});
const App = () => {
const emailRef = useRef(null);
const submit = () => {
const emailString = emailRef.current.value
};
return (
<>
<Input ref={emailRef} />
<button onClick={submit}>Submit</button>
</>
);
};
If the parent needs to know about the childs state, you can
move the state up. This is resolved by passing down a setState function that the child calls (the states data is available in the parent)
use a context https://reactjs.org/docs/context.html
use a state management library e.g. redux
In your simple case I'd go with 1)
The answer is probably, yes. Because it works, and we use it all day, I know. I am asking to be %100 sure and for future learners of React. I couldn't see it in the official docs of React, except giving an example for passing multiple arguments to event handlers alongside the event object. So for example: As you can see onFormSubmit, although not having an event argument inside the JSX reference, it has access to the event object to do stuff (preventing page refresh on this example) at the execution.
If you write the onFormSubmit as an inline arrow function like onChange handler, you need to pass the event object, then it is not automatic.
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = { term:''}
this.onFormSubmit = this.onFormSubmit.bind(this)
}
onFormSubmit(event) {
event.preventDefault()
console.log(this.state.term)
}
render() {
return (
<div className="ui segment" >
<form onSubmit={this.onFormSubmit} className="ui form" >
<div className="field" >
<label>Search</label>
<input type="text"
value = {this.state.term}
onChange={event => this.setState({ term: event.target.value })} />
</div>
</form>
</div>
)
}
}
export default SearchBar
You have defined the function to your onChange event handlers which calls the submit method passing the necessary arguments implicity.
There is nothing special about event handlers in React. Every function, if defined works this way.
const a = [1, 2, 3];
function print(elem, idx) {
console.log(elem, idx);
}
// here you have defined the print function.
// the elem and idx are passed implicitly to run the function.
a.forEach(print);
or
// now, rather than defining the function here.
// you have used another arrow function.
// then you need to pass the arguments explicitly.
a.forEach((elem, idx) => print(elem, idx));
React approaches the event handling a little bit differently, using Synthetic Events but this is how callback handlers work generally. If you use a function reference there, the event object is the only argument passed to your function. So, if the event object is the only argument you want to get then you don't need to use an arrow function.
If you want to pass other variables alongside with the event object, then you can use an arrow function.
<form onSubmit={e => this.onFormSubmit(e, otherVar)} className="ui form" >
So, your callback gets the event parameter and you pass this to your handler function with your other variables.
onFormSubmit(event, otherVar) {
event.preventDefault()
console.log(otherVar)
}
Edited. People have suggested passing in values to my action creator but I've tried that every way I can think of and it fails to work.
I'm currently getting my first taste of Redux and trying to get my call to mapDispatchToProps to read information in a form on button click but I'm not clear as how to get it to do so. The form component is rendered by React, and so it's a question of being able to bind when it's available but Redux is a monkey wrench I don't know how to compensate for yet. Essentially I have this for my component:
import React from 'react';
import { connect } from 'react-redux';
import { action } from '../actions/actionFile';
const Add = (props) => (
<div className="add">
<input className="field-one" type="text" placeholder="One" />
<input className="field-two" type="number" placeholder="Two" />
<input className="field-three" type="number" placeholder="Three" />
<button onClick={() => props.addItem('Literally anything')}>+</button>
</div>
)
const mapDispatchToProps = (dispatch) => {
return {
action: () => dispatch(action({
// I have three fields I need to update in the store.
}))
}
}
export default connect(null, mapDispatchToProps)(Add);
And this for my actions file:
import { ADD_ITEM } from '../constants/items';
export const addItem = (val) => {
return {
type: ADD_ITEM,
val
}
}
But if I run this and set a breakpoint inside the action creator the val value is undefined. For some reason Redux isn't letting me feed dynamic data to the action creator and I don't understand why.
Obviously I can't just pull the information with querySelector because the form doesn't exist when the callback is loaded. If I fill the object passed to the action with hard-coded dummy values it works, but I'm not able to pull in data from the form fields. I'm not even clear as to where to start with this. Any direction is greatly appreciated.
You can't access any data from Redux state, or from inside the component, in mapDispatch, because it is used as part of a wrapper component that goes around your actual component (and thus doesn't have access to anything in your component's state).
Your main options are:
Pass any necessary values as arguments into the function, like props.action(a, b, c)
Switch to using the React-Redux hooks API (useSelector and useDispatch), which lets you access data from the Redux store inside of your function component. You can then capture these values while defining a click handler.
Also, as a side note: if you are going to use connect, you should use the "object shorthand" form of mapDispatch rather than defining it as a function.
You just need to add onChange event handler to your three fields and store data of each input into your component state.
Then on button click dispatch action using this.props.action with data in your state.
In this way you can get all of your data into redux.
render() {
return <button onClick={() =>this.props.toggleTodo(this.props.todoId)} />
}
const mapDispatchToProps = dispatch => {
return { toggleTodo: todoId =>dispatch(toggleTodo(todoId)) }
}
For reference -Connect: Dispatching Actions with mapDispatchToProps · React Redux
As far as I know, there are three ways to define functions in JavaScript.
1. Declaration
function handleEvent(e) {}
2. Assignment
var handleEvent = function(e) {}
3. Arrow
var handleEvent = (e) => {}
I have been searching for hours trying to find information on which of these options is the preferred way to declare handlers in functional React components. All the articles I found talk about class components, binding, etc. But with the new Hooks out, there must be standard for defining them inside the function as well. (After all, functional components have always existed anyway.)
Consider the following component, which declares three handlers, which are examples of different behaviour that you might need in a React component.
function NameForm(props) {
const [inputName, setInputName] = useState("");
useEffect(() => setInputName(props.initialValue), [props.initialValue]);
const handleInputChange = function(event) {
setInputName(event.target.value);
};
const resetForm = function() {
setInputName(props.initialValue);
props.onReset();
};
const handleFormSubmit = function(event) {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
};
/* React-Bootstrap simple form example using these handlers. */
return (
<Form onSubmit={handleFormSubmit}>
<Form.Group controlId="NameForm">
<Form.Control
type="text"
placeholder="Enter your name here"
value={inputName}
onChange={handleInputChange}
/>
<Button type="submit">Submit</Button>
<Button onClick={resetForm}>Reset</Button>
</Form.Group>
</Form>
);
}
All of these handlers are directly passed as callbacks into other components. They might be called whenever, but at that exact moment, we need to have access to the current values of props and any state like inputName. Additionally, as you might have noticed, the handleFormSubmit handler also calls the resetForm handler.
What would be the recommended approach to defining the handlers from a performance perspective? Can it be avoidable that they are redefined on every render?
Does useCallback also fit in here somewhere?
The current standard is to declare the handlers as constants for immutability and as arrow-functions for binding purposes.
function NameForm(props) {
const [inputName, setInputName] = useState("");
useEffect(() => setInputName(props.initialValue), [props.initialValue]);
const handleInputChange = (event) => {
setInputName(event.target.value);
}
const resetForm = () => {
setInputName(props.initialValue);
props.onReset();
}
const handleFormSubmit = (event) => {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
}
/* React-Bootstrap simple form example using these handlers. */
return (
<Form onSubmit={handleFormSubmit}>
<Form.Group controlId="NameForm">
<Form.Control
type="text"
placeholder="Enter your name here"
value={inputName}
onChange={handleInputChange}
/>
<Button type="submit">Submit</Button>
<Button onClick={resetForm}>Reset</Button>
</Form.Group>
</Form>
);
}
All of these handlers are directly passed as callbacks into other
components. They might be called whenever, but at that exact moment,
we need to have access to the current values of props and any state
like inputName
As currently constructed we meet the requirements for your description. Since props and state are defined as higher level data that the component has access to, all the event-handlers have access to them. And when used as a call-back in another component, they will remain bound to the initial component where they were defined in.
Which means if you have an event-handler like this:
const handleInputChange = (e) => {
setValue(e.target.value)
}
And you pass it into a ChildComponent like this:
<Child handleInputChange={handleInputChange}/>
And the Child uses the prop/event-handler like this:
<input onChange={props.handleInputChange}/>
You would be getting the event from the Child input, but you would be updating the state of the Parent, the original component that defined the event-handler.
There is virtually no difference between the "declaration", "assignment" and "arrow" approaches. The only thing that matters is that you don't always create new instances of the handler functions on each render. For this, use the useCallback hook to memoize the function references:
const handleInputChange = useCallback((event) => {
setInputName(event.target.value);
}, []); // `setInputName` is guaranteed to be unique, from the React Hooks docs, so no need to pass it as a dependency
const resetForm = useCallback(() => {
setInputName(props.initialValue);
props.onReset();
}, [props.onReset, props.initialValue]; // these come from props, so we don't know if they're unique => need to be passed as dependencies
const handleFormSubmit = useCallback((event) => {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
}, [props.onSubmit, resetForm, inputName]); // `resetForm` and `inputName`, although defined locally, will change between renders, so we also need to pass them as dependencies
useCallback docs: https://reactjs.org/docs/hooks-reference.html#usecallback
In functional component we don't need access to this(even more - most linters will give you warning in such a case - and for a reason!). So it does not matter if we use arrow expression or declare a function.
But performance matters. Whatever option from your list you choose it will be recreated on each render. Declaring function itself is not really big deal but:
passed down to child it may cause unnecessary re-rendering for PureComponent/React.memo-wrapped components.
May cause redundant run for useMemo/other useCallback/useEffect if later one includes your handler into dependencies list.
So either declare handler out of the component(once it does not rely on internal state at all) or use useCallback. Beware it needs you explicitly list of all dependencies - don't ignore that. Otherwise result handler may operate on stale data.
I have a very basic comment form that takes some text input from a user and sends a POST request via AJAX to create a new comment.
var CommentForm = React.createClass({
propTypes: {
// ...
// ...
},
handleFormSubmit: function(e) {
e.preventDefault();
var component = this;
return $.ajax({
type: "POST",
url: this.props.someURL,
data: // ???? - Need to figure out how to serialize data here,
dataType: "json",
contentType: "application/json",
success: (function(response){ alert("SUCESS") }),
error: (function(){ alert("ERROR"); })
});
},
render: function() {
var component = this;
return (
<form onSubmit={component.handleFormSubmit} className="comments__form" id="new_comment" accept-charset="UTF-8">
// ...
</form>
);
}
});
I need to serialize the form data to send along with my POST request, but I'm not sure how. I know in JQuery I can select the form element and do something like $("#my_form").serialize(), but I can't seem to call that from inside the React component (not sure why?)
Some other stackoverflow answers suggested adding a ref='foo' to each relevant DOM element and then I can access them with React.findDOMNode(this.refs.foo).getValue();. This works fine but it leaves me to manually construct the whole serialized form data string, which isn't pretty if the form is a lot longer and complex.
// Sample serialized form string
"utf8=✓&authenticity_token=+Bm8bJT+UBX6Hyr+f0Cqec65CR7qj6UEhHrl1gj0lJfhc85nuu+j2YhJC8f4PM1UAJbhzC0TtQTuceXpn5lSOg==&comment[body]=new+comment!"
Is there a more idiomatic way to approach this - perhaps a helper that will let me serialize my form data within ReactJS?
Thanks!
You would usually use the component state to store all the information.
For example, when an inputs text is changed, you would change the state to reflect that:
<input type="text" onChange={ (e) => this.setState({ text: e.target.value }) }
Then, when it comes to submit the form you can access the value using this.state.text.
This does mean that you would have to build the object yourself however, although, this could be achieved via a loop:
this.state.map((value, index) => {
obj[index] = value;
});
and then send obj as the post data.
UPDATE
The only performance issue with updating the state is that the component is re-rendered each time. This isn't an issue and is the way react components work, as long as you watch for the size of the render method and what it is doing.
Forms in the usual HTML <form> tag sense, don't exist within React. You use the form components to change the state. I suggest reading up on React Forms
In terms of the UTF8 flag, that would be the value of a hidden field, so you could use refs in this case to get that value for your object:
<input type="text" ref="utf8" value="✓" />
obj.utf8 = this.refs['utf8'].value
For those who want to use Serialize form in ReactJs with functional component try this:
1. Create a state
const [form, setForm] = useState({});
2. Create a function handler
const handleForm = (name, value) => {
setForm({...form, [name]: value});
}
Now you got your object here:
const handleSubmit = () => {
console.log(form);
}
3. In your render
<form>
<input onChange={(e)=>handleForm('name', e.target.value)} />
<input onChange={(e)=>handleForm('phone', e.target.value)} />
<button onClick={()=>handleSubmit()}>Submit</button>
</form>
UPDATE 2022
Or use useForm
useForm is a custom hook for managing forms with ease. It takes one object as optional argument. The following example demonstrates all of its properties along with their default values.