Get Redux Form Values For Unknown Form - javascript

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]

Related

ReactJS autocomplete from React Bootstrap not working

I'm trying to build an autocomplete search field, using this form component from React Bootstrap, which is rendered as a input by the browser.
Here's what I did in my React component:
<FormControl
id="frenchToEnglishInput"
placeholder="type a word..."
aria-label="Recipient's username"
autocomplete="on"
data={frenchToEnglishWords}
onChange={this.fetchFrenchToEnglish}
renderItem = {item => {
return (
<div>
{item}
</div>
);
}}
/>
the frenchToEnglishWords array is declared outside the component as a var, as I intend to update it as I type some value into the input field.
Now here is the function that triggers the onChange event :
fetchFrenchToEnglish = async () => {
if(document.getElementById("frenchToEnglishInput").value!==''){
axios.get(dictionaryURIs.english.French_English+""+document.getElementById("frenchToEnglishInput").value)
.then(response => {
frenchToEnglishWords = response.data
})
}
}
The request is made from MongoDb, after setting up an autocomplete index on a collection, but this part works fine.
My question is, why is the only "autocomplete" I'm getting is the one made of the previous words I've typed ?
Or maybe the array I'm using as input data must be a const (as I've seen in many examples) and not a var ?
When I do type in some word, I do not get any autosuggestion from the frenchToEnglishWords, which is being updated from the DB.
You need to use State!
This is not working because the data field is not a State, you need to bind the fetchFrenchToEnglish function to the data state.
But first of all, there's no reason to use var, because the most differences between them is just scope and immutability, here is more about it.
Also, you can use hooks like useState and useRef to no use getElementById.

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

With react-select, select multiple items matching search simultaneously

I am using react-select to display a searchable drop-down list of items, of which the user can select multiple items. My list is quite long and the user will often wish to multi-select many items which match the same filter string, which is a bit of a tedious procedure because each time you select an item the dropdown disappears and you need to re-type the search.
For example, the following sandbox has a react-select which lists lots of apples and cheeses. In order to select all the Apples, one would have to keep typing "Apple" and choosing one apple at a time.
https://codesandbox.io/s/2l99lry5p
Coming from desktop UI background, I naturally want to be able to type a search query and press Ctrl-A to select all of the matching search results and add them to my list, or Ctrl-Click to cherry pick multiple items from the matching set. But as far as I can tell there's no support for any hotkey like this in react-select.
Does the react-select API have any way that I can implement a "select all" hotkey which would select everything that matches the current search filter (or even an explicit "select all matches" button on the page would be fine)? I cannot see any programmatic way to get access to the set of objects which match the filter. Is this something that I would need to fork react-select to implement or is it possible to do this via the existing API somehow?
React Select has built-in props that can be used to prevent the menu from closing on select and persist the search string.
First prevent the menu from closing on select by setting closeMenuOnSelect to false.
Then onInputChange store the search string in state if the action equals 'input-change'.
Setting inputValue to this.state.value will persist the search string in the input field.
class Foo extends Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleInputChange = (value, e) => {
if (e.action === 'input-change') {
this.setState({value});
}
}
render() {
return (
<Select
isMulti
name="colors"
options={options.map(x => MakeOption(x))}
className="basic-multi-select"
classNamePrefix="select"
/* added these props */
closeMenuOnSelect={false}
onInputChange={this.handleInputChange}
inputValue={this.state.value}
/>
)
}
}
Updated sandbox: https://codesandbox.io/s/yvmzx6pn6z
I hacked up something that kind of does what I want but is quite ugly:
https://codesandbox.io/s/j7453qrmv
To use:
Try searching "apple", then press "Add all matching items to selection"
The approach:
As #wdm mentioned, there's a onInputChanged you can hook in to.
In onInputChanged, get the matching items store them in the state of the component
I add a button near the Select which allows the user to choose to copy the matching set of items into another state variable chosenItems
The react-select Select component has a value property that you can provide to programmatically choose the items. I pass state.chosenItems in to this.
This works but there were many things that make this a pain:
The onInputChanged handler gets called before the items matching the filter appear. I attempted to grab the matching items by DOM queries but it did not work because onInputChanged is too early. So rather than relying on react-select's filtering logic, I'm replicating the filtering logic in the onInputChanged handler. This is not great as there could be a discrepancy between my filtering logic and the displayed list of matching items.
Whenever you click after typing a search, the react-select clears the search, which invokes the onInputChanged event again. So by clicking on the custom "Add All Matching Items" button, it removes the filter, clearing the list, invoking onInputChanged and setting the state with a new list of matching items. To deal with this I needed to have a previousMatchingOptions state variable which keeps track of the matching items from the previous call to onInputChanged. This seems like a terrible hack.
Likewise, I attempted to hide/show my "Select All Matching Items" button based on whether there were currently more than one item that matches the search, but I was similarly thwarted by timing issues. I attempted to hack around this but kept getting caught up in corner cases.
The UI I came up with for "Select All Matching Items" doesn't feel integrated with the react-select very well. It would be nicer if it was part of their component rather than beside it.
By using the values property of the Select component, you are bypassing the component's internal management of its state, so the normal way of adding, removing, and clearing items does not work without reimplementing all that in a custom onChange handler which modifies the state.chosen which is passed to values. Managing this myself seems also less than desirable.
So, I have a solution, but if someone has something has a suggestion that is much cleaner and/or simpler I would be happy to hear it!
It seems like forking the control and doing these changes internal to the component might be the better way to go.
In my onInputChanged I attempted to get the matching search results directly from the DOM using some getElementsByClassName hackery, though this approach did not work because the onInputChanged
A very simple way of implementing a "Select All" option is overriding React-Select component with a custom component. For that you first need to import it as
import { default as ReactSelect } from 'react-select';
then create a custom component which defines a new Property named "allowSelectAll", and selects all the options when this property is set to 'true'.
const Select = props => {
if (props.allowSelectAll) {
if (props.value && (props.value.length === props.options.length)) {
return (
<ReactSelect
{...props}
value={[props.allOption]}
onChange={selected => props.onChange(selected.slice(1))}
/>
);
}
return (
<ReactSelect
{...props}
options={[props.allOption, ...props.options]}
onChange={selected => {
if (
selected.length > 0 &&
selected[selected.length - 1].value === props.allOption.value
) {
return props.onChange(props.options);
}
return props.onChange(selected);
}}
/>
);
}
return <ReactSelect {...props} />;
};
Select.propTypes = {
options: PropTypes.array,
value: PropTypes.any,
onChange: PropTypes.func,
allowSelectAll: PropTypes.bool,
allOption: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string
})
};
Select.defaultProps = {
allOption: {
label: "Select all",
value: "*"
}
};
Note: You can simply copy and paste the above given code and it will work absolutely fine.
And once that is done you can simply use the new 'Select' component with 'allowSelectAll' property set to true.
<Select allowSelectAll={true} isMulti={true} isSearchable={true} options={options} />
You can use the filterOption function like this:
<select
options={[{label: 'test', value: 1, customData: 'bla blub test'}]}
filterOption={(option, filter) => {
const { customData } = option.customData;
if(customData.toLowerCase().indexOf(filter.toLowerCase()) >= 0) {
return true;
}
}} />
Hope this will help you :)

