Material-UI date picker component not working properly on Safari - javascript

I am building a React app and using a material-ui-pickers Date Picker component inside a custom component that also uses formik form, and yup for validation.
The component is working fine on different browsers, but is giving me both the errors from inside the component (using the formik+yup validation) and the built in invalidDateMessage attribute of the Date Picker component when on Safari.
The reason I believe the issue has something to do with the material-ui-pickers Date Picker is that in the form i have other Material-UI fields, all working fine on all browsers.
I tried looking up similiar issues but haven't found any, also playing around with the validation (changing date() to string()) did not work.
I am playing around with the value on change to be able to get a string out of the date, but without it the component won't work at all so I assume this is not the issue.
The component:
import moment from "moment";
import MomentUtils from "#date-io/moment";
import React from "react";
import { FormHelperText } from "#material-ui/core";
import { Field, ErrorMessage } from "formik";
import { DatePicker, MuiPickersUtilsProvider } from "#material-ui/pickers";
const FormikDatePicker = ({
name,
label,
disablePast = false,
disableFuture = false,
ongoing = false,
disableEnd = false,
}) => {
const convertDateToString = (d) => {
return d.format("MM-DD-YYYY");
};
return (
<Field name={name}>
{({ form, field, meta }) => {
return (
<MuiPickersUtilsProvider utils={MomentUtils}>
<DatePicker
id={name}
disableFuture={disableFuture}
disablePast={disablePast}
label={label}
value={form.values[name]}
disabled={ongoing && disableEnd}
onChange={(v) =>
form.setFieldValue(name, convertDateToString(v))
}
onBlur={field.onBlur}
fullWidth
disableToolbar
autoOk
openTo="year"
format="DD/MM/yyyy"
views={["year", "month", "date"]}
variant="inline"
inputVariant="outlined"
error={!!(meta.touched && meta.error)}
invalidDateMessage="this is the built in error"
/>
<ErrorMessage name={name}>
{(error) => {
return (
<FormHelperText style={{ color: "red" }}>
Required field.
</FormHelperText>
);
}}
</ErrorMessage>
</MuiPickersUtilsProvider>
);
}}
</Field>
);
};
export default FormikDatePicker;
Yup validation:
export const pageThree = object({
"Country of residence": string("Please enter a valid country name.")
.min(2, "Required field.")
.required("Required field."),
"City of residence": string("Please enter a valid city name.")
.min(2, "Required field.")
.required("Required field."),
Nationality: string("Please enter a valid value.").required(
"Required field."
),
"Date of Birth": date("Required field.").required("Required field."),
});

Related

How to generate a component in React Form onClick

