Formik validation for multiple components react-native - react-native

i'm working on validating this form I have.
the form consists of the following code
const [fieldNum, setFieldNum] = React.useState(0);
const addField = () => {
setFieldNum(fieldNum + 1);
}
return (
<>
<Button onPress={() => addField()}>Add</Button>
{
[...Array(fieldNum)].map((e, i) => <SpecificFreightItem />)
}
</>
}
)
every time user presses "Add" this will add a new specific-freight-item.tsx
<Box>
<Input
backgroundColor="#fff"
p="3"
fontFamily="Montserrat_500Medium"
fontSize="15"
placeholder="Cost"
/>
<Input
backgroundColor="#fff"
p="3"
fontFamily="Montserrat_500Medium"
fontSize="15"
placeholder="$0.00"
placeholder="Description"
/>
</Box>
when the user presses Add this will add a specific-freight-item field.
What I want to do is add a button on the form
<Button>Validate</Button>
which will validate the form no matter how many specific-freight-item fields there are, this should validate each input and not let the user through until each input has been field.
How can I achieve this result using Formik and Yup.
Thanks,
Arnav.

Related

How to create a custom record action button inside a List component with React-Admin?

I'm a totally newbie with React and React-Admin. IMHO, I'm trying to achieve something simple that many people must have already done but I cannot find any kind of tutorial anywhere.
I'd like to add another button to the list of action buttons (show/edit) within each row in a <List> component. This button would archive the record.
My last try looks like the code below.
import React from 'react';
import {
Datagrid,
EmailField,
List,
TextField,
ShowButton,
EditButton,
DeleteButton,
CloneButton,
} from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import ArchiveIcon from '#material-ui/icons/Archive';
const useRowActionToolbarStyles = makeStyles({
toolbar: {
alignItems: 'center',
float: 'right',
width: '160px',
marginTop: -1,
marginBottom: -1,
},
icon_action_button: {
minWidth: '40px;'
},
});
const ArchiveButton = props => {
const transform = data => ({
...data,
archived: true
});
return <CloneButton {...props} transform={transform} />;
}
const RowActionToolbar = (props) => {
const classes = useRowActionToolbarStyles();
return (
<div className={classes.toolbar}>
<ShowButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<EditButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<ArchiveButton {...props} basePath={props.basePath} label="" icon={<ArchiveIcon/>} record={props.record} className={classes.icon_action_button} />
<DeleteButton basePath={props.basePath} label="" record={props.record} className={classes.icon_action_button}/>
</div>
);
};
export const UserList = props => {
return (
<List
{...props}
sort={{ field: 'first_name', order: 'ASC' }}
>
<Datagrid>
<TextField source="first_name"/>
<TextField source="last_name"/>
<EmailField source="email"/>
<RowActionToolbar/>
</Datagrid>
</List>
)
};
Obviously, this code does not work because the <CloneButton> component get rid of the id the record. Moreover, except if I did something wrong - which is totally possible -, it makes a GET request to a create endpoint.
I'm using different routes in my dataProvider (The back end is using Django and Django rest framework). I want to send a PATCH to the detail endpoint, like the <Edit> component does.
I also tried with a <SaveButton>, but it fails too.
Uncaught TypeError: Cannot read property 'save' of undefined
at useSaveContext (SaveContext.js:23)
I guess the <SaveButton> must be within a <SimpleForm>?
I'd like the save behaviour of the <DeleteButton>, i.e. update the record from the list, display the notification that the record has been archived (with the Undo link), send the request to the back end, refresh the list.
Any guidance, directions would be very appreciated.
I don't know that this is a full answer, but felt like more than a comment...
You are trying to archive the existing record, not create a whole new record, right? CloneButton is supposed to be used to create a new record with a new ID (which is why your ID is going away), so you don't want to us it here. note that I've never used CloneButton. it is not fully documented so I could be wrong about its use.
I am thinking that you should use the useRecordContext hook within your Archive button to pull in all of the record's data, including the id; read this little section: https://marmelab.com/react-admin/Architecture.html#context-pull-dont-push
And I don't think transform is what you're looking for here. You will need to use one of the dataProvider hooks, i'm assuming useUpdate: https://marmelab.com/react-admin/Actions.html#useupdate
//first create component
const MyButton = (props: any) => {
const [sendEmailLoading, setSendEmailLoading] =
React.useState<boolean>(false);
const record = useRecordContext(props);
const sendEmail = (id: Identifier) => {
setSendEmailLoading(true)
dataProvider.sendEmail(
"notifications", { id: id })
.then(({ data }: any) => {
if (data && data.status == "success")
notify('Email send success', { type: 'success' });
setSendEmailLoading(false);
refresh();
});
};
return (
<ButtonMUI color='primary' size="small" onClick={() => {
sendEmail(record.id) }}>
{
!record.publish &&(
!sendEmailLoading ? (
translate('resources.notifications.buttons.send')
) : (
<CircularProgress size={25} thickness={2} />
)
)
}
</ButtonMUI>
)
}
//and second add to datagrid list
<Datagrid>
<NumberField source="id" />
<TextFieldRA source="subject" />
<DateField source="date" />
<BooleanField source="publish" />
{/* <EditButton /> */}
<ShowButton />
<MyButton />
</Datagrid>