React - Mutating form object for each change . Is that anyway we can do it in a better way

handleChange(evt, field) {
let form = this.state.form;
form[field] = evt.value;
console.log('change triggered');
this.setState({
form: form
});
}
render(){
return(
<div>
<input type="text" name="name" id="name"
text={this.state.form.name}
onChange={event => { this.handleChange(event, 'name'); }} />
<br />
<input type="text" name="email" id="email"
text={this.state.form.email}
onChange={event => { this.handleChange(event, 'email'); }} />
</div>
);
}
I have added simple form for reference . In the above content Whenever form field changes handleChange method invoked and In that form object mutated updated as per field changes and setting to state object.
I want to know whether we can avoid mutation of object of each field text changes or Is there any other better way to address the same . Also want to know object mutation affects the performance in any way because here I mentioned couple of fields and In my original project I am working contains atleast 15 to 20 fields for each form.
Added the same working module.
https://stackblitz.com/edit/react-ssgcnu?file=Hello.js
Please help me out . Thanks in advance
If you don't want to update each character change you can use onBlur event.
onBlur={event => { this.handleChange(event, 'name'); }}
So that on leaving the field only you can update the state.
'...' Spread operator produces a shallow copy of an object
handleChange(evt, field) {
let form = {...this.state.form}; //object spread notation
form[field] = evt.value;
console.log('change triggered');
this.setState({ form: form });
}
When it comes to forms with React, there are two approaches.
1) Uncontrolled components, which basically making use of refs and getting values from the DOM (please check the official documentation for more details https://reactjs.org/docs/uncontrolled-components.html) or
2) Controlled components making use of states and getting/handling values from React states (please check the official documentation for more details https://reactjs.org/docs/forms.html#controlled-components)
With controlled components => React, state is the single source of truth (as official document suggests), that means, you need to provide methods to handle state changes as user provides the input
With uncontrolled components => Instead of updating state on every single change, you can get values inside of onSubmit method and handle them before submit. Since you won't need to update the state, you won't need additional functions to handle state changes.
For you seeking of better way to handling things and avoid mutations, it actually depends on your use case, but official documentation suggests that
In most cases, we recommend using controlled components to implement
forms. In a controlled component, form data is handled by a React
component
When it comes to mutating object, indeed mutations is not good, but there are ways to avoid mutations during state changes. as #mhkit already suggested, you can use spread operator to create a new object (shallow copy, that means it only copies the values) or you could use Object.assign() method.
Let's say you have the following state
state = {
form: {
email: '',
name: '',
lastName: '',
}
}
When you user provides the email, you basically need to update the email field,
in that case in your handleChangeEmail() method, what you can do is the following
this.handleChangeEmail = (value) => {
this.setState(({form}) => {
return {
form: {
...form,
email: value
}
})
}
So with this method, what I basically do is,
1) I utilize functional setState and extracted the current value of form via ES6 object destructuring, then I say that, okay my new form object inside of the state, will have all the existing field that former form has, BUT, the email field will have a new value based on the new input user provided.
By this way, instead of mutating form object, we created a shallow copy of it with some values are exactly the same, but some values are updated. THUS we prevent the mutation

React - Best practice with state in form components?

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.

Categories

Resources