Conditional Attributes in React js - javascript

How do i accept conditional attributes in react.js
below is my search component, I want the InputGroup to have a onSubmit attribute if the onSubmit function is passed and an onChange attribute if an onChange function is passed
class QueryBar extends PureComponent {
render() {
const { placeholder, leftIcon, onSubmit, onChange, width } = this.props;
return (
<form
style={{ width }}
onSubmit={e => {
e.preventDefault();
onSubmit(e.target[0].value);
}}
>
<InputGroup
placeholder={placeholder}
width={width}
leftIcon="search"
rightElement={
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
}
/>
</form>
);
}
}
QueryBar.propTypes = {
width: PropTypes.number,
placeholder: PropTypes.string,
leftIcon: PropTypes.oneOfType(['string', 'element']),
onSubmit: PropTypes.func
};
QueryBar.defaultProps = {
placeholder: 'Search...',
leftIcon: 'arrow-right',
width: 360
};
export default QueryBar;

jsx elements can also accept objects. Initialize an object that contains information for both situations and then add a conditional to add a function if it exists in the props passed in.
render() {
const { placeholder, leftIcon, onSubmit, onChange, width } = this.props;
const inputGroupProps = {
placeholder,
width,
leftIcon: 'search',
rightElement: (
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
)
}
if (onChange) {
inputGroupProps.onChange = onChange
}
if (onSubmit) {
inputGroupProps.onSubmit = onSubmit
}
return (
<form
style={{ width }}
onSubmit={e => {
e.preventDefault();
onSubmit(e.target[0].value);
}}
>
<InputGroup {...inputGroupProps} />
</form>
);
}
While I do not recommend it, adding both are technically OK because a prop that isn't passed in from the parent but destructured, will be undefined. I don't recommend this because it is not expressive and will probably confuse you in the future
<InputGroup
placeholder={placeholder}
width={width}
leftIcon="search"
rightElement={
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
}
onChange={onChange} // will be undefined and have no behavior if parent does not pass an onChange prop
onSubmit={onSubmit} // same for this one
/>

You can pass null if its not there i.e :
<InputGroup
placeholder={placeholder}
width={width}
leftIcon="search"
onChange={onChangeFn?onChangeFn:null}
onSubmit={onSubmitFn ? onSubmitFn : null}
rightElement={
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
}
/>
It will make sure if function is there then call function otherwise it will not anything.

I would do this:
The idea is to have an Object optionalProps, an empty object for any possible conditional properties, when a property exists, we add it to the object, then, in the InputGroup component we apply it as {...optionalProps} which will extract any added properties to the object, and return nothing if null.
we could follow another approach: onChange={onChange && onChange}
But, note This will return false as a value for cases where onChange doesn't exist.
render() {
const { placeholder, leftIcon, onSubmit, onChange, width } = this.props;
let optionalProps = {};
if(onChange){
optionalProps['onChange'] = onChange;
}
if(onSubmit){
optionalProps['onSubmit'] = onSubmit;
}
return (
<form
style={{ width }}
onSubmit={e => {
e.preventDefault();
onSubmit(e.target[0].value);
}}
>
<InputGroup
placeholder={placeholder}
width={width}
leftIcon="search"
{...optionalProps}
rightElement={
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
}
/>
</form>
);
}

Related

Formik - Render ErrorMessage automatically

