Make react-select 2.0.0 <Async> work with redux-form <Field> - redux-form

react-select just upgraded to 2.0.0 so google results on the first three pages are all about older versions, even the official document, and none of them helped.
My select box can show all options correctly, but redux form won't pick up the value, with the warning: Warning: A component is changing a controlled input of type hidden to be uncontrolled.
I wonder what have I missed here...
Form component:
<Field
name="residentialAddress"
label = "Residential Address"
type="select"
component={AddressField}
validate={required}
/>
Component
export class AddressField extends Component {
searchAddress = input => {
let options = []
return myPromise(input)
.then(suggestions => {
options = suggestions.map(suggestion =>
({
label: suggestion.label,
data: suggestion.value
})
)
return options;
}
).catch(
error => {
return options = [{ label: "Auto fetching failed, please enter your address manually", value: "", isDisabled: true }];
}
);
};
render() {
const {
input,
label,
meta: { touched, error },
type
} = this.props;
return(
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<Async
{...input}
placeholder={label}
isClearable={true}
getOptionValue={(option) => option.residentialAddress}
onChange = { value => input.onChange(value.data) }
loadOptions={this.searchAddress}
/>
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}

Solution: Simply remove the {...input} in <Async>.
Unlike regular custom Field component where we need to pass in {input}, the react-select Async component seems to take care of itself very well and doesn't require any intervene. Someone may explain it in a more professional way perhaps...
Also worth mention for those who come across this question:
loadOptions with promise used to require object {options: options} as return type. Now it changes to just array (options as in my code). But I didn't find any document that mentions this one.
Hope this could help.

Related

react-admin filter with i18n fields

I have followed the docs of marmelab to add i18n to my react-admin, so far so good. One of my ressources has only a field that is translated in 3 languages (en.js fr.js etc.)
name: {
en: 'name',
fr: 'name (French)',
cn: 'name (Chinese)',
},
this is the components I have tried to add a filter to
import { Datagrid, List, TextInput, TextField } from 'react-admin';
function ManufacturerList() {
const manufacturerFilter = [
<TextInput
source='name.en'
alwaysOn
resettable
/>,
];
return (
<List
sort={{ field: 'name', order: 'ASC' }}
filters={manufacturerFilter}
>
<Datagrid
rowClick='show'
bulkActionButtons={false}
>
<TextField source={`name.en`} />
<TextField source={`name.fr`} />
<TextField source={`name.cn`} />
</Datagrid>
</List>
);
}
export default ManufacturerList;
so I have 3 problems:
the filter with name.en gives me an error "something went wrong", the url return after typing "Example" in the textinput filter is
http://localhost:3001/#/manufacturers?displayedFilters=%7B%7D&filter=%7B%22name%22%3A%7B%22en%22%3A%22example%22%7D%7D&order=ASC&page=1&perPage=10&sort=name
once i figure how to use the name.en, can i replace .en by .${locale} that would match the language chose by the user?
my search filter before i18n where source='name.like' to have partial match, is it possible to combine this with the i18n ?
and here is the query that is shown in react query devtool
["manufacturers","getList",{"filter":{"name":{"en":"example"}},"pagination":{"page":1,"perPage":10},"sort":{"field":"name","order":"ASC"}}]
--> if i edit the url and i get to this query below, then the filtering works
["manufacturers","getList",{"filter":{"name.en":"example"},"pagination":{"page":1,"perPage":10},"sort":{"field":"name","order":"ASC"}}]
React-admin sees compound names as paths, so it's normal that a filter on names.en translates to the following filter value in dataProvider.getList(): {"name":{"en":"example"}}.
If that's not what your API expects, then you must do the translation in your dataProvider, by flattening the filter value, e.g. using the flat npm module.
Something like:
const dataProvider = {
...baseDataProvider,
getList: async (resource, params) => {
let newParams = params;
if (resource === "manufacturers") {
newParams.filter = flat(params.filter);
}
return baseDataProvider.getList(resource, newParams);
},
};

How to get 'Last Update Date' of a blog post in GATSBY.js

Hello I'm not a dev so may the question will be easy for you guys. I used the advance starter from gatsby site. The blog is working perfect but I need to provide the LAST UPDATED time under my title. Searched for some solutions but none of them worked. Could you Provide some help?
gatsby-node.js
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type !== 'MarkdownRemark') {
return;
}
const fileNode = getNode(node.parent);
createNodeField({
node,
name: 'modifiedTime',
value: fileNode.mtime
});
};
`````````````````````````
PostListing.jsx
class PostListing extends React.Component {
getPostList() {
const postList = [];
this.props.postEdges.forEach(postEdge => {
postList.push({
path: postEdge.node.fields.slug,
tags: postEdge.node.frontmatter.tags,
cover: postEdge.node.frontmatter.cover,
title: postEdge.node.frontmatter.title,
date: postEdge.node.fields.date,
excerpt: postEdge.node.excerpt,
timeToRead: postEdge.node.timeToRead,
modifiedTime:postEdge.node.modifiedTime
});
});
return postList;
}
render() {
const postList = this.getPostList();
return (
<div className='posts'>
{/* Your post list here. */
postList.map(post => (
<Fragment>
<div className='singlePost__date'>
<h4 style={{color:'white'}}> {post.modifiedTime}</h4>
</div>
<div className='singlePost__Title'>
<Link classname='singlePost' to={post.path} key={post.title}>
<h1 className='singlePost__title'>{post.title}</h1>
</Link>
</div>
</Fragment>
))}
</div>
);
}
}
export default PostListing;
I expect something like
TITLE
last updated : 3/2/2019
You can use information stored in Git to get the latest time when a file was modified.
1st approach
Track it manually, but this can be error-prone if you forget to edit the modified time. So I would recommend that as the last option if you can't get others to work.
2nd approach
You can edit your gatsby-node.js to pull information from Git like so:
const { execSync } = require("child_process")
exports.onCreateNode = ({ node, actions }) => {
// ...
if (node.internal.type === "MarkdownRemark") {
const gitAuthorTime = execSync(
`git log -1 --pretty=format:%aI ${node.fileAbsolutePath}`
).toString()
actions.createNodeField({
node,
name: "gitAuthorTime",
value: gitAuthorTime,
})
}
// ...
}
Then, in your template, you can fetch it:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
fields {
gitAuthorTime
}
# ...
}
}
And, finally, use it in JSX like so:
import React from "react"
const BlogPost = (props) => {
const { gitAuthorTime } = props.data.markdownRemark.fields
render(<p>Updated at: ${gitAuthorTime}</p>)
}
export default BlogPost
3rd approach
Similar to the previous one but it uses a plugin gatsby-transformer-info. It does a similar thing as in the 2nd approach, but you need to access the modified time differently this time. Like so:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
parent {
... on File {
fields {
gitLogLatestDate
}
}
}
# ...
}
}
I wrote more about this in my blog post "Add Updated At To Your Gatsby Blog" if you want to check it out.
Edit: The answer below is actually wrong, since File. modifiedTime is the modifiedTime of the markdown file itself & not the modifiedTime for your content. For example, if you deploy your blog on say, Netlify, then the modifiedTime of your files there will be different than in your local environment.
I think the right answer is to track it separately. If you're using a CMS like NetlifyCMS, you can create a field that automatically update the date/time on every edit.
Wherever you're querying for your markdown files, you can use the below field:
query {
allMarkdownRemark {
edges {
node {
frontmatter { /* other stuff */ }
parent {
... on File {
modifiedTime(formatString: "MM/DD/YYYY")
}
}
}
}
}
}
And access it in your via postEdge.node.parent.modifiedTime

Split string from Redux-Form Field into multiple data points

I'm currently working on a project that uses redux-form fields. We make use of the react-google-autocomplete component to allow users to enter an address in a similar fashion to if they were typing it in Google Maps.
Currently, we strip out the name of the location (if there is one) and just store the address. (So if I typed in "The White House", and selected the suggestion of "The White House, Pennsylvania Avenue Northwest, Washington, DC", we actually end up storing "1600 Pennsylvania Avenue Northwest, Washington, DC".
import React, { PropTypes } from 'react';
import { Field } from 'redux-form';
import Autocomplete from 'react-google-autocomplete';
function emulateTabPress(currentEl) {
const formEls = Array.from(currentEl.form.elements);
const currentIdx = formEls.findIndex(el => el === currentEl);
formEls[currentIdx + 1].focus();
}
function getFormattedGoogleAddress(googleParams) {
return googleParams.formatted_address || googleParams.name;
}
function renderGoogleAutoComplete(props) {
return (
<Autocomplete
type="text"
name="location"
onPlaceSelected={
param => props.input.onChange(getFormattedGoogleAddress(param))
}
types={[]}
value={props.input.value}
onChange={newValue => props.input.onChange(newValue)}
onKeyPress={event => {
if (event.key === 'Enter') {
event.preventDefault();
emulateTabPress(event.target);
}
}}
/>
);
}
renderGoogleAutoComplete.propTypes = {
input: PropTypes.shape({
value: PropTypes.string,
onChange: PropTypes.func
})
};
function AutocompleteLocation({ name, required }) {
return (
<Field
name={name}
required={required}
component={renderGoogleAutoComplete}
/>
);
}
AutocompleteLocation.propTypes = {
name: PropTypes.string.isRequired,
required: PropTypes.bool
};
export default AutocompleteLocation;
What I WANT to do, is store three separate pieces of information
The address (googleParam.formatted_address)
The name of the location (googleParam.name)
The Google ID for the location (googleParam.id)
I've written code that stores this as an object and used that as the value in my component, but then when I try to use any of the values from the store later on, it just shows as "object object"
Any suggestions on how to get these values into discrete data elements?

Angular2 Model Driven Dynamic Form Validation

Please Refer to my plunkr
I've been playing around with the new Angular 2 RC and I think I have figured out how the form validation works.
First I build 2 objects called defaultValidationMessages and formDefinition
private defaultValidationMessages: { [id: string]: string };
formDefinition: {
[fieldname: string]:
{
displayName: string,
placeholder: string,
currentErrorMessage: string,
customValidationMessages: { [errorKey: string]: string }
defaultValidators: ValidatorFn,
defaultValue: any
}
};
Then I load up those objects with the default validators and field information. and build the ControlGroup from the formDefinition object.
this.defaultValidationMessages = {
'required': '{displayName} is required',
'minlength': '{displayName} must be at least {minlength} characters',
'maxlength': '{displayName} cannot exceed {maxlength} characters',
'pattern': '{displayName} is not valid'
}
this.formDefinition = {
'name': {
displayName: 'Name',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose(
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(50)
]),
defaultValue: this.person.name
},
'isEmployee': {
displayName: 'Is Employee',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose([]),
defaultValue: this.person.isEmployee
},
'employeeId': {
displayName: 'Employee Id',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: { 'pattern': '{displayName} must be 5 numerical digits' },
defaultValidators: Validators.compose(
[
Validators.pattern((/\d{5}/).source)
]),
defaultValue: this.person.employeeId
}
}
this.personForm = this.formBuilder.group({});
for (var v in this.formDefinition) {
this.personForm.addControl(v, new Control(this.formDefinition[v].defaultValue, this.formDefinition[v].defaultValidators));
}
this.personForm.valueChanges
.map(value => {
return value;
})
.subscribe(data => this.onValueChanged(data));
Using a technique that I learned from Deborah Kurata's ng-conf 2016 session I bind a method to the ControlGroups valueChanges event.
By defining sets of default validators on each control it allows the control to dynamically append new validators to it based on future action. And then clearing back to the default validators later.
Issue I still have.
I was having an issue getting my typescript intellisense to import the ValidatorFn type. I found it here but I don't think I'm suppose to access it like this:
import { ValidatorFn } from '../../../node_modules/#angular/common/src/forms/directives/validators'
I also had to reset the form by setting some internal members. Is there a better way to reset the form? see below:
(<any> this.programForm.controls[v])._touched = false;
(<any> this.programForm.controls[v])._dirty = false;
(<any> this.programForm.controls[v])._pristine = true;
Please look at my plunk and let me know if there is a better way to handle model driven dynamic form validation?
My import string looks like this and it isn't marked as an error.
import { ValidatorFn } from '#angular/common/src/forms/directives/validators';
And some info about the reset form issue. There is not proper reset feature available yet, but a workaround exists. I've found it in docs.
You need a component field
active: true;
and you need to check it in your form tag:
<form *ngIf="active">
After that you should change your personFormSubmit() method to:
personFormSubmit() {
this.person = new Person();
this.active = false;
setTimeout(()=> {
this.active=true;
this.changeDetectorRef.detectChanges();
alert("Form submitted and reset.");
}, 0);
}
I tried this solution with you plnkr example and seems that it works.

Conditional required validator directive in Angular 2

I need to make certain form fields required or not based on the value of other fields. The built-in RequiredValidator directive doesn't seem to support this, so I created my own directive:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
validate(control: AbstractControl): { [key: string]: any } {
return this.required && !control.value
? { myRequired: true }
: null;
}
}
Sample usage:
<form>
<p><label><input type="checkbox" [(ngModel)]="isNameRequired"> Is Name Required?</label></p>
<p><label>Name: <input type="text" [myRequired]="isNameRequired" #nameControl="ngForm" ngControl="name" [(ngModel)]="name"></label></p>
<p *ngIf="nameControl.control?.hasError('myRequired')">This field is required.</p>
</form>
This works fine if the user first toggles the check box and then types or erases text in the text box. However, if the user toggles the check box while the text box is blank, then the validation message doesn't update appropriately.
How can I modify MyRequiredValidator to trigger validation when its required property is changed?
Note: I'm looking for a solution that only involves changes to MyRequiredValidator. I want to avoid adding any logic to the App component.
Plunker: https://plnkr.co/edit/ExBdzh6nVHrcm51rQ5Fi?p=preview
I would use something like that:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
ngOnChanges() {
// Called when required is updated
if (this.control) {
this.control.updateValueAndValidity();
}
}
validate(control: AbstractControl): { [key: string]: any } {
this.control = control;
return this.required && !control.value
? { myRequired: true }
: null;
}
}
See this plunkr: https://plnkr.co/edit/14jDdUj1rdzAaLEBaB9G?p=preview.

Resources