How to call a method on form submission if there is a validation error using Formik + Yup?

I am working on react native application where using Formik and Yup for forms. I want to update one state value depending on the form's value while submitting a form.
Issue Description::
It works fine if there is no validation error, but there is one address field and depending on that field I have added a hidden field "zipcode". I want to show zipcode input text field if my address is empty.
Code::
const validationSchema = Yup.object().shape({
address: Yup.string()
.required('Address should not be empty'),
zipcode: Yup.string()
.required(errMgs.emptyText)
.matches(regExp, errMgs.specialCharNotAllowed),
apt: Yup.string()
.matches(regExp, errMgs.specialCharNotAllowed)
});
Inside render function::
<Formik
initialValues={this.initialValues}
onSubmit={(data) => this.onSubmitForm(data)}
ref={el => (this.form = el)}
validationSchema={validationSchema}
render={({ handleSubmit, values, setValues, setFieldValue, errors, touched, ...props }) => {
return (
<Form>
<GooglePlacesInput
onPress={(data, detail) => this.onPressOfAddress(data, detail)}
address={values.address}
name="address"
onChangeText={(value) => setFieldValue('address', value)}
/>
{errors.address && touched.address && <ErrorText message={errors.address} />}
{isZipCode &&
<>
<TextInput placeholder="Zipcode" textContentType="postalCode" name="zipcode" type="text" onValueChange={(value) => this.onChangeZipcode(value)} />
{errors.zipcode && touched.zipcode && <ErrorText message={errors.address} />}
</>
}
<TextInput placeholder="apt" textContentType="streetAddressLine1" name="apt" type="text" onValueChange={(value) => setFieldValue('apt', value)} />
<Button onPress={handleSubmit} block large variant="success">SAVE</Button>
<Button onPress={() => popScreen(this.props.componentId)} block large variant="danger">CANCEL</Button>
</Form>
);
}}
/>
I want to show zip code input with error when my address is empty on submission, means when there is a validation error in the form. How can I do this?
I want to show zipcode input text field if my address is empty.
You can have 2 cases:
1 - Check for address errors and only display the zipCode if address is not valid
To do that, you should get the error of the address field.
Instead of {isZipCode && (...)} you should do {errors.address && touched.address && (...)}, which is when the adress field is empty.
2 - After the first submit, if address is not valid, always show zipCode
Other solution is to manually trigger validation with validateForm the will come from the render function.
e.g.
<button type="button" onClick={() => validateForm().then(() => {
console.log('Check for errors and Update State of isZipCode')
})}>
Submit Button
</button>
You can also validate only one field with validateField('fieldName'), so maybe only check for address and set the state depending on the errors.

How to TextInput component "clear" button

I'm using react-native-testing-library on a react-native-elements Input component. The component shows the clear button while editing.
How can I tap the clear button to test the side effects?
This doesn't work:
const addressField = component.getByPlaceholder("Address");
addressField.clear();
// TypeError: addressField.clear is not a function
You can use the clear option.
handleSearchClear = () => {
this.setState({ query: "" })
}
....
<Input
placeholder='BASIC INPUT'
onClear={this.handleSearchClear}
value={this.state.query}
/>
OR You can use ref
this.input.clear();
...
<Input
placeholder='BASIC INPUT'
ref={ref => {this.input = ref;}}
value={this.state.query}
/>

