React Final Form uses a render prop pattern in its form component, and I am trying to understand how I can modify it to pass it additional arguments.
According to it's documentation, it passes the following props to the render function - including (crucially) the FormState.
Let's take a look at my particular implementation, focusing on the render function:
<Form
onSubmit={() => {console.log("wow coolio you submitted a form!")}}
initialValues={initData}
validate={validateMyForm}
render={({ handleSubmit, reset, submitting, pristine, values }) => {
formSubmitHandler = async () => {
await handleSubmit()
return values
}
return(
//... form components with the values prop passed into them below
//...i.e. <Part_1_Of_Form values={values}/>
If I understand correctly, you will note I destruct the render prop object in the JSX {({})} and get the values from the FormState.
I then pass them to various modular form components below - like <Part_1_Of_Form/> in order to make each part of the form react to state changes in other parts of the form. But my problem is I want it to react to more than the values. For example, if I want to display a label conditionally based on if another label option (or another option) is selected in another part of the form, I can't access it because I only get access to the values - not the labels.
Example, with a currency selection tool to propagate throughout the form using state:
<Field
name="part_one.currency"
component={MyRFFInputSelectComponent}
options={[{value: 'USD', label: '$ (USD)', symbol: '$'}, {value: 'EUR', label: '€ (EUR)', symbol: '€'}]}
required={true}
className="form-control">
</Field>
I can pass the value (let's say USD) around other parts of the form, using the render props and the values part of that returned object - but I don't want to. I want to pass the symbol - the value (USD) belongs to the database (it's only USD because the DB wants it as a string like that) - but the other parts of the form should display the symbol instead of the value once I select my currency in the top part of the form.
Example of what I have to do now elsewhere in the form:
<Field
...
append={values['part_one']['currency']}
Example of what I want to do:
<Field
...
append={symbols['part_one']['currency']}
Or maybe even better:
<Field
...
append={allFormData(notjustvalues)['part_one']['currency']['symbol']}
So that each input that needs a price can show a currency in the appended label.
Does this usecase make sense?
Is there another way to pick up the symbol or a way to add it to the FormState? I think its probably bad for perf to pass something like allFormData around just to get one custom symbol. I can't use the symbol for the value because my backend developer will cry. 😢
If I'm understanding you correctly, your value for part_one.currency needs to be the entire {value: 'USD', label: '$ (USD)', symbol: '$'} object.
Then you can do values.part_one.currency.symbol elsewhere in your form. If you only want the value to be 'USD' when you submit, you'll need to handle that in your onSubmit function. Does that help?
Related
I'm learning React and decided to try creating a form with different inputs. After copy-pasting the labels and inputs for all different input types 10 times, I felt like I should try to create a component that would render different inputs based on the props passed to it.
I created the following component that works fine for inputs like text, password, email, number. I use it like this:
<FormGroup
inputType="text"
inputName="firstName"
inputPlaceholder="First Name"
</FormGroup>
And here's the component:
const FormGroup = (props) => {
return (
<div className="form-group">
<label className="form-label" htmlFor={props.inputName}>
<p>{props.inputPlaceholder}</p>
</label>
<input
type={props.inputType}
name={props.inputName}
placeholder={props.inputPlaceholder}
/>
</div>
);
};
export default FormGroup;
The problem is that as soon as I started thinking about inputs like upload, range, checkbox, date, range, I started wondering if creating a universal input component that can handle any input type feasible. Maybe the component will become too big/complex/confusing. Should I continue trying to create such universal input component, or try an alternative?
Yes, you could.
No, you (probably) shouldn't.
Technically, there is no limitation in putting this together, and for input that is quite similar (text input, date input, number input) its actually probably a nice thing to have.
However, when you jump across to an element like <select> where its props are quite different, it will not only make the internals of your component more complex, but the components consuming your universal component more complex too, which imo, is a much more important consideration.
I typically have the following components in most typical web projects:
<Input /> (supports text, number and date)
<Select />
<Checkbox /> and <Checkboxes /> as a wrapper
<Radio /> and <Radios /> as a wrapper
<Upload /> - its own beast
It is doable but it become very complex as you add support to more types.
For form handling you'd better use a library that also manages the state of your inputs.
Formik is a popular and solid choice at the moment
https://formik.org/
Try to go through their docs, even if you decide not to use it, it should give you a better understanding of the requirements for what you are trying to achieve
There is a principle in programming called singled-responsibility principle that'll help you better structure your code. Keep things simple! The first problem I see is that your component is doing multiple things. It does display a form group which is about grouping elements together (children), like a label and a form element. But it goes on displaying also the actual label and an input (what about other types of form element?). It should have just been a wrapper.
An <input> already accepts different types of input. But a FormGroup can not only accept an input but other things, too, like select and what not. So you'll then add more complexity to your component.
Javascript is great, it's permissive, you can do all sort of things. Imageine at some point, you'll decide to add Typescript to your project. Or better yet, you'll want to write tests for your component. How are you going to test such a monster component. Grouping things like that is great, it saves you some time for a while but at some point, you'll want to have the liberty to add whatever you want as children. As it is now, you'll have to modify your component each time you want to add something different under your component. Again, it should have just been a wrapper.
And lastly, in general, when you add if's to your component (you'll have to at some point and if I understand correctly where you're going) which is a signal that your component has many behaviors which goes against the single-responsibility principle, ask yourself if it would not be better to separate this into two or more components. I've seen codes where there was a component that would take a boolean and would either render this or that jsx whether we would pass true or false. And the props that we pass in each situation would also be different. This is a good candidate for refactoring...
Oh, one other last thing to note. You probably want to use reactsrap (assuming you're using bootstrap that is). It has a FormGroup and anything else. You won't have to create these components yourself.
Use children or props?
Sometimes, you probably wonder why you have to bother passing children each time, especially when it's the same code over and over again. That goes against DRY... and that's what you wanted to avoid. But you lose control over the children. If you want to add some classes to the inputs, to the label or whatever else, you'll also have to pass these by props, too... labelClasses, inputClasses.
Let's take an example, an antd table:
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
<Table dataSource={dataSource} columns={columns} />;
Okay, this is great. I won't have to repeat the columns myself. But what you can do with these columns are somewhat limited.
Whereas, with this:
<Table dataSource={dataSource}>
<Column></Column>
</Table>
I can take the Column and wrap it into a styled-component:
const MyStyledColumn = styled(Column)``
<Table dataSource={dataSource}>
<MyStyledColumn></MyStyledColumn>
</Table>
Pretty cool.
I call an API, get a payload and load it into state. Eg:
Payload : {
id: 1,
firstName: 'Craig',
surname: 'Smith',
anotherField: 'test data',
....
}
(Note, there are around 20 fields in real life)
On my react screen, I have the fields to display this data. At the moment, for every field, I have an onChange function. So:
this.firstnameOnChange = this.firstnameOnChange.bind(this);
and then the function:
firstnameOnChange() { ....}
Is there a simpler pattern to maybe lessen the amount of methods I need to create? Some sort of generic OnChange event that takes a name, and the value, which can then be used to update the state?
I can bind to something like:
myOnChange(fieldName, value) {
// Somehow find the 'fieldName' in the state and update it's value with 'value'
}
Is this a valid pattern? Or is there a better way? Or should I stick to separate OnChange methods for each field?
If I can go generic, how would I find the name of the field in the state to update?
No this is not recommended to write onChange for every field. You can write a generic onChange method like this:
handleChange= name => event => {
this.setState({payload: {...this.state.payload, [name] : event.target.value}});
}
for example you have an input like this :
<Input onChange={this.handleChange('firstName')} placeholder="Please Enter" />
PS here we are invoking handleChange directly so we use currying means a new instance being created for every invocation. There is another approach by extract name form event.target and create a name attribute in your input. Then you don't need to pass input name explicitly.
Since the state is an object that means you can update it's key and value.
All you have to do is pass down the correct parameters to a generic update function which will update the state.
Here's one way you can do it:
https://codesandbox.io/s/v382v6l483
Since your payload is an entry one level down, we'll be using the spread operator and spread out the payload object.
Update the value that you need and finally returns the payload to setState function to update the state and rerender the component.
You can bind this function to each field and keep your state updated.
Read more here on handling events and binding functions: https://reactjs.org/docs/handling-events.html
I use controlled components in my React forms where I'll tie the value of a field in the form to a property of an object I use to collect data -- see below. For numeric fields in my forms, I like using properties of numeric type but I've discovered an unpleasant behavior and want to see how others handle this.
// In my reducer, I have this object that I use to collect data
newItem: {
description: "",
reward: 0
},
// Then, in my component, I'll tie the input to a property of the object
<input name="reward" type="number" value={this.props.newItem.reward} onClick={e => this.myHandler(e)} />
I'll typically set the initial value of a numeric field to 0 which then renders a 0 in the form for the corresponding field. This is a bit unfriendly because user has to first select the value, then type a new one in -- I realize there are keyboard and mouse tricks one can use but most users don't know them and they will simply use the mouse to highlight the value, then type a new one in.
But the real problem I have is that if the user deletes the 0, we end up with NaN being displayed in the field -- see below before and after.
Other than using a string type for my property then parsing it into a number when I need to, what other options do I have to handle this scenario?
This is just a suggestion, but maybe you could have a onChange() function wich would look if the current value is a number, if not, set the value to 0?
onChange(event){
if( isNaN(event.target.value) ){
// do your stuff here
}
}
You could probably get around it by putting a conditional in the value parameter and assigning a default numeric value:
<input name="reward" type="number" value={isNan(this.props.newItem.reward) ? 0 : this.props.newItem.reward} onClick={e => this.myHandler(e)} />
Instead of using the initial value as you are currently doing, you can display a placeholder value like this:
<input type="text" placeholder="0" ...
More info from Mozilla. The '0' will disappear when a user clicks on it.
To fix the NaN issue, one way would be to store the value as a string instead of as a number, and make the form a controlled component. This is detailed in the React Docs.
For example:
The input:
<input type="text" placeholder="0" value={this.state.value} onChange={this.handleChange} />
handleChange:
handleChange(event) {
if (!isNaN(event.target.value) {
this.setState({value: event.target.value});
}
}
This should ensure that only numbers can be entered in the box.
I have an input box (textfield component, Ext.form.field.Text) on my form.
I place the data into this componen using a function
window.getForm().loadRecord(myRecords)
But data represented into myRecords array hasen't been formatted. I need a "pre-renderring" function in a MyTextField component (which extending a standard field.Text component). How I can implement this function?
If you need a pre-rendering function, my guess is that you also need a pre-save function to convert values in the field back into values in your record.
For these two tasks, you can override the rawToValue and valueToRaw functions on your field.
I am using ReactJs for building my web application. The application has a state variable which contains the state of the application. My application has a feature where user see three radio buttons (thousands, millions, actual). When user clicks thousands radio button, I need to show all the numeric values in thousands. Same login apply for millions.
The state variable contains array of object. Each object can have its own sets of properties. Each property can be numeric, object, boolean or string.
What I want: I want to go over each property, check if it is numeric type, if true, divide each value by 1000 in case of thousand radio button is checked (similar logic for million button).
What is the most efficient way to achieve desired result.
for formatting, use something like https://www.npmjs.com/package/format-number.
In render function of your component, use array.filter to filter out any non-numeric values, then map the result to something like renderValue function. In this function, check currently selected formatter, which will be saved in state (I'll get to it in a second) by this.state.formatter. Format your value using format-number package and your formatter from this.state.formatter and display it.
Filtering will look something similar to:
this.props.values.filter((value) => !isNaN(value)).map(this.renderValue)
When it comes to setting your formatter to state, create <Input type="radio" name="format" value={1000} onChange={this.handleChange}/>and your handleChange function:
handleChange: function(event) {
this.setState({formatter: event.target.value});
}