trying to render fields on condition based inside FieldArray react JS - javascript

I am trying to render the field based on condition that is passed to the component like as below
return (
<FieldArray name={`spaceType.mechanicalData.${mappedLibrarySourceArray}`}>
{({ fields }) => {
const dataSource = fields.map((name, index) => ({
rowKey: index,
...values.spaceType.mechanicalData[mappedLibrarySourceArray][index],
{ isProjectSystem && (select ( // getting error at this line (compile errors)
<Field
name="airSystem.isEquipmentIncluded"
component={AntdCheckboxAdapter}
type="checkbox"
label="Included"
disabled={isReadOnly}
/>
)),
},
id: (
<Field
name={name}
component={AntdSelectSectionAdapter}
isProjectSystem={isProjectSystem}
section={section}
disabled={isReadOnly}
placeholder="Select source"
render={sourceRender}
/>
),
}));
return (
<div>
<Table
columns={tableColumns}
dataSource={dataSource}
rowKey="rowKey"
/>
</div>
);
}}
</FieldArray>
);
and i am not sure where i am doing wrong with above code and isProjectSystem is boolean variable passed onto this component
I am using react JS with final form adapters plugin
Could any one please suggest any ideas on this that would be very grateful.
thanks in advance

You could do:
const dataSource = fields.map((name, index) => ({
rowKey: index,
...values.spaceType.mechanicalData[mappedLibrarySourceArray][index],
select: isProjectSystem ? (
<Field
name="airSystem.isEquipmentIncluded"
component={AntdCheckboxAdapter}
type="checkbox"
label="Included"
disabled={isReadOnly}
/>
) : null,
id: (
<Field
name={name}
component={AntdSelectSectionAdapter}
isProjectSystem={isProjectSystem}
section={section}
disabled={isReadOnly}
placeholder="Select source"
render={sourceRender}
/>
),
}));
that will have the select property there regardless. Alternately, you could change your map to conditionally add that property after.
const dataSource = fields.map((name, index) => {
const output = {
rowKey: index,
...values.spaceType.mechanicalData[mappedLibrarySourceArray][index],
id: (
<Field
name={name}
component={AntdSelectSectionAdapter}
isProjectSystem={isProjectSystem}
section={section}
disabled={isReadOnly}
placeholder="Select source"
render={sourceRender}
/>
),
};
if (isProjectSystem) {
output.select = (
<Field
name="airSystem.isEquipmentIncluded"
component={AntdCheckboxAdapter}
type="checkbox"
label="Included"
disabled={isReadOnly}
/>
);
}
return output;
});

Related

Iterating over data to display inputs renders data multiple times

