React - Best practice with state in form components? - javascript

I'm trying to get my head around best practice regarding state in react components. I started creating a form by writing a TextField component as follows
var TextField = React.createClass({
render: function() {
const {value, title, placeholder} = this.props;
return (<div>
{title}
<input type="text"
value={value}
placeholder={placeholder}
onChange={this.handleChange} />
</div>);
},
handleChange (evt){
this.props.onChange(evt.target.value);
}
});
This is a controlled component. So the parent container has to pass a value in for the input in via props and change that value when there is a change. It seems like this is the usual approach.
My problem comes when I want to create a numeric field. For this example assume that my numeric field will allow non numeric characters to be input (the field just won't validate). I don't like the idea of having the validation of that field within the parent so this is what I wrote
var NumericField = React.createClass({
getInitialState: function(){
return{
value : ""
}
},
componentWillReceiveProps: function(nextProps) {
if(this.validate(nextProps.value)){
this.setState({value:nextProps.value});
}
},
validate : function(input) {
return !isNaN(input);
},
render: function() {
const {value} = this.state;
const {title} = this.props;
return (<div>
{title}
<input type="text"
value={value}
onChange={this.handleChange} />
</div>);
},
handleChange (evt){
this.setState({value:evt.target.value});
if(this.validate(evt.target.value)){
this.props.onChange(evt.target.value);
}
}
});
This allows the parent to set the value and update it via the "value" prop, but the "onChange" prop will only trigger when the content is valid. It feels to me like I've used a different pattern for each, and that's not good. Not sure if that's a valid feeling?
I suppose I just wanted to ask if my approach to the numeric field seems reasonable or if there is a better pattern to follow?
Just in case someone wants to know why I want a numeric field to work this way, I don't, it's just a simplified example. A more valid example would be a text area for json, that only called onChange when the content was valid json.
Appreciate any feedback

Setting state by passing in props is generally frowned upon.
Props should be immutable (like your NumericField component's title)
If you want to set an initial value it should come from the controller or store the parent component is getting it from, eg.
getInitialState() {
return({
value: FormDataStore.getInitialNumericFieldValue()
});
}
After that, any changes to the value should be handled by the NumericField component. If you need to validate do so before setting the new state, eg.
handleChange(evt) {
if (this.validate(evt.target.value)){
this.setState({
value: evt.target.value
});
/* You can also pass the new validated value
up to the parent component to hold on to
till you're ready to process the form*/
this.props.onChange(evt.target.value);
}
}
Your state will now only ever hold (and subsequently the parent component will only ever receive) the last validated value so you could even display valid/invalid message if this.state.value === input, but that's extra
Incidentally, your TextField component should also follow this pattern. Passing a changed value up to the parent just to have it passed down again as a prop defeats the purpose of having a child component. In that case I would have the JSX (and any validation process) all in the parent component instead of abstracting a child. Unless the child component could be re-used. But then I'd still let the child handle its own state.

Related

Read value of imported form component upon submit (without storing in state)

I developed a component for ReactJS as to be used as a form item in conjunction with Antd's form. But, the onFinish callback function returns undefined for the value of my custom component. Probably, Antd's form is not being able to retrieve the value from my component. That does not happen when I am only using Antd components as form items.
On the example below, MultipleEntry is the component I have developed. MultipleEntry has a nested TextArea component.
function Sandbox(props) {
return (
<>
<Form onFinish={(values) => console.log(values)}>
<Form.Item name={'myComponent'} >
<MultipleEntry />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</>
);
}
function MultipleEntry(props) {
const [value, setValue] = useState([]);
const Split = string =>
{
setValue(String(string).split(';'))
}
return (
<TextArea
onChange={(e) => {Split(e.target.value)}}
/>
);
}
I thought about two alternatives here:
Storing the values of MultipleEntry in its internal state.
Storing the values of MultipleEntry on the parent component.
But, I dont want to store the values anywhere on the client's state, since the data inside the MultipleEntry component would be too big and impactful for performance.
How can I use Antd form's API to make it read the value of MultipleEntry as a form field, and populate the input parameter of onFinish?
Antd's FormItem behaves like a high-order component and passes some props to the child component.
So, if you are using any HTML form field elements like input, then FormItem will pass the onChange and value as props to input. Hence FormItem will control the value and onChange event of that element.
But having said above you will not be able to use the styling if some validation error is there in the FormItem.
Similarly Antd's component also usages native HTML form field elements and usages value and onChange. But on top of that, you will get the styles if there are any validation errors.
Here it comes your specific case, you can use onChange and value from props and utilize the same as following
function MultipleEntry(props) {
const Split = e => {
e.target.value = String(e.target.value).split(",");
props.onChange(e);
};
return <input value={props.value} onChange={e => Split(e)} />;
}
If you are using any native HTML form elements then just split the props and pass it across the element like below
function MultipleEntry(props) {
return <input {...props} />;
}
And for splitting you can use getValueFromEvent and then split the value there.
Here is the link for the modified sandbox to especially solve your problem https://codesandbox.io/s/agitated-lake-65prw

Get Redux Form Values For Unknown Form

I have a component that contains related fields for composition in different forms. Sometimes it belongs to a FieldArray. The value of one fields must affect the presentation of another field.
Is there a clean way to access field values in this scenario?
const MyReusableFields = () => {
return (
<div>
<Field
component={TextField}
name="fieldA"
/>
{fieldA === 'something' && // How can he get fieldA's value here?
<Field
component={TextField}
name="fieldB"
/>
}
</div>
);
};
I don't think I can use formValueSelector or getFormValues because I don't know the name of the form. Even if I knew the name of the form, I don't know the exact field name because under FieldArray it will have an unknowable prefix.
I'm imagining some ugly code searching through state, but I would prefer to use a pattern supported by the framework if it exists.
I think I may have roughly answered your question here https://github.com/erikras/redux-form/issues/3103#issuecomment-312556081
If you are creating a UI widget for which there are a small number of inter-related fields Fields is perhaps what you want.
It is worth noting, that you can inject the form name from this.context._reduxForm.form - you can make a withFormName HOC to wrap connect and use ownProps.form inside connect to select the values you're looking for.
After this, it becomes architectural questions for you...
For example you seem to state that you don't know the field names because you don't know the context in which the Fields will be. But fieldNames are absolute unless you're using Form sections. For example a Field with name "hello" inside a field array will store it's value under store.form.formName.hello - not under the array prefixed value - unless you pass those prefixes to the component and manually do the prefixing (that's why fields.map returns field names).
The point is that you generally must know the names up front because you're the one specifying them to the Field/Fields components. If you are using FormSections, all three components have a name prop that will return the name taking into account the FormSection context.
For these reasons I cannot really see why you cannot use form value selectors or Fields to create conditional form value filling. Indeed, these really are the only standard ways that you can know the values for conditional rendering of fields.
Note if you use Fields you won't get proper registering/deregistering in the way you do with conditional field rendering but this may or may not matter to you.
You have to use formValueSelector form redux form. Example (In this example the template fields only show if template checkbox is checked):
import { reduxForm, Field, formValueSelector } from "redux-form";
import { connect } from "react-redux";
let UnitReduxForm = props => {
const { handleSubmit, templateCheckboxActive } = props;
return (
<form onSubmit={handleSubmit}>
<Field
id="templateChecked"
name="templateChecked"
label="Unidad basada en plantilla"
component={renderCheckboxField}
type="checkbox"
/>
{templateCheckboxActive && (
<Field
id="template"
name="template"
label="Plantilla"
items={[
{ value: "cocina", label: "Plantilla Cocina" },
{ value: "bano", label: "Plantilla BaƱo" },
{ value: "sala", label: "Plantilla Sala" },
{ value: "comedor", label: "Plantilla Comedor" }
]}
component={renderSelectField}
/>
)}
</form>
)
}
//redux form
UnitReduxForm = reduxForm({ form: "unit" })(UnitReduxForm);
//selector
const selector = formValueSelector("unit");
UnitReduxForm = connect(state => {
const templateCheckboxActive = selector(state, "templateChecked");
return { templateCheckboxActive };
})(UnitReduxForm);
export default UnitReduxForm;
More info in: [https://redux-form.com/8.2.2/docs/api/formvalueselector.md/][1]

ReactJs trying to create a filter list

I am trying to create an filter list which filter the list on typed input. But don't know why the if statement is not working.
Here is the function I wrote: `
filterList (event) {
var updatedList = this.props.array;
var filterText = this.state.text;
updatedList = updatedList.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
}
});
Can someone help me out on this here is a link to my codepen: LINK
Your filter needs to update the array in the component state to force a re-render. You don't update props to force a re-render, props are more for initial data, or consistent data, which can be updated from the top level.
Change the handle function to pass the text to the filterList function, and return the result
handleChange(event) {
var array = this.filterList(event.target.value);
this.setState({ text: event.target.value, array: array });
},
filterList (filterText) {
var updatedList = this.props.array;
return updatedList.filter(function(item){
return item.name === filterText;
});
}
Update getInitialState to use the props:
getInitialState() {
return { text:'', array: this.props.array};
}
Then use the state instead of props in your render:
var arrayComponents = this.state.array.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
})
First, when I run your codepen example, I'm not seeing any errors indicating that this.state.text is undefined. It's not filtering anything, but it is displaying the value of this.state.text. So the issue is not quite what your question suggests it is...
So how to we get this thing filtering? Basically, the idea with ReactJS components is that everything related to the current state of your component should be in this.state, and any decisions about what to display based on that state should be in the render method. And keep in mind that any time you change the state using the setState method, it's going to trigger a re-rendering of your component.
With that in mind, I'd set things up like this:
your component receives a list of photos via the array prop (I'd probably call it something else, since it's not very descriptive and it's awfully close to a reserved word in Javascript)
the state for the component has one value: text (again, not very descriptive)
the component has a handleChange handler for the input element. I would connect this to the onChange handler for the input element - don't worry, it will be called every time the value in the input element changes. This should be the only event handler on the input element and all it should do is call this.setState({ text: event.target.value });
do the list filtering in the render method. The key here is that you don't need to keep a filtered list of your photos - all you're doing with it is rendering it, so do it only when you need to (or rather, when React thinks you need to and calls the render method). So your render method will look something like this:
render() {
var myFilteredList = this.props.array.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
return true;
}
return false;
});
var arrayComponents = myFilteredList.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
});
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>{this.state.text}</p>
<input type="text" onKeyUp={this.handleChange} />
<ul>
{arrayComponents}
</ul>
</div>
);
}
That's it. This has a couple of advantages:
it keeps it simple - don't maintain any state for the component outside of this.state and if you only need it for rendering, don't maintain it at all.
it keeps your code cleaner - the way I've approached it, your component has only two methods: handleChange and render
it (should be) more performant - React is pretty good at deciding when to render based on state changes, and it tends to be better when components have minimal state. (I say "should be" simply because I haven't tested it out at all).

The best way to retrieve data of a range of different child components in React Js

I am quite new to react js. Have searched a bit but haven't got an answer to this question:
Suppose I have two dynamic input tables in my page, and each of them is a separate component. And the lines are different components (React classes) as well. And on the page, there is a save button. Once the save button is clicked, all the information of the page should be gathered and pushed to server as a JSON string.
The very obvious approach for me is to gather the information via jQuery. This will definitely work. But it makes me feel it is not the react way of doing it. Since react data is one way binding, I am not quite sure how to handle this situation more appropriately. Any suggestions?
As always, there are a few ways of doing this.
1. Using refs.
You can assign references to input fields and then loop through them to get the values. The good thing about this approach is that no events are needed, but you still have to somehow know the reference to each. It adds complexity if you have dynamic fields or heavily nested fields. This is still my preferred way, mainly because you don't need events (e.g. keyup, blur, change, depending on your usage)
2. Using state
This makes it easier to instantly get values, if the values are updated in state as soon as the user makes a change to the field. Obviously you will need to know when a change has been made so you need events.
Your event callback can do one of many things, such as
update a global state object (e.g. via redux)
update form's values (or state) object via context usage
I hope this helps plan your forms.
Please check out this simplified example: https://jsfiddle.net/352v4n72/2/
It has three components. The most basic one is Input, which informs its parent when the value is changed:
var Input = React.createClass({
render: function() {
return (
<input type="text" placeholder={this.props.name}
onChange={e => this.props.onInputChange(e.currentTarget.value)} />
);
}
});
Now, its parent component, Line:
var Line = React.createClass({
onInputChange(inputId, value) {
var obj = {};
obj[inputId] = value;
this.setState(obj, state => {
this.props.onLineChange(this.props.lineId, this.state);
});
},
render: function() {
return (
<div>
<Input name="firstName" onInputChange={value => this.onInputChange('firstName', value)} />
<Input name="lastName" onInputChange={value => this.onInputChange('lastName', value)} />
<Input name="email" onInputChange={value => this.onInputChange('email', value)} />
</div>
);
}
});
This component holds three Input components. When each of them changes, the onInputChange function is called, and this function basically aggregates all the input values, creating a whole "line" data.
The last component is Table:
var Table = React.createClass({
onLineChange(lineId, value) {
var obj = {};
obj[lineId] = value;
this.setState(obj, state => console.log(this.state));
},
render: function() {
return (
<div>
<Line lineId={1} onLineChange={this.onLineChange} />
<Line lineId={2} onLineChange={this.onLineChange} />
<Line lineId={3} onLineChange={this.onLineChange} />
</div>
);
}
});
This component holds three lines, and just like the line aggregates Input's, Table aggregates Line's. You can see the state as it changes in the console.

React.js onChange make the parent aware of the changed state

I have a component that is rendering <select> with <option> elements. When any change occurs, I would like to change the state of the component to keep the value of the currently selected option. As far as I know I don't have any other alternative for keeping this value since the props in React JS have to be immutable.
The problem comes when I notify the parent for the change. I do this using a callback from handleChange to parent's handleChangefunction. So in the child element I actually call the handleChangefunction, set the new state and call the callback (parent's handleChange). But then in the parent function when I ask for the value of the state property I receive the older one (seems like the new one is still not set).
So any ideas?
I would suggest using a single data flow pattern (like Flux or Reflux) to structure your react apps and avoid that kind of mistake and complicated reverse data flows.
From what I understood of your question, without Flux, you could do something like this.
var React = require("react");
var ParentComponent = React.createClass({
handleChange: function(newOption){
console.log("option in child component changed to " + newOption);
},
render: function(){
return (
<div>
<ChildComponent handleChange={this.handleChange}/>
</div>
)
}
});
var ChildComponent = React.createClass({
getInitialState: function(){
return {
selectedOption: 0
};
},
handleChange: function(){
var option = this.refs.select.getDOMNode().value;
this.setState({ selectedOption: option});
// I'm passing the actual value as an argument,
// not this.state.selectedOption
// If you want to do that, do it in componentDidUpdate
// then the state will have been set
this.props.handleChange(option);
},
render: function(){
return (
<div>
<h4>My Select</h4>
{this.state.selectedOption}
<select ref="select"
onChange={this.handleChange}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
)
}
});
Edit
Added a couple of forgotten semi-colons. I'm coding too much Python these days.
Edit2
Changed the code. Your problem might be that if you call the parent's handleChange with the value from the state (this.state.selectedOption), the state won't be set yet so you have to give the actual value as an argument instead. If you really want to use this.state.selectedOption, call parent's handleChange in componentDidUpdate.

Categories

Resources