I have the following code which you can find here:
https://stackblitz.com/edit/react-d2fadr?file=src%2FApp.js
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
<ErrorMessage name={fieldName} />
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
I want to know if is it any way to render the ErrorMessage element automatically?
The error message should be shown somewhere around the input text.
If you know how, you can fork the StackBlitz above with your suggestion.
Thanks!
Don't really know why ErroMessage is not rendering before you submit your form once but you can replace the line <ErrorMessage name={fieldName} /> by {formik.errors[fieldName]} to make it works
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
{formik.errors[fieldName]}
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
the issue is validation schema. When I changed 6
return /^[0-9a-f]{6}$/.test(value);
to 3
return /^[0-9a-f]{3}$/.test(value);
and submitted with the initial value, ErrorMessage component is rendered
To reach your goal, I changed your code as below:
Since Formik's default component is Input, I deleted your TextInput component as there was nothing special in your component and handleChange function.
<Field name="hexColor" placeholder="Hex Color" onChange={(e) => handleChange(e.target.value)}/>
<ErrorMessage name="hexColor" />
As in mentioned in this answer, I changed your submit button condition to determine whether the button is disabled or not:
<Button type="submit" disabled={Object.keys(errors).length}>
Submit
</Button>
You can view my entire solution here.
Edit
If you want to keep your component, you should pass props as you might be missing something important, e.g. onChange,onBlur etc.
const TextInput = ({ field, ...props }) => {
return (
<input
{...field} {...props}
// ... your custom things
/>
);
};
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
onChange={(e) => handleChange(e.target.value)}
/>
Solution 2

How to simplify overriding props in React?

I have an If statement and returning the same component with the extra props based on the state. Any idea how to simplify this? Can I use recursion? Any idea?
iconRight is only difference.
renderInput = () => {
if (isLoading) {
return (
<Input
iconRight={(
<Spinner />
)}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
);
}
return (
<Input
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
);
}
}
You can spread props onto the component:
renderInput = () => {
const props = {
autoComplete: 'off',
id: 'unique-id-2',
'aria-autocomplete': 'off'
};
if (isLoading) {
return (
<Input
iconRight={(
<Spinner />
)}
{...props}
/>
);
}
return (
<Input {...props} />
);
}
}
But i'd suggest changing your Input component to accept a loading prop and let the Input component handle that logic. It'll make your consuming code a lot easier to read also.
I think your function can be shortened to the following;
renderInput = () => (
<Input
iconRight={isLoading ? (<Spinner />) : null}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
)
If you don't already, inside your Input component, you should check if the iconRight prop is not null, and only render it then.
You can try this:
renderInput = () =>(
<Input
iconRight={
isLoading && (
<Spinner />
)}
autoComplete="off"
id="unique-id-2"
aria-autocomplete="both"
/>
)

How do i pass a value from child component to parent component using function?

How do i pass a validation value from child component to parents component?
i tried to use props but it didn't work . i tried to pass the 'isValidValue' status
Child Component :
function MilikSendiri({isValidValue}) {
const { register, handleSubmit } = useForm()
function sweetAlertclick(){
Swal.fire({
icon: 'success',
title: 'Data anda sudah tersimpan ',
})
}
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
const isValidValue = formik.isValid? ("Data Completed") : ("DData incomplete");
return(
<div>
<div>
Status : {isValidValue}
<label htmlFor="luasTanah"> Luas Tanah </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="luasTanah"
fullWidth
id="luasTanah"
name="luasTanah"
margin="normal" variant="outlined"
/>
<ErrorMessage name='luasTanah' component={TextError}/>
</div>
<div>
<label htmlFor="BiayaPBB"> Biaya PBB </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="BiayaPBB"
fullWidth
id="BiayaPBB"
name="BiayaPBB"
margin="normal" variant="outlined"
/>
<ErrorMessage name='BiayaPBB' component={TextError}/>
</div>
<Button onClick={sweetAlertclick} type ="submit"
variant="contained" startIcon={<SaveIcon />} color="primary" style={{
marginLeft: '25rem', marginTop: '20px', width: '20rem', height: 45,
fontSize: 22, backgroundColor: '#22689F'}}
disabled={!formik.isDirty && !formik.isValid} >Simpan
</div>
)
}
}
</Formik>
)
}
Parent Component :
function UKTRumah ({isValidValue}) {
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
console.log('Formik props', formik)
return(
<div className ="IsiBiodata">
<Accordion square expanded={expanded === 'panel1'} onChange=.
{handleChange('panel1')} style={{marginLeft: '15rem', marginRight:
'15rem', marginTop: '3rem'}}>
<AccordionSummary aria-controls="panel1d-content" id="panel1d-
header">
<PersonIcon/>
<Typography> Data Rumah</Typography>
<Typography}> { isValidValue }
</Typography>
</AccordionSummary>
<AccordionDetails>
<div className ="IsiBiodata">
<Form>
</div>
</Form>
</div>
</AccordionDetails>
</Accordion>
</div>
)}}
</Formik>
)}
Thank you
Your example code seems to be lacking some key lines to answer the question specifically.
However, generally if it is data that Parent should be aware of, but that the child will make use of, it should be a value of state in the parent, then handed to the child as props. Here's a very small example using functional components:
const Child = ({ formik, setIsValid, isValid }) => {
useEffect(() => {
setIsValid(formik.isValid)
}, [formik.isValid]);
return <input />;
}
const Parent = () => {
const [isValid, setIsValid] = useState(true);
return <Child isValid={isValid} setIsValid={setIsValid} />
}
You can hold the value on your parent and pass a function to change it to your child. I can't really show you that with the code you posted, but I can show an example of what I mean. The parent has a state with an update function setIsValid and passes that to the child. The child can call setIsValid and that will update the isValid value on the parent.
parent
function Parent() {
const [isValid, setIsValid] = useState(false);
return <div>
<Child setIsValid={setIsValid} />
IsValid {isValid}
</div>
}
child
function Child({ setIsValid }) {
return <button onClick={() => setIsValid(true)}>Set Valid</button>
}

