Using nested object names in formik/<ErrorMessage/> - javascript

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;
}}

Related

Unable to edit a post's tags properly [React JS] due to an uncaught type error

new to React and was following along with a tutorial online
I ran into an issue when editing a post, namely, I could edit all the other fields like "title", "creator", "message", but the site wouldn't load when I updated the tag--I'd have to manually refresh the page to see any changes.
Here's the issue-causing code that I had originally:
<Typography variant="body2" color="textSecondary">{post.tags.map((tag) => `#${tag} `)}</Typography>
As you can see, I wanted to enter something like "tag1, tag2, tag3" in the tags field and have it displayed on the post as "#tag1 #tag2 #tag3". However, my console said that post.tags.map is not a function. However, this was used successfully in the tutorial, does this function no longer work?
When I refresh the page, the tags are successfully updated. However, before refreshing, all the components of the page aside from the background css vanishes.
For the sake of just getting the edit function to work, I changed it to:
<Typography variant="body2" color="textSecondary">{post.tags}</Typography>
Which works, but just displays the tags as "tag1 tag2 tag3" without the hashtags.
Again, this is not a big deal, but I am confused as to why post.tags.map did not work. Again, I am new to JS so I might have done something silly.
Here is the code for the postMessage model:
const postSchema = mongoose.Schema({
title: String,
message: String,
creator: String,
tags: [String],
selectedFile: String,
likeCount: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: new Date()
}
});
On top of asking why post.tags.map does not work, how would I get each tag to be displayed with a hashtag before it without having to manually type the hashtag in the field? Currently, with my temporary "fix", all the commas also are displayed on the post, which I assume is because it's putting all the tags as a single string element of the array.
Some additional information that might be helpful:
The site only acts strange when editing a post's tags, not when creating a post with tags. This makes me wonder if there's some funny business with any updatePost functions--however, if that were the case, that would mean I wouldn't be able to update any of the other fields either, which I can.
Here's my code for the Form:
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import FileBase from 'react-file-base64';
import { useDispatch, useSelector } from 'react-redux';
import useStyles from './styles';
import { createPost, updatePost } from '../../actions/posts';
// get current ID
const Form = ({ currentId, setCurrentId }) => {
const [postData, setPostData] = useState({
creator: '', title: '', message: '', tags: '', selectedFile: ''
});
const post = useSelector((state) => currentId ? state.posts.find((p) => p._id === currentId) : null);
const classes = useStyles();
const dispatch = useDispatch();
useEffect(() => {
if(post) setPostData(post);
}, [post]);
const handleSubmit = (e) => {
e.preventDefault();
if(currentId) {
dispatch(updatePost(currentId, postData));
} else {
dispatch(createPost(postData));
}
clear();
}
const clear = () => {
setCurrentId(null);
setPostData({creator: '', title: '', message: '', tags: '', selectedFile: ''});
}
return (
<Paper className={classes.paper}>
<form autoComplete='off' noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}>
<Typography variant="h6">{ currentId ? 'Editing' : 'Creating' } a Post </Typography>
<TextField name="creator" variant="outlined" label="Creator" fullWidth value={postData.creator} onChange={(e) => setPostData({ ...postData, creator: e.target.value })} />
<TextField name="title" variant="outlined" label="Title" fullWidth value={postData.title} onChange={(e) => setPostData({ ...postData, title: e.target.value })} />
<TextField name="message" variant="outlined" label="Message" fullWidth value={postData.message} onChange={(e) => setPostData({ ...postData, message: e.target.value })} />
<TextField name="tags" variant="outlined" label="Tags" fullWidth value={postData.tags} onChange={(e) => setPostData({ ...postData, tags: e.target.value })} />
<div className={classes.fileInput}>
<FileBase type="file" multiple={false} onDone={({base64}) => setPostData({ ...postData, selectedFile: base64})}/>
</div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
<Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button>
</form>
</Paper>
);
}
export default Form;
And, just to be thorough, my code in actions/posts.js:
export const updatePost = (id, post) => async (dispatch) => {
try {
const { data } = await api.updatePost(id, post);
dispatch({ type: 'UPDATE', payload: data });
} catch (error) {
console.log(error);
}
}
If there is any other bits of code you'd need to see, let me know and I'll update this post.
Thanks

Material-UI date picker component not working properly on Safari

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."),
});

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