I am struggling with some React functionality. My goal is to create a form where a day template can be added (for context - like a training club can make up a template of trainings for the day and then schedule them regularly). For that I wanted to add a button which onClick will create a smaller block with 2 form fields - time and training info. And I need user to add several of those, as much as they want.
The thing is, while I understand a bit how react works, it seems to me I am just banging my head against the wall with this, as one thing is to render a component, but another to generate a bunch of same, completely new ones and connected to the form somehow, so I can send the data when clicking submit button.
Here is repository with this component:
https://github.com/badgerwannabe/spacefitness-test-client
Here is path to this component
spacefitness-test-client/src/components/template-components/addTemplateForm.js
Here below how it looks rendered
UPDATE 1 here is the full component here:
import React, {useState} from "react";
import {useMutation } from "#apollo/client";
import {useForm} from '../../utils/hooks'
import { Button, Form } from "semantic-ui-react";
import {FETCH_TEMPLATES_QUERY, FETCH_TRAININGS_QUERY,ADD_TEMPLATES_MUTATION} from '../../utils/graphql'
//hook for form functioning
function AddTemplateForm (props){
const {values, onChange, onSubmit} = useForm(createDayCallback,{
date:'', dayTrainings:[{
time:'testing time', training:"60e9e7580a6b113b2486113a"
},{
time:'testing2 time2', training:"61ec6a6d0f94870016f419bd"
}
]
});
//apollo hook to send data through GraphQL
const [createDay, {error}] = useMutation(ADD_TEMPLATES_MUTATION, {
errorPolicy: 'all',
variables:values,
update(proxy, result){
const data = proxy.readQuery({
query:FETCH_TEMPLATES_QUERY,
});
proxy.writeQuery({query:FETCH_TEMPLATES_QUERY,
data:{
getDays: [result.data.createDay, ...data.getDays]
}})
props.history.push('/templates')
},},
{});
function createDayCallback(){
createDay();
}
//little component I want to dynamically add each time people press a button
function addDayTraining(){
const addDayTraining = (
<>
<Form.Field>
<Form.Input
placeholder="time"
name="time"
onChange={()=>{
console.log("time")
}}
values={values.time}
error={error ? true : false}
/>
<Form.Input
placeholder="training"
name="training"
onChange={()=>{
console.log("training")
}}
values={values.training}
error={error ? true : false}
/>
</Form.Field>
</>
)
return addDayTraining
}
//Form component itself
const AddTemplateForm = (
<>
<Form onSubmit={onSubmit}>
<h2>Add a template :</h2>
<Form.Field>
<Form.Input
placeholder="date"
name="date"
onChange={onChange}
values={values.date}
error={error ? true : false}
/>
</Form.Field>
<Form.Field>
<Button type="button" onClick={
addDayTraining
}>Add training</Button>
</Form.Field>
<Button type ="submit" color="teal">Submit</Button>
</Form>
{error && (
<div className="ui error message" style={{marginBottom:20}}>
<li>{error.graphQLErrors[0].message}</li>
</div>
)}
</>
)
return AddTemplateForm
}
export default AddTemplateForm;
Can you just set up a function on the submit button which pushes an object with {time: new Date(), trainingInfo: ""} and push that object into an existing array of training objects? (obviously starting empty)
You could then map those objects into a component and when the component is updated (i.e. when the user adds a time and training details text) use a callback function to update the values in the array at the index of that object.
export default function yourIndexPage({yourprops}) {
const [trainingObjects, setTrainingObjects] = useState([]);
function addTraining(){
const newTrainingObject = {
time: new Date(), //assuming you want it to default to todays date
trainingInfo: "your placeholder text"
};
setTrainingObjects([...trainingObjects, newTrainingObject]);
}
//I am assuming your training object will be a list item here so wrapped in <ul>
return(
<div>
<div className='your list of training things'> (might need to set style as flex and add some padding etc..)
{trainingObjects.length === 0 ? <div/> : trainingObjects.map((trainingObject, index) => (
<YourTrainingObjectComponent trainingObject={trainingObject} trainingItemIndex={index} key={index}/>
))}
</div>
<Button onClick={() => {addTraining}} />
</div>
)
}

Using nested object names in formik/<ErrorMessage/>