How to pass state from child component to parent in React.JS?

I have a form that has 10+ input fields that update the state of the class. To make things look cleaner I moved all input fields with labels into a separate component so I could re-use it for each input instead. This component takes 2 parameters and serves as a child in my main class.
child component:
const Input = ({ name, placeholder }) => {
return (
<div className="wrapper">
<Row className="at_centre">
<Col sm="2" style={{ marginTop: "0.5%" }}><Form.Label>{ name }</Form.Label></Col>
<Col sm="5"><Form.Control placeholder={ placeholder }/></Col>
</Row>
</div>
)
}
parent:
state = { name: '', description: '' }
handleSubmit = (e) => {
e.preventDefault()
console.log(this.state);
}
render(){
return(
<Form style={{ marginBottom: "5%", padding: 10 }} onSubmit={ this.handleSubmit } >
<Input name="Name: " placeholder="How is it called?" onChange={ (event) => this.setState({name: event.target.value}) }/>
<Input name="Description: " placeholder="Please describe how does it look like?" onChange={ (event) => this.setState({description: event.target.value}) }/>
<Button variant="outline-success" size="lg" type="submit" >SUBMIT</Button>
</Form>
)
}
After I did that I can't find the way how to update the state from my child components when the text is changed. All my attempts to do so either crashed the website or did nothing. I am still new to React.js so any feedback is appreciated.
Pass onChange event to your child component and wire it with Form.Control control.
Your Input component will be,
const Input = ({ name, placeholder, onChange }) => {
return (
<div className="wrapper">
<Row className="at_centre">
<Col sm="2" style={{ marginTop: "0.5%" }}>
<Form.Label>{name}</Form.Label>
</Col>
<Col sm="5">
<Form.Control onChange={onChange} placeholder={placeholder} />
</Col>
</Row>
</div>
);
};
And your Parent component is,
class Parent extends React.Component {
state = { name: "", description: "" };
handleSubmit = e => {
e.preventDefault();
console.log(this.state);
};
render() {
return (
<Form
style={{ marginBottom: "5%", padding: 10 }}
onSubmit={this.handleSubmit}
>
<Input
name="Name: "
placeholder="How is it called?"
onChange={event => this.setState({ name: event.target.value })}
/>
<Input
name="Description: "
placeholder="Please describe how does it look like?"
onChange={event => this.setState({ description: event.target.value })}
/>
<Button variant="outline-success" size="lg" type="submit">
SUBMIT
</Button>
</Form>
);
}
}
Working Codesandbox here.
In React, properties flow from the parent component to the child component, so you cannot directly "pass" the state from the child to the parent.
What you can do however is to have the parent pass a callback function to the child that will be called to update the parent's state.
Here is an example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
}
updateName(name) {
if (name === this.state.name) return;
this.setState({ name });
}
render() {
return (
<div>
<p>The name is {this.state.name}</p>
<ChildComponent handleNameUpdate={name => this.updateName(name)} />
</div>
);
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
}
handleInputChange(e) {
this.setState({ name: e.target.value });
this.props.handleNameUpdate(e.target.value)
}
render() {
return <input type="text" value={this.state.name} onChange={e => this.handleInputChange(e)} />;
}
}
You have to build what is known as a controlled component.
const Input = ({ label, name, onChange, placeholder }) => (
<div className="wrapper">
<Row className="at_centre">
<Col sm="2" style={{ marginTop: "0.5%" }}>
<Form.Label>{ label }</Form.Label></Col>
<Col sm="5">
<Form.Control name={ name }
value={ value }
placeholder={ placeholder }
onChange={ onChange }
/>
</Col>
</Row>
</div>
)
And in your parent,
state = { name: '', description: '' }
handleChange = ({ target: { name, value } }) => this.setState({ [name]: value })
render() {
const { name, description } = this.state
<Form style={{ marginBottom: "5%", padding: 10 }} onSubmit={ this.handleSubmit } >
<Input label="Name: " name="name" value={name} onChange={handleChange}/>
<Input label="Description: " description="description" value={description} onChange={handleChange}/>
<Button variant="outline-success" size="lg" type="submit" >SUBMIT</Button>
</Form>
}
Advice
Try to avoid manufacturing lambda methods inside the render function as much as possible and have a class property as a lambda method so that lambdas do not need to be manufactured on every render cycle.