I am trying to create a custom component that iterates over an array of data and display for each item an input field type radio with its according label, it works but for some reason the data is displayed three times, instead of just once for each field and I cant figure out why, I just want to render just three inputs, why does this behavior occur, am I missing something? Here is my code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Tooltip } from '#progress/kendo-react-tooltip';
import { Form, Field, FormElement } from '#progress/kendo-react-form';
import { RadioGroup, RadioButton } from '#progress/kendo-react-inputs';
import { Error } from '#progress/kendo-react-labels';
import { Input } from '#progress/kendo-react-inputs';
const emailRegex = new RegExp(/\S+#\S+\.\S+/);
const data = [
{
label: 'Female',
value: 'female',
},
{
label: 'Male',
value: 'male',
},
{
label: 'Other',
value: 'other',
},
];
const emailValidator = (value) =>
emailRegex.test(value) ? '' : 'Please enter a valid email.';
const EmailInput = (fieldRenderProps) => {
const { validationMessage, visited, ...others } = fieldRenderProps;
return (
<div>
<Input {...others} />
{visited && validationMessage && <Error>{validationMessage}</Error>}
</div>
);
};
const Component = () => {
return (
<>
{data.map((item, index) => {
return (
<p>
<input
name="group1"
type="radio"
value={item.value}
label={item.label}
key={index}
/>
<label>{item.label}</label>
</p>
);
})}
</>
);
};
const App = () => {
const handleSubmit = (dataItem) => alert(JSON.stringify(dataItem, null, 2));
const tooltip = React.useRef(null);
return (
<Form
onSubmit={handleSubmit}
render={(formRenderProps) => (
<FormElement
style={{
maxWidth: 650,
}}
>
<fieldset className={'k-form-fieldset'}>
<legend className={'k-form-legend'}>
Please fill in the fields:
</legend>
<div className="mb-3">
<Field
name={'firstName'}
component={Input}
label={'First name'}
/>
</div>
<div className="mb-3">
<Field name={'lastName'} component={Input} label={'Last name'} />
</div>
<div className="mb-3">
<Field
name={'email'}
type={'email'}
component={EmailInput}
label={'Email'}
validator={emailValidator}
/>
</div>
</fieldset>
<div
onMouseOver={(event) =>
tooltip.current && tooltip.current.handleMouseOver(event)
}
onMouseOut={(event) =>
tooltip.current && tooltip.current.handleMouseOut(event)
}
>
<RadioGroup data={data} item={Component} />
<Tooltip
ref={tooltip}
anchorElement="target"
position="right"
openDelay={300}
parentTitle={true}
/>
</div>
<div className="k-form-buttons">
<button
type={'submit'}
className="k-button k-button-md k-rounded-md k-button-solid k-button-solid-base"
disabled={!formRenderProps.allowSubmit}
>
Submit
</button>
</div>
</FormElement>
)}
/>
);
};
ReactDOM.render(<App />, document.querySelector('my-app'));
and here is an example:
https://stackblitz.com/edit/react-rfb1ux-jpqvwy?file=app/main.jsx
<RadioGroup data={[1]} item={Component} />
RadioGroup assumes that Component element is to be rendered over data array ... so it renders like this -->
data.map(val=><Component />)
here size of data array is 3 .
or
<Component />
Radio Group will do the mapping for you. Try changing Component to:
const Component = (props) => {
return (
<p>
<input
name="group1"
type="radio"
value={props.children.props.value}
label={props.children.props.label}
key={props.children.props.label}
/>
<label>{props.children.props.label}</label>
</p>
);
};
This will then map out the items for you.
seems like this works,
const Component = ({ children: { props } }) => {
const { value, label } = { ...props };
return (
<p>
<input name="group1" type="radio" value={value} label={label} />
<label>{label}</label>
</p>
);
};

Updating prop on a specific component based on API response

I have an API which with an object and encapsulates errors related with each inputField within it, for example:
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"firstname": [
"Invalid firstname",
"Firstname must be at least 2 characters long"
],
"companyname": ["company name is a required field"]
}
}
based on the errors, I need to display the error right below the input element, for which the code looks like this:
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
<Field name='companyname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Field name='firstname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Translate
content='button.register'
className='btn btn-primary btn-form'
type='submit'
component='button'
/>
</form>
</div>
);}}
The inputField:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={<Translate content={error} />}
/>
</div>
);
};
and finally;
import React, { Component } from "react";
class InputFieldError extends Component {
render() {
return <font color="red">{this.props.touched && this.props.error}</font>;
}
}
export default InputFieldError;
If I validate simply with validate={validation.required} the error property is attached to the correct input field and I can render the error div using InputFieldError right below it.
I am mapping all the errors back from the API response on to props like this:
function mapStateToProps(state) {
return { errorMessage: state.errors, countries: state.countries };
}
which means I can print every error that I encounter at any place like this:
{this.props.errorMessage
? displayServerErrors(this.props.errorMessage)
: null}
rendering all at same place by simply going through each property on errorMessage is easy.
Now when I try to assign the errors back from the API ("errors": {"firstname": []} is linked with Field name="firstname" and so on...), I cannot find a way to attach the error in "firstname" property to the correct InputFieldError component in Field name="firstname"
I hope the question is clear enough, to summarise it I am trying to render error I got from API to their respective input element.
Try to pass errors.firstname to field inner component like this
<Field name='companyname' type='text' component={<inputField apiErrors={this.props.errorMessage.errors.firstname || null} {...props}/>} className='form-control' validate={validation.required} />
and then your inputField will be:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
apiErrors,
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={apiErrors ? apiErrors : <Translate content={error} />}
/>
</div>
);
};
and:
class InputFieldError extends Component {
render() {
const errors = this.props.error
let errorContent;
if (Array.isArray(errors)) {
errorContent = '<ul>'
errors.map(error, idx => errorContent+= <li key={idx}><Translate content={error}/></li>)
errorContent += '</ul>'
} else {
errorContent = errors
}
return <font color="red">{this.props.touched && errorContent}</font>;
}
}
You can also add the main error at the top of your form
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
{this.props.errorMessage.message &&
<Alert>
<Translate content={this.props.errorMessage.message}/>
</Alert>
}
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
...