Formik's documentation states you can use a lodash type dot path to name/access nested objects (e.g. name.firstName). This is also supposed to apply to it's built in <ErrorMessage/> component. I was working with a React-Typescript tutorial app that uses formik for form inputs and it seemed to work fine under the hood when paired with backend code, but I did notice that the fields that fed nested object values would not throw any errors in the UI. The error itself would be generated, but the <ErrorMessage/> component didn't seem to want to render.
A pared down version of the app is below. The "Field is Required" error should be thrown if you exit a form field without a valid input but again it doesn't work for the nested object fields (first/last name). I was wondering if anyone else has run across this issue. It's a little annoying.
I've seen that formik seems to be paired frequently with Yup for validation, which may make this issue moot, but I haven't gotten quite that far yet. Thanks!
import React from 'react';
import ReactDOM from 'react-dom';
import { ErrorMessage, Field, FieldProps, Formik } from "formik";
import { Form, Button } from "semantic-ui-react";
interface TextProps extends FieldProps {
label: string;
placeholder: string;
}
const TextField: React.FC<TextProps> = ({
field, label, placeholder
}) => (
<Form.Field>
<label>{label}</label>
<Field placeholder={placeholder} {...field} />
<div style={{ color: "red" }}>
<ErrorMessage name={field.name} />
</div>
</Form.Field>
);
const App: React.FC = () => {
return (
<Formik
initialValues={{
name: {
firstName: "",
lastName: ""
},
job: "",
}}
onSubmit={()=>console.log("submitted")}
validate={values => {
const requiredError = "Field is required";
const errors: { [field: string]: string } = {};
if (!values.name.firstName) {
errors["name.firstName"] = requiredError;
}
if (!values.name.lastName) {
errors["name.lastName"] = requiredError;
}
if (!values.job) {
errors["job"] = requiredError;
}
return errors;
}}
>
{({ isValid, dirty}) => {
return (
<Form>
<Field label="First Name" name="name.firstName" component={TextField} />
<Field label="Last Name" name="name.lastName" component={TextField} />
<Field label="Job" name="job" component={TextField} />
<Button type="submit" disabled={!dirty || !isValid}> Add</Button>
</Form>
);
}}
</Formik>
);
};
Have you tried using formik's inbuilt function getIn()? It is used to extract values from deeply nested objects. Check this Q&A
Try changing validate function to this
validate={values => {
const requiredError = "Field is required";
const errors: any = {name: {}};
if (!values.name.firstName) {
errors.name.firstName = requiredError;
}
if (!values.name.lastName) {
errors.name.lastName = requiredError;
}
if (!values.job) {
errors["job"] = requiredError;
}
return errors;
}}

Getting values from FormPanel in ExtReact 6.6.0

How should I be getting values from a FormPanel using ext-react 6.6.0?
According to the API documentation I should be using getValues function, that works in 6.5.1 but I get error _this.form.getValues is not a function in 6.6.0
Code
Works in 6.5.1: https://fiddle.sencha.com/?extreact#view/editor&fiddle/2n05
Fails in 6.6.0 (see console for error): https://fiddle.sencha.com/?extreact#view/editor&fiddle/2n04
I get error _this.form.getValues is not a function in 6.6.0
The reason ref={form => this.form = form}. In extreact-6.6.0 the form variable is not exact formpanel. So for this you need to access like this
ref={form => this.form = (this.form || form.cmp)}}
Another way you use button.up('formpanel') to get the formpanel component. This button is first parameter of your handler.
button.up('formpanel').getValues()
You can check here with working fiddle.
Code Snippet
import React, { Component } from 'react';
import {launch} from '#sencha/ext-react';
import { ExtReact } from '#sencha/ext-react';
import { Container, Label, FormPanel, TextField, Button } from '#sencha/ext-modern';
class App extends Component {
state = {
values:JSON.stringify({
fname: 'null',
lname: 'null'
})
}
submit = (btn) => {
const values = btn.up('formpanel').getValues();
console.log('Values using up selector',values);
console.log('Values using up ref',this.form.getValues());
this.setState({values:JSON.stringify(this.form.getValues())});
}
render() {
return (
<Container defaults={{ margin: 10, shadow: true }}>
<FormPanel title="Form" ref={form => this.form = (this.form || form.cmp)}>
<TextField name="fname" label="First Name"/>
<TextField name="lname" label="Last Name"/>
<Button handler={this.submit} text="Submit"/>
</FormPanel>
<Label padding={'10'} html={this.state.values} />
</Container>
)
}
}
launch(<ExtReact><App /></ExtReact>);

Using react-dates with redux-form results in an error