Pass from parent to child onChange function which does not work

There is parent component which have handleChange function, I pass to the child that function.
And provide to the input fileds, then there is bug with input fileds, I am not able to change values at all.
ParentComp:
handleChange = (e, data) => {
if (data && data.name) {
this.props.setFieldValue(data.name, data.value)
if (data.name === 'pay_rate') {
console.log('PAY_RATE: ', data)
}
}
}
return (
<Grid.Column width={8}>
<Segment raised>
<Header>
<p style={{ fontSize: '1.2rem' }}>
Church Mutual Worker\'s Compensation Claim
<span style={{ float: 'right' }}>{`Claim #${props.claim.claimNumber}`}</span>
</p>
</Header>
<Form onSubmit={handleSubmit}>
// Here called is Child component
<EditStandaloneClaimDetails
claim={props.claim}
loading={loading}
handleChange={props.handleChange}
/>
</Form>
<Comment currentClaim={props.currentClaim} />
</Segment>
</Grid.Column>
)
ChildComponent:
const EditStandaloneClaimDetails = ({ handleChange, claim, loading, testChange }) => {
if (!claim || loading) {
return null
}
const { noticeOnly, recieveDate, accountNumber } = claim
return (
<Segment
raised
style={{
backgroundColor: '#F0F0F0',
}}
>
<h5>Claim Details</h5>
<Form.Group >
<Form.Field>
// CANNOT ENTER A NEW VALUE FOR DATE INPUT FILED
<label>Date Received</label>
<DateInput
name={'recieveDate'}
placeholder="Date received"
value={recieveDate}
onChange={handleChange}
style={{ width: '65%' }}
dateFormat={'MM/DD/YYYY'}
/>
</Form.Field>
</Form.Group >
Maybe problem is in this attribute value={recieveDate}?
You can handle it using call back function.
In your parent component replace below lines:
handleChange = (e, data) => {
//here you get the updated date
//add your logic
}
// Here called is Child component
<EditStandaloneClaimDetails
recieveDate = {'your date'}
claim={props.claim}
loading={loading}
handleChange={(event, data) =>this.handleChange(event,data)}
/>
In Child component add below function, that function is responsible to call back to parent component.
constructor(props){
super();
this.state = { recieveDate: prop.recieveDate };
}
HandleChange(event,value){
this.setState({ recieveDate})
this.props.handleChange(value,event.uid);
}
<DateInput
name={'recieveDate'}
placeholder="Date received"
value={recieveDate}
onChange={(event, value) =>this.HandleChange(event,data)}
style={{ width: '65%' }}
dateFormat={'MM/DD/YYYY'}
/>

Categories

Resources