Update input value based on another input's value?

In react-admin, there doesn't seem to be any way to access the state of an input, nor to add an onChange prop to an input. We want to be able to have the user select something from, say an AutocompleteInput, and then populate a value in, say, a TextInput. Is this possible? If so, please post a code example.
Here's an example - two TextInput fields (quantity and cost) that, when the user enters these values, automatically populates the third field, amount. When not using react-admin, we can do this:
handleTextInputChange = event => {
let {target} = event;
let source = target.getAttribute('source');
let {record} = this.state;
let {amount} = record;
if( 'cost' === source || 'quantity' === source ){
amount = ( 'cost' === source ? record.quantity*target.value : target.value*record.cost );
}
if( source ){
record[source] = target.value;
if( 'amount' !== source ){
record['amount'] = amount;
}
this.setState({record: record});
}
};
render() {
let {record} = this.state;
return (
<form>
<Grid container spacing={24}>
<Grid item sm={4} xs={12}>
<label>Quantity</label><br />
<TextField
id="quantity"
fullWidth={true}
value={this.state.record.quantity}
inputProps={{source: "quantity"}}
onChange={this.handleTextInputChange}
/>
</Grid>
<Grid item sm={4} xs={12}>
<label>Cost</label><br />
<TextField
id="cost"
fullWidth={true}
value={this.state.record.cost}
inputProps={{source: "cost"}}
onChange={this.handleTextInputChange}
/>
</Grid>
<Grid item sm={4} xs={12}>
<label>Amount</label><br />
<TextField
id="amount"
fullWidth={true}
value={this.state.record.amount}
inputProps={{source: "amount"}}
disabled={true}
onChange={this.handleTextInputChange}
/>
</Grid>
</Grid>
</form>
);
}
How can we accomplish this with react-admin?

OnPress change VIEW content

I want to have two tab buttons on top and some content underneath.
After that, the content I need a View like this :
<Form style={styles.form}>
<Label style={styles.label}>
data 1
</Label>
<Item >
<Input/>
</Item>
<Label style={styles.label}>
Data2
</Label>
<Item>
<Input/>
</Item>
</Form>
When I clicking on the first button, it is active. I need that form to appear.
After that, when I clicking on the second button, I need that form change to:
<Form style={styles.form}>
<Label style={styles.label}>
data 3
</Label>
<Item >
<Input />
</Item>
</Form>
What I'm understanding is that I need a state variable.
state = {showFirst : true, showSecond:false }
and have somewhere a conditional:
if showFirst true, display FORM1
if showSecond true, display FORM2
And
onPress {() => {this.setState{{the state = true)}}
But I am not sure how to bind this together as I'm using React Native for the first time.
Currently what I'm using now is it a good practice?
I set separate states variables for both forms, because another button may be added later.
So I can't only one button:
state = { showForm: true}
showForm?Form1:Form2
onPress={() => {this.setState{{showForm:false)}}
How can I get this to work?
This is a minimum example Component for what you said you were trying to achieve:
import React, {Component} from ‘react’;
import {Button, View} from ‘react-native’;
export default class ExampleComponent extends Component {
constructor(props) {
super(props);
this.state = {
showForm: 0
};
}
render() {
var form;
if (this.state.showForm === 0) {
form = (
<View> INSERT_FORM1 </View>
);
} else if (this.state.showForm === 1) {
form = (
<View> INSERT_FORM2 </View>
);
}
return (
<View>
<Button title=‘Show Form 1’ onPress={() => this.setState({showForm: 0})}/>
<Button title=‘Show Form 2’ onPress{() => this.setState({showForm: 1})}/>
{form}
</View>
);
}
}
You can dynamically choose what content to show based on the Component props and state.
In the example above I used a numerical value to determine what form to show to minimize the amount of state values you would have to track later if the form count expanded.
A switch statement would be a better choice in the event of more available form choices, but I used if-else here for easy of typing for now.