I am trying to use react-dates with redux-form. Did a render thing for it. I have handled text input and select fields pretty much the same way. Those are working fine.
Getting a funky error on either DateRangePicker or even SingleDatePicker, which I cannot make sense of. Any ideas/suggestions are greatly appreciated.
Did a render component as:
const renderDateRangePicker = ({
input,
focusedInput,
onFocusChange,
startDatePlaceholderText,
endDatePlaceholderText
}) => (
<DateRangePicker
onDatesChange={(start, end) => input.onChange(start, end)}
onFocusChange={onFocusChange}
startDatePlaceholderText={startDatePlaceholderText}
endDatePlaceholderText={endDatePlaceholderText}
focusedInput={focusedInput}
startDate={(input.value && input.value.startDate) || null}
startDateId="startDateId"
endDateId="endDateId"
endDate={(input.value && input.value.endDate) || null}
minimumNights={0}
/>
)
My class is just a form as:
class ActivityForm extends Component {
// values: ['startDate', 'endDate']
state = {
focusedInput: null
}
onFocusChange(focusedInput) {
this.setState({ focusedInput });
}
render () {
const { focusedInput } = this.state
const { handleSubmit, teams } = this.props
return (
<form onSubmit={handleSubmit} className="activity__form">
<div className="activity__form_row">
<Field
name="name"
label="Activity name"
component={renderTextField}
margin="normal"
validate={[required]}
className="activity__form_field_name"
InputLabelProps={{
shrink: true,
}}
/>
<div className="activity__form_spacer"/>
<Field
name="daterange"
onFocusChange={this.onFocusChange}
focusedInput={focusedInput}
component={renderDateRangePicker}
/>
<div className="activity__form_spacer"/>
<Button className="activity__form_button" type="submit">Save</Button>
</div>
</form>
)
}
}
export default reduxForm({ form: 'activity' })(ActivityForm)
For some reason, DateRangePicker causes a strange error: Uncaught TypeError: Cannot read property 'createLTR' of undefined.
What am I missing?
I believe this error is caused by missing or misplaced import of the initialization of react-dates, you can take a look at the Initialize section in (https://github.com/airbnb/react-dates)
import 'react-dates/initialize';
It also looks like there is an update to DateRangePicker:
So include starteDateId and endDateId as props to the DateRangePicker component.
<DateRangePicker
startDateId="2" // PropTypes.string.isRequired,
endDateId="1" // PropTypes.string.isRequired,
startDate={this.props.filters.startDate}
endDate={this.props.filters.endDate}
onDatesChange={this.onDatesChange}
focusedInput={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
showClearDates={true}
numberOfMonths={1}
isOutsideRange={() => false}
/>
It worked for me.

Trying to re-use component in other class and getting error: "Warning: setState(...): Can only update a mounted or mounting > component. "

I'm putting together a little POC, where one piece of it is executing a Search function. The idea is that "Search" will be responsible for the following things:
- Displaying the search input form (e.g., text, date and locations parameters)
- Hit the backend AWS Lambda search API
- Return the result object back to the Search object
- Be able to be re-used on multiple pages
I have two different pages that I want to leverage the "search" functionality, but render the results in different ways.
The search component/form works stand-alone, but I can't figure out how to embed it on the other web pages. Whenever I try to input anything into the "Search form" the console throws the following error:
index.js:2178 Warning: setState(...): Can only update a mounted or mounting > component. This usually means you called setState() on an unmounted component. > This is a no-op.
Please check the code for the Search component.
The "Search" code is below, along with the start of page to display results. I'm a novice to front-end dev so I may be doing something stupid here...looking for input on what I'm doing wrong! Thanks for the help.
import React, { Component } from "react";
import {FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./Home.css";
import DatePicker from 'react-datepicker';
import moment from 'moment';
import PlacesAutocomplete from 'react-places-autocomplete';
import { searchAssets } from "../libs/awsLib";
import 'react-datepicker/dist/react-datepicker.css';
// CSS Modules, react-datepicker-cssmodules.css
import 'react-datepicker/dist/react-datepicker-cssmodules.css';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
freeText: "",
startDate: null,
endDate: null,
radius: 5,
address: '',
searchResults: null
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleStartDateChange = this.handleStartDateChange.bind(this);
this.handleEndDateChange = this.handleEndDateChange.bind(this);
this.handleLocationChange = this.handleLocationChange.bind(this);
}
// Basic change handling
handleChange = event => {
this.setState({ [event.target.id]: event.target.value });
}
// Location change needed because event.id cannot be taken from places object
handleLocationChange = address => {
console.log("The address has been changed: " + address);
this.setState({ address: address });
}
// I need two separate handlers for each piece of the date range
handleStartDateChange = date => {
this.setState({ startDate: date });
}
handleEndDateChange = date => {
this.setState({ endDate: date });
}
//
handleSubmit = async event => {
event.preventDefault();
console.log(event);
// Construct the query string for AWS search
var queryStatement = {
accountId: 'account1', // Dummy
groupId: 'group1', // Dummy
caseId: '999', // Dummy
freeText: this.state.freeText,
radius: this.state.radius
};
if (this.state.startDate != null) {
queryStatement['startDate'] = this.state.startDate.format("MM/DD/YYYY");
}
if (this.state.endDate != null) {
queryStatement['endDate'] = this.state.endDate.format("MM/DD/YYYY");
}
if (this.state.address !== '') {
queryStatement['address'] = this.state.address
}
console.log(queryStatement);
this.setState({ isLoading: true });
// Submit to the search API and load the Payload as a JSON object
try {
var resultSet;
resultSet = await searchAssets(queryStatement);
if (resultSet['StatusCode'] !== 200) {
console.log("Error in lambda function");
}
console.log(JSON.parse(resultSet['Payload']));
this.setState({
isLoading: false,
searchResults: JSON.parse(resultSet['Payload'])
});
}
catch (e) {
console.log(e);
this.setState({ isLoading: false });
}
}
render() {
const autoLocationProps = {
value: this.state.address,
onChange: this.handleLocationChange,
}
console.log(this.state);
// Only fetch suggestions when the input text is longer than 3 characters.
const shouldFetchSuggestions = ({ value }) => value.length > 3
return (
<div className="Search">
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="freeText" bsSize="large">
<ControlLabel>Enter text to search on</ControlLabel>
<FormControl
type="textarea"
onChange={this.handleChange}
value={this.state.freeText}
placeholder="Enter any values to search on"
/>
</FormGroup>
<FormGroup controlId="address" bsSize="large">
<ControlLabel>Enter search location</ControlLabel>
<PlacesAutocomplete
inputProps={autoLocationProps}
shouldFetchSuggestions={shouldFetchSuggestions}
placeholderText="Start typing an address"
/>
</FormGroup>
<FormGroup controlId="radius" bsSize="large">
<ControlLabel>Enter search radius</ControlLabel>
<FormControl
onChange={this.handleChange}
type="text"
value={this.state.radius}
/>
</FormGroup>
<FormGroup controlId="startDate" bsSize="large">
<ControlLabel>Enter start date</ControlLabel>
<DatePicker
onChange={this.handleStartDateChange}
selected={this.state.startDate}
placeholderText="Enter start date"
isClearable={true}
maxDate={this.state.endDate}
/>
</FormGroup>
<FormGroup controlId="endDate" bsSize="large">
<ControlLabel>Enter end date</ControlLabel>
<DatePicker
onChange={this.handleEndDateChange}
selected={this.state.endDate}
placeholderText="Enter end date"
isClearable={true}
minDate={this.state.startDate}
/>
</FormGroup>
<LoaderButton
block
bsSize="large"
type="submit"
isLoading={this.state.isLoading}
text="Search for files"
loadingText="Searching..."
/>
</form>
</div>
);
}
}
View as a table:
import React, { Component } from "react";
import {Table, thead, tbody, th, tr, td } from "react-bootstrap";
import Search from "./Search";
import "./Home.css";
export default class Viewfiles extends Component {
constructor(props) {
super(props);
}
render() {
// Eventually we will render the results in the table...
var resultsObject = new Search();
return (
<div className="Viewfiles">
{resultsObject.render()}
<hr/>
<Table striped condensed responsive >
<thead>
<tr>
<th>Thumbnail</th>
<th>Checksum</th>
<th>Address</th>
<th>Checksum</th>
<th>Description</th>
</tr>
</thead>
</Table>
</div>
);
}
}
React components are meant to be rendered in JSX, like <Search />. If you instantiate them on your own, React won't know to mount them properly, with state and everything.

Categories

Resources