Sending parameter with function to child component and back to parent component

I've got a custom component called InputWithButton that looks like this:
const InputWithButton = ({ type = "text", id, label, isOptional, name, placeholder = "", value = "", showPasswordReset, error, isDisabled, buttonLabel, handleChange, handleBlur, handleClick }) => (
<StyledInput>
{label && <label htmlFor="id">{label}{isOptional && <span className="optional">optioneel</span>}</label>}
<div>
<input className={error ? 'error' : ''} type={type} id={id} name={name} value={value} placeholder={placeholder} disabled={isDisabled} onChange={handleChange} onBlur={handleBlur} autoComplete="off" autoCorrect="off" />
<Button type="button" label={buttonLabel} isDisabled={isDisabled} handleClick={() => handleClick(value)} />
</div>
{error && <Error>{Parser(error)}</Error>}
</StyledInput>
);
export default InputWithButton;
Button is another component and looks like this:
const Button = ({ type = "button", label, isLoading, isDisabled, style, handleClick }) => (
<StyledButton type={type} disabled={isDisabled} style={style} onClick={handleClick}>{label}</StyledButton>
);
export default Button;
I'm using the InputWithButton component in a parent component like this:
render() {
const { name } = this.state;
return (
<React.Fragment>
<InputWithButton label="Name" name="Name" buttonLabel="Search" value={name} handleChange={this.handleChange} handleClick={this.searchForName} />
</React.Fragment>
);
}
If the button is clicked, the searchForName function is called:
searchForName = value => {
console.log(value); //Input field value
}
This is working but I want to add another parameter to it but this time, a parameter that comes from the parent component
// handleClick={() => this.searchForName('person')}
<InputWithButton label="Name" name="Name" buttonLabel="Search" value={name} handleChange={this.handleChange} handleClick={() => this.searchForName('person')} />
The output in searchForName is now 'person' instead of the value.
I thought I could fix this with the following code:
searchForName = type => value => {
console.log(type); //Should be person
console.log(value); //Should be the value of the input field
}
However this approach doesn't execute the function anymore.
How can I fix this?
EDIT: Codepen
I would try handleClick={this.searchForName.bind(this, 'person')}, please let me know if it'll work for you.
EDIT:
I changed fragment from your codepen, it's working:
searchName(key, value) {
console.log(key);
console.log(value);
}
render() {
const { name } = this.state;
return (
<InputWithButton name="name" value={name} buttonLabel="Search" handleChange={this.handleChange} handleClick={this.searchName.bind(this, 'person')} />
)
}
as I suspected, just pass it an object and make sure you're accepting the argument in your handleClick function
handleClick={value => this.searchName({value, person: 'person'})}
or more verbose - without the syntactic sugar
handleClick={value => this.searchName({value: value, person: 'person'})}
then you can get at it with value.person
full codepen here
Hope this helps

React Algolia instantsearch connectSearchBox

