I need to create a form in React made of multiple components, such as <TextField>, <DropDown>, <DatePicker> where each one has a different props, some of them are common such as Id, Label
Question is how should I program this, I have come with a few ideas ->
It would be easier to split it into separate arrays: dropdowns, textFields, datePickers and then render them via .map(), but it wouldn't work as they are not in order in the form
Put all of components into array and render them via .map() with if/switch/function where I would decide which component is which
const App = (props) => {
const forms = props.forms
const getItem(type) {
if (type === Type.DropDown) {
return <Dropdown dropDownProps>
}
....
}
return (
forms.map(item => {
return getItem(item.type)
})
)
}
Next question is how/where to store all props - there is about 10 of these fields in the form, so should I do some file like InitData.ts with
const FirstField = {id: 0, label: "FirstField"}
...
but then how would I pass props such as onChange etc?
Also how should I store all those inputs using hooks? Array of strings, or some object?
Always base your components on abstraction and try to make them reusable, so assume I want to create a login form.
I would create a folder called common having common components, like text field and dropdowns as a way of reusing them.
and then another component for wrapping up most of the functionalities: handling submit events, handling form validation, render input, render dropdown. Most of these functionalities are core in my form, so abstract them away into a separate component called form component.
finally, I create another component called loginForm, in which I call these functionalities to render them.
I store the initial field values of type empty strings in a state hook inside loginform and pass it as an argument to the props of the form component.
Related
How should i keep custom form twice, one instance open and another is close on the same page that is built with React js?
I have created one react app that is having form functionality and added it to our web page. Now I want an another instance of this app to have same functionality but as a open form in my page. How could I achieve this? Any Suggestion or link would be helpful regarding having multiple instance of a same react app form, if provided.
It's a bit tricky but can be done.
GenericForm.js (your generic form component)
const Form = (props: Props) => { ... }
const createReduxForm = reduxForm({ validate }) // validate is optional - just to show you can pass other stuff as usual
const ReduxForm = createReduxForm(Form)
export default ReduxForm
The takeaway from above is that you're declaring a Form without a form name.
View where the generic form gets used:
import GenericForm from '../GenericForm' // adjust for your paths
const View = (props) => {
return (
<div>
<GenericForm onSubmit={...} form="Form1" />
<GenericForm onSubmit={...} form="Form2" />
...
</div>
)
}
This exploits the fact that redux-from uses the form name to connect/update to the store. The form prop is the key to the redux store. So by passing form name dynamically, you can reuse the same generic form in a single view.
So that's the hard part. The easy part is keeping one open and one closed - you can use an accordion control or just some custom logic to show hide forms on the page for that.
I'm making a react application that shows details about the pokemon you searched. So I have a Home component which has an input field + submit button.
I want to render my api call in my Main component.
The question that I have is : How can I pass the value from this input field that is located in my home component to my main component when I enter submit?
home component details
I need this value to update my pokemon name state in my Main component in order to get the pokemon name for my fetch call.
I need this value to be stored inside my 'searchValue' state.
Main component details
any tips?
One of the ways you can achieve this by Using ref in reactjs , Inside your Main Component you need to make a reference, like :
componentDidMount() {
this.props.onRef(this);
}
// Delete the reference once component is unmounted
componentWillUnmount() {
this.props.onRef(undefined);
}
And then create a method , which will receive the values from a the Home component and then setState like :
method(values) {
this.setState({ searchValue: values });
}
Now inside your Home component you need to reference method component before your input like , (You can amend it accordignly)
import Home from './Home'
<Home onRef={ref => (this.home = ref)} />
<Form onSubmit={e => { this.onSubmit(e) }}
Make sure to add onSubmit method inside main component which will send the values to Home Component
onSubmit = values => {
this.home.method(values);
}
You can read more about Ref and the DOM on React Documentation
I would propose you to take a look at redux which is great tool for state management. Redux will ensure you have a single source of truth.
What you will have to do is create an Action which would dispatch an event to update the state, a Reducer which would update the state (value of the search-key in redux-store). Your Main component will read the data from the API (Same Action-Reducer way) & render it without having to bother about the current state. I can explain the whole architecture in detail if you are more interested. This architecture will have less bugs & clear flow of information including shared state between components.
I know this sounds bit too complex but its worth a try if your application is growing.
Few suggestions:
Your Main component has too much of logic. Components should have least logic as possible so they are more deterministic. Please also read about React Stateless functional components
Here are some links if you wish to take redux path
10-tips-for-better-redux-architecture
Redux Best Practices
In Redux-Form, it seems like you pass onSubmit as a prop to your form component and then the form component will have a onSubmit={handleSubmit} property on its form tag. This makes sense as Redux-Form will then gather up all of your form data and pass it in as one single object parameter to your onSubmit function. But what happens when you have multiple form component children of the same parent component? You can only have one onSubmit function to pass to both your form children even though they need to do different things with their submitted information. Is the only solution to have multiple parents for multiple forms just so you can pass in a different onSubmit? To me this seems not very DRY.
You can use the composition concept of reactjs here.
What you need to is :-
Keep a common form, lets call it CommonForm.js. This form is going to attached to your redux form. So all your form common functions(maybe opening form,closing form) would be implemented here.
P.S : No Field components should be present here.
Now, say you have 5 different forms which you want to render. Lets take the example of first form. I will name the file FirstForm.js.
Here in FirstForm.js you render your fields and pass your rendered fields as props to your CommonForm(I am using composition here).
Check the following code:-
Form 1
class Form1 extends Component {
render(){
const renderedFields = this.renderFormFields()
return(
<CommonForm {...this.props} renderedFields = {renderedFields } />
)
}
}
CommonForm
class CommonForm extends Component {
componentDidMount(){
const { data,initialize} = this.props
if(data){
initialize(data)
}
}
render(){
const { renderedFormFieldss } = this.props
return(
<div className='form-container'>
<form onSubmit={this.instrumentSplitCreateOrUpdate()}>
{renderedFormFields}
</form>
</div>
)
}
}
CommonForm = reduxForm({fields: ["text"], validate : validateInputField})(CommonForm)
export default CommonForm
This way you are using same base component to connect to redux form.
DYNAMIC FORMS:-
Ideally above should solve your query but there is something more interesting. If you went through the code proper in the last line of common form you can see I am passing following as parameter:-
{fields: ["text"]}
So, in this you can create forms with variable names.Say you have same forms used by multiple components and you want to render same form but with different names. For this you can try following:-
<CommonForm {...this.props} renderedFields = {renderedFields } />
While calling CommonForm from say Form1 pass a prop called form with the variable name with which you want to create form. You can something like following:-
<CommonForm form='//some key' {...this.props} renderedFields = {renderedFields } />
I have dynamic JSON data and have a custom method to go through the JSON to dynamically render a form on the page. The reason for the JSON schema is to build various forms that is not predefined.
I have hooked up Redux so that the schema and the formValues below gets assigned as the props of this class. So, the form is rendering correctly with the correct label, correct input field types etc. When an onChange event happens on the fields, the app state(under formData) is being updated correctly. But I am noticing that when the formData changes in the app state, the entire form gets re-rendered, instead of just the "specific fields". Is this because I am storing the form values as an object under formData like this? How do I avoid this issue?
formData = {
userName: 'username',
firstName: 'firstName
}
Example schema
const form = {
"fields":[
{
"label":"Username",
"field_type":"text",
"name":"username"
},
{
"label":"First Name",
"field_type":"text",
"name":"firstName"
}
]
}
Redux state
const reducer = combineReducers({
formSchema: FormSchema,
formData: Form
});
//render method
render() {
const { fields } = this.props.form,
forms = fields.map(({label, name, field_type, required }) => {
const value = value; //assume this finds the correct value from the "formData" state.
return (
<div key={name}>
<label>{label}</label>
<input type={field_type}
onChange={this.onChange}
value={value}
name={name} />
</div>
);
})
}
//onchange method (for controlled form inputs, updates the fields in the formData app state)
onChange(event) {
this.props.dispatch(updateFormData({
field: event.target.name,
value: event.target.value
}));
}
From your example I'm not sure, but if you're rendering the whole thing in a single render() method, yes, the component will be rendered again. And that is the problem, THE component. If you are trying to have multiple components, then they should be split up as much as possible. Otherwise if the state changes, it triggers a re-render of the only component there is.
Try breaking it as much as you can.
Hints: (dont know if they apply but maybe)
use ref={}s
implement shouldComponentUpdate()
EDIT: Just thought about this, but are you storing the fields' values in your state? This doesnt feel correct. Be sure to read carefully the React guide about controlled components. (Eg try to render using plain <span>s instead of inputs, and listen to onKeyPress. Would it still work? If not you might be misusing the value attribute)
Is it a good practice to do this in ReactJS?
var Component = React.createClass({
render: function () {
return (<div></div>);
},
field: 'value', // is this safe?
method: function () {
// do something with field
}
})
Before starting to suggest that I should use this.props or this.state, for me it's not the case, because those are fields that do not affect rendering in any way directly, they just work together to control the rendering.
I would like to use the React class as I do with regular javascript 'classes'.
My main concern here is how those fields and methods are handled inside React, and if the fields are set on the instance itself or directly on the prototype, which would not be suitable at all for what I need.
I ran a quick test and it seems that the fields are set on the instance, and the methods on the prototype, which is ideal. But is this the expected and documented behavior? And is this safe for future versions?
I think it can work the way you are doing and that it's safe. However if I understand well you are proceeding data calculation/transformation directly in the view.
So I would advise that you remove this logic from the view and treat it in the model part of a mvc or mv*, in your backbone models, or in your flux store for example.
This way you won't be mixing data transformation logic and pure rendering.
I would say so, I have been using things like this for a while and have not seen any issues. For example, let's say you want a handler of some sort that you want to pass to nested components, you would create the function in this component and pass it as a prop to a child. I believe they have examples that use similar concept in the ReactJS Facebook site.
Under the hood React is just looping through the properties of the object you pass to createClass and copying them to the prototype of the Component. Primitive values like strings or numbers obviously cannot be copied by reference, so don't get shared across all instances, whereas objects, functions, arrays and so on will.
If you want to work with values that are just local to the component instance you need to use the state API. I'm not sure what you mean by "[state and props] do not affect rendering in any way directly, they just work together to control the rendering". The whole point of props and state is that they work together to generate values to be used when (re)rendering.
https://facebook.github.io/react/docs/component-api.html
A React component should only render in response to either changing props or changing state. You cannot/shouldn't trigger a re-render by mutating other fields directly.
You need to think of your component as something closer to a pure function. State and props go in at the top, and static VDOM/HTML comes out.
I would re-write your example as,
var Component = React.createClass({
getInitialState: function () {
return {field: 'value'};
},
render: function () {
var field = this.state.field;
return (<div>{field}</div>);
},
method: function () {
var field = this.state.field;
// do something with field
this.setState({field: 'anotherValue'});
}
})