Need some suggestions for a dynamic form using React - javascript

I'm building an enterprise-level application and I need some tips and suggestions for handling dynamic form.
The fields of the form are totally dynamic and they come differently for each user.
I loop through each field(fields come from an API call) on a file called renderUiType.js and based on a property of the field called uitype, we render different Inputs.
For example if uitype===1{render TextField}, if uitype===2{ render Checkbox } and so on...
So far the displaying part is correct but now I want to save the values of each field rendered and have them all in an object so I can do a POST API Call
So my question is, how can I do that? Should I create an onChange handler function for each form-element at the main file renderUiType.js and then pass it with props to the form-elements components or should I use Redux?
Any suggestion/article or anything is welcomed. Thank you
The folder structure looks like the image below(just in case it helps to understand what I ask)
..

You can use one callback function and use it in each onChange component specific handlers. You could have everything in state of the Form if you would like hidden under the unique keys/id, so you don't need to have Redux. f.e.
if (uitype===1)
{render <TextField value={this.state[fieldId]} onChange={this.onChange}/>}
if (uitype===2)
{ render <Checkbox value={this.state[fieldId]} onChange={this.onChange}/>}
or to simplify:
const getComponentByUIType = (uiType) => {
switch(uiType) {
case 1: return TextField
case 2: return Checkbox
}
}
// ...
onChange = fieldId => value => this.setState(state => ({fieldId: value}))
//...
render() {
getComponentByUIType(uiType).map(Component => <Component value={this.state[fieldId]} onChange = {this.onChange(fieldId)} />
}

Using Redux for this shouldn't be necessary unless you need to access this form's state somewhere outside the form. If you only need the form info to do a POST, I would keep all the data inside one component's state.
Just use the unique ID provided by the IP (the one you were gonna use for the POST) to build that state object. Every field will have an onChange that updates the main form component's state, and then that same value from the state is passed in to each field as a prop.

Related

React components rendering with wrong props when changing filter

I have a page in a react app that uses a radio form to filter different objects of data that are being passed to the page.
The problem I am encountering, is that when I change the filter, (click on a different option on the radio form), only some of the data in the resulting list changes. A simple example of what happens is as follows:
Option one is selected with all the old data
Option two is selected, but only some of the new data comes through
First, I use an axios request to get an array of objects that will be used for the data:
componentDidMount() {
axios.get("xxxxxxxxx")
.then(result => {
this.setState({
data: result.data
});
});
Then, I create an array that filters the data from axios by an attribute based on which radio option is selected in the form:
let filteredData = [];
filteredData = this.state.data.filter(thisData => thisData.attribute === "attribute1");
Finally, I map all of the filtered data in the render function:
filteredData.map(filteredItem => ( <MyComponent key={i++} itemInfo={filteredItem.info} /> ))
In the definition of MyComponent, I use props to access the filtered item's info and put it into the table like this:
<td>{this.props.itemInfo.thisDataPoint}</td>
I'd love to hear if anybody has any idea why some of the components data updates when the filter changes, but not all of it. It seems weird to me that some data changes but some does not.
I have tried converting the props into state so that the component re-renders on the change but that did not work.
Thanks again for any help I get :)
given that filteredData is correct, and based on your code the issue must be on key={i++}. using some kind of implementation index can often lead to rendering problems, react will have trouble to distinguish the components since it uses the key to track them.
you should provide some unique identifier to each component as key like key={filteredItem.id}. if you don't have it, you can generate it with some library like uuid.

Multiple input fields painted via loop not updating new values on insertion in react and redux

I am working in React with Redux.Things looked very free flowing untill I came across one problem.May be I am not implementing this particular part the right way.However I need your help in this.
So I have a react component and on componentWillMount() I get data from an API call
I have my actions and action creators in place such that it dispatches a getSupplier action,I store them in redux state as supplierList which is an array of objects like
[{name:'Bob',score:10,id:1},{name:'John',score:10,id:2}];
Now My UI comprises of a table with name and score as table data.Score will be an input field which will initially have score values which came from the getSupplier service.
I have looped and returned the jsx inside my component as follow
render(){
var data = this.props.supplierList;
if(data.length){
return (
<div className='fx-container'>
{
data.map((v,i)=>{
(<div class='fx-table-row' key={v.id}>
<div class='fx-supp-name'>{v.name}</div>
<input
type='text'
className='fx-input'
value={v.score}
onChange={(e)=>{this.handleInputChange(e.target.value)}}
onBlur={(e)=> {this.props.actions.updateScore(v.id,e.target.value)}}
</div>);
});
}
<div>
);
}
else{
return null;
}
}
So I am painting my intial values and on inserting of new value and on blur I need to update the new value by dispatching an action which is written in action.js file
The probblem I am facing is that I am not able to insert any value into the input.I am not able to hit backspace and insert new values.
Kindly help as I am new to react and redux.
PS: Above code is a dummy code just to illustrate my problem.
First, you might not want to call an API in componentWillMount. See this post: https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
The reason you're not able to modify your input is that this is a controlled field and you're not calling an onChange event. See https://facebook.github.io/react/docs/forms.html
It's because of value={v.score}
The value of Input field is controlled by v.score and it is from props
So in the this.props.actions.updateScore() function you should change the score so that the props change.
The value of Input field will never change unless the props are changed
Also you should use onChange() instead of onBlur()

How to update a field value after action dispatched with redux

I would like to populate a form after an ajax call with redux.
My case is pretty simple:
I have a simple user form (with only one text field for now), it's a react view bound to the state with connect().
I call a rest API to fetch the user.
When the api call is done, an action is dispatched with the user.
A reducer update the store state with the user.
I would like to populate/update the form with the retrieved values.
Solution 1:
If I set the value from the props like that:
const Field = ({ field, onFieldChange, value }) => (
<input
value={value}
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
It works but I get this warning:
Field is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
I understand why I get this error as I should not use a component to display something and also be able to update it.
I also tried to use the defaultValue props but this is only used at the component creation (and we don't have the user yet). After the ajax call return, defaultValue cannot be called.
Solution 2:
Use redux-form with a custom plugin to update the form model each time the state get updated. I don't find this solution really clean but maybe I'm wrong.
I really thin that I'm going in the wrong direction and that it should exist a better way.
Does somebody already faced this kind of issue?
I encountered the same problem when I was trying to pass undefined as the input value.
To fix this, ensure that you are passing at least empty string to the input, not undefined
const Field = ({ field, onFieldChange, value }) => (
<input
value={value || ''} // <- add fallback value here
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
Actually you might try to make your component statefull - store and manage value of input inside of it (they say it's ok).
Or if you really need this value in the store use redux-form, I have realy good experience of using it (you'll have to write less boilerplate code).
By the way, you will not have to use any custom plugin, you can use initialValues, see more here
The solution above will work for sure, but it doesn't seem to be nice.

Submit button from outside Redux Form v6.0.0

This question relates to Redux Form v6.0.0 (in time of writing this question it is v6.0.0-alpha.15).
How can I get form validation status (like pristine, submitting, invalid) from outside of form component ?
Let me give an example. This is "classical redux-form" pseudo-structure:
<Form(MyExampleForm)>
<MyExampleForm>
<input name="title" ... />
<submit-button />
...where <submit-button> in JSX looks like this:
<button type="submit" disabled={pristine || submitting || invalid} >Save</button>
But in my application, my submit button must be outside of the form, placed on different place in the application (let's say in application header, on the top of whole application).
How can I get those pristine, submitting, invalid from outside of Redux-Form? (Without really nasty hacking, if possible :-))
How can I submit that form?
Just decorate another component with same form name and you have access to same state variables there. Also you can pass it onSubmit function from parent and be able to submit all the Field values from wherever you define them as they are all from redux state, not HTML of current form instance. (it is kind of "hacky" way, but it feels right)
The submit function is defined from parent, not shared in state, so you can have it different for every instance.
class MySubmitForm extends React.Component {
render() {
return (
<button
onClick={this.props.handleSubmit}
>
{this.props.pristine ? 'pristine' : 'changed'}
</button>
)
}
}
export default reduxForm({
form: 'myFormName'
})(MySubmitForm);
redux-form works with React Redux to enable an html form in React to use Redux to store all of its state.
If "outside of Redux-Form" means still redux application, you can try to store those properties in state by dispatching some actions.
In forms: you're detecting whats happening (when its invalid etc), dispatch an action to modify a state,
In "outside" part: you're passing a proper property to component (with those you need) and depends on that you disable a button.
in latest redux-form version 6.0.2:
it is possible to access form state pristine, submitting, invalid via selectors http://redux-form.com/6.0.2/docs/api/Selectors.md/
it is possible to export redux-form action creators http://redux-form.com/6.0.2/docs/api/ActionCreators.md/
Maybe you can have a look at the Instance API of redux-forms. It provides access to a submit() method on an instance of your decorated form component. There is also a pristine Boolean property and an invalid Boolean property available (there is a request to expose the submitting property too).
There is an example here : http://redux-form.com/5.3.1/#/examples/submit-from-parent?_k=jgv0m4 (example is for 5.3.1, but the process is similar with v6 using the Instance API)
The basic idea is that by adding a ref="myExampleForm" to your form, you can pass it around with this.refs.myExampleForm. You can then check properties of the instance or call the submit() method (or any other method exposed).
Now is easier to do this. You can call the Submit action in the standalone component that has the button.
See this example:
https://redux-form.com/7.1.2/examples/remotesubmit/
If we are talking about just submitting the form, then you should provide {withRef: true} option to your redux connect() function.
Consider Row component that has child RowDetail component which has information that should be saved from Row.
RowDetail in this case could be created like this:
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
const RowDetailForm = reduxForm({form: 'row-detail-form'})(RowDetail);
export default connect(mapStateToProps, null, null, {withRef: true})(RowDetailForm);
Then, in your parent component (Row) you create your form with ref attribute:
<RowDetailForm ref={'rowDetailForm'} .../>
Submitting now is 'easy':
onSave() {
this.refs.rowDetailForm.getWrappedInstance().submit();
}
If we are talking about pristine and other form properties, then you could try to get them from your mapStateToProps function in your parent component.
const rowDetailFormName = 'row-detail-form';
const mapStateToProps = (state) => ({
rowDetailForm: state.form[rowDetailFormName]
});
But this way seems a bit hacky because as I understand redux-form API all form states were never meant to be accessed directly. Please correct me if I am wrong
I had to tackle this issue recently. I ended up passing a callback to the form, which is invoked every time the properties I'm interested in change.
I use the componentDidUpdate life cycle method to detect changes.
This should work on any version of the library.
Here is a post with sample code - http://nikgrozev.com/2018/06/08/redux-form-with-external-submit-button/

React: update a field value based on another field in dynamically generated form

I'm trying to find out the "proper" way to do the following in React:
I have a form with two fields, url and title.
Whenever url's value changes, I make an API call to Embedly to retrieve metadata about the link.
Once the metadata has been retrieved, I want to update the title field with the result.
The difficulty here is that the url and title fields are not in the same component. Here's the basic structure of the form (I'm using Formsy):
<Formsy.Form onSubmit={this.submitForm} onChange={this.updateState} ref="form">
{fields.map(field => <FormComponent
name={field.name}
type={field.type}
value={field.value}
/>)}
<Button type="submit">Submit</Button>
</Formsy.Form>
As you can see, the form loops over an array of fields and calls a generic <FormComponent/> component for each of them, which is basically a big switch that then calls the appropriate Formsy Component based on the field's type.
The logic for querying Embedly is already working inside the component for the url field, but I'm not sure if there's a way to accomplish what I want while still using the default Input component for title?
[Edit: thanks to Dan I was able to come up with a better solution for step 4 and after]
So here's the solution I came up with. I have no clue if it's the best pattern or not, but at least so far it seems to work.
Step 1: add a addToPrefilledValues method to the <Form/> component that takes a property and adds it to the prefilledValues object on that component's state.
Step 2: add addToPrefilledValues to the form component's context so that the method is passed on to all child components (note: I could also pass it as a prop but context seems easier to pass it on to grandchild components).
Step 3: make my <URLField/> component call addToPrefilledValues whenever it receives new metadata from Embedly:
this.context.addToPrefilledValues({title: result.title, body: result.description});
Step 4 [Wrong, see below]: in the <TitleField/> component's shouldComponentUpdate method, watch for changes to the context and update the field's value (if it's empty):
shouldComponentUpdate(nextProps, nextState, nextContext) {
const nextTitle = nextContext.prefilledValues && nextContext.prefilledValues.title;
const currentTitle = this.context.prefilledValues && this.context.prefilledValues.title;
if (!!nextTitle && nextTitle != currentTitle && !this.input.getValue()) {
this.input.setValue(nextTitle);
}
return true;
}
Step 4 (better): whenever the form changes, store all of the form's values in a currentValue object on the form's state.
Step 5: Look at:
the original value of the field (i.e. in the database), as passed through this.props).
the current value being typed in the field, as found in this.state.currentValue.
the prefilled value generated by the Embedly API call, as found in this.state.prefilledValue.
Figure out which one is correct (i.e. if the user hasn't entered anything in the field prefill it, if not don't) and pass that on to the form field child component.
This achieves the desired result:
<URLField/> and <TitleField/> don't have to know about or communicate with each other.
<Form/> doesn't have to know about <URLField/> or <TitleField/> either.
As #ffxsam suggested though, I should probably just use Redux Form…

Categories

Resources