I am using Algolia React InstantSearch, with connectSearchBox to create a custom input. The way I am currently doing this is below:
const MySearchBox = connectSearchBox(({currentRefinement, refine}) => {
return (
<input
type="text"
placeholder="Search"
value={currentRefinement}
onFocus={()=> props.onFocus()}
onBlur={()=> props.onBlur()}
onChange={(e) => {refine(e.target.value)}}
/>
);
});
And then the following code to instantiate:
<InstantSearch
onSearchStateChange={(result) => this.onSearchChange(result)}
appId={appId}
apiKey={apiKey}
indexName={index}>
<MySearchBox/>
</InstantSearch>
This works perfectly. However, what I would like to do is be able to pass props to MySearchBox. So I do something like this:
const MySearchBox = (props) => {
connectSearchBox(({currentRefinement, refine}) => {
return (
<input
type="text"
....
/>
);
})
}
Or this:
const MySearchBox = React.createClass({
render() {
return (
connectSearchBox(({currentRefinement, refine}) => {
return (
<input
type="text"
/>
);
})
)
}
});
However, running this code, I get the following error:
MYSEARCHBOX(...): A VALID REACT ELEMENT (OR NULL) MUST BE RETURNED.
YOU MAY HAVE RETURNED UNDEFINED, AN ARRAY OR SOME OTHER INVALID
OBJECT.
In the end, what is a way for me to pass props to MySearchBox?
You can simply pass props to your custom search box and retrieve them like this:
Custom SearchBox:
const MySearchBox = connectSearchBox(({ onFocus, onBlur, currentRefinement, refine }) => {
return (
<input
type="text"
placeholder="Search"
value={currentRefinement}
onFocus={() => onFocus()}
onBlur={() => onBlur()}
onChange={e => {
refine(e.target.value);
}}
/>
);
});
Usage:
<MySearchBox onFocus={} onBlur={} />
Also, we are now forwarding the on* events passed to the <SearchBox> widget.

Passing arguments to React component using ES6

I am trying to create a sortable list with the react.js library "react-sortable-hoc" (https://github.com/clauderic/react-sortable-hoc).
In "SortableList" I map a function on each element of array which (should) create a "SortableElement" with the arguments key, index and value. This is how "SortableElement" is defined: https://github.com/clauderic/react-sortable-hoc/blob/master/src/SortableElement/index.js
SortableItem = SortableElement(({value,key}) =>
<Row>
<this.DragHandle/>
<ControlLabel>Question</ControlLabel>
<FormControl
componentClass="textarea"
placeholder="Enter question"
onChange={()=> {
console.log(key); //why undefined??
console.log(value);
}
}
/>
</Row>);
SortableList = SortableContainer(({items}) => {
return (
<div>
{items.map((value, index) =>
<this.SortableItem key={`item-${index}`}
index={index}
value={value}/>
)}
</div>
);
});
Unfortunately, key and index are always undefined, and I just don't understand why. If you are interested in the full code, please visit https://github.com/rcffc/react-dnd-test/blob/master/src/SortableComponent.js
Any help is appreciated.
If you look at the source for react-sortable-hoc, you'll see that when passing props in the render, index is omitted. Also, key isn't passed in props. If you change your destructuring to log props in SortableItem, you'll see an object containing just the value ('Question 1', 'Question 2', etc). If you need to access the index or key, pass them as different props.
e.g.
SortableItem = SortableElement(({value,yourIndex}) =>
<Row>
<this.DragHandle/>
<ControlLabel>Question</ControlLabel>
<FormControl
componentClass="textarea"
placeholder="Enter question"
onChange={()=> {
console.log(yourIndex);
console.log(value);
}
}
/>
</Row>
);
SortableList = SortableContainer(({items}) => {
return (
<div>
{items.map((value, index) =>
<this.SortableItem key={`item-${index}`}
index={index}
value={value}
yourIndex={index} />
)}
</div>
);
});

Categories