react-admin filter with i18n fields - filter

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

Related

How to wrap text in Polaris Index Table cell?

am using Polaris Index Table to display some data in my Shopify app. One of the cells in my table has a long string of text and I want to make it wrap so that it fits the size of the screen. Is there a way to do this in Polaris?
Here is my code:
import {IndexTable, Card, useIndexResourceState, Text} from '#shopify/polaris';
import React from 'react';
function SimpleIndexTableExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison long text here, very very long......',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<Text variant="bodyMd" fontWeight="bold" as="span">
{name}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
I've tried modifying the cells, styles, etc.
To make the text wrap in a cell in a Polaris IndexTable, you can apply the textWrap property to the Text component that you are using within the cell.
You may try this,
import {IndexTable, Card, useIndexResourceState, Text} from '#shopify/polaris';
import React from 'react';
function SimpleIndexTableExample() {
// ...
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<Text variant="bodyMd" fontWeight="bold" as="span" textWrap>
{name}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
// ...
}
I found a workaround for this – in my case there was a HTML representation of a text string available. Then I have used following construction in IndexTable.Row content:
<div dangerouslySetInnerHTML={{__html: myHTMLContent}} />
This allowed using line breaks and other HTML that can be formatted in a way you need. Of course I understand that this adds another problem of creating HTML string, but this is just a workaround.
P.S. that this method adds a risk of XSS attack so can recommend to use this only if you 100% understand the security side.

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

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.

ButterCMS: Unknown field on RootQueryType

Hello Im trying to query data into Gatsby from ButterCMS by following the documentation in gatsby-source-buttercms(https://www.gatsbyjs.org/packages/gatsby-source-buttercms/#gatsby-source-buttercms). But got the error "unknown field allButterJob on RootQueryType". I dont know what i did wrong. Someone please take a look at this for me. Here's my gatsby-config.js:
module.exports = {
siteMetadata: {
title: 'Gatsby Default Starter',
},
plugins: [
'gatsby-plugin-react-helmet',
{
resolve: 'gatsby-source-buttercms',
options: {
authToken: '2a926fdcab34e736332a54e24649cedbaf5d0e89',
contentFields: {
keys: [ // Comma delimited list of content field keys.
'job',
'news'
],
test: 0 // Optional. Set to 1 to enable test mode for viewing draft content.
}
}
}
],
}
Here's where i made the query:
import React from 'react'
import Link from 'gatsby-link'
import HeaderlineSection from '../components/headerlineSection'
import FeatureSection from '../components/featureSection'
import TeamSection from '../components/teamSection'
import NewsSection from '../components/newsSection'
import CareerSection from '../components/careerSection'
const IndexPage = ({data}) => (
<div>
<HeaderlineSection />
<FeatureSection />
<TeamSection />
<NewsSection />
<CareerSection />
{console.log(data)}
</div>
)
export default IndexPage
export const query = graphql`
query IndexPageQuery{
allButterJob{
edges{
node{
id
title
}
}
}
}
`
RootQueryType is the top level “item” in your GraphQL schema (Gatsby v1 sets this up). So the relevant part of the error here is “unknown field allButterJob”, which is pretty self explanatory: the field/type you're trying to query doesn't exist at the top level.
It's likely that it's there under a different name. Usually I hop into Graphiql (localhost:8000/___graphql if you're running gatsby develop under the standard port), where you will see something like this in the sidebar (click on the Docs link if it isn't showing):
From here you can click on “Query” to drill down into it. (Note that this screenshot is from a Gatshy v2 app, so instead of RootQueryType it's just listed as Query.) That'll pull up a list of the fields available on Query (or in your case, RootQueryType) that looks something like this:
In this example, allSitePage is a top-level field available to query like this:
query AnythingYouLikeHere {
allSitePage {
edges {
node {
path
}
}
}
}

How do I dynamically set validations fields in vuelidate

I'm using VueJS2 with vuelidate library. I can validate the fields based on the validation objects. The validation will execute during computed time. But My validations objects is fixed, instead of dynamic. I have some fields will hide based on the selection.
import { validationMixin } from 'vuelidate'
import { required, maxLength, email } from 'vuelidate/lib/validators'
export default {
mixins: [validationMixin],
validations: {
company_name: { required },
company_position_title: { required }
},
methods: {
submit(){
this.$v.touch();
if(this.$v.$invalid == false){
// All validation fields success
}
}
}
}
HTML
<v-select
label="Who are you?"
v-model="select" // can be 'company' or 'others'
:items="items"
:error-messages="selectErrors"
#change="$v.select.$touch();resetInfoFields();"
#blur="$v.select.$touch()"
required
></v-select>
<v-text-field
label="Company Name"
v-model="company_name"
:error-messages="companyNameErrors"
:counter="150"
#input="$v.companyName.$touch()"
#blur="$v.companyName.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-text-field
label="Company Position Title"
v-model="company_position_title"
:error-messages="companyPositionErrors"
:counter="150"
#input="$v.companyPosition.$touch()"
#blur="$v.companyPosition.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-btn #click="submit">submit</v-btn>
Problem
When I select 'other' option and click submit, the this.$v.$invalid is still true. It should be false as there is no validation fields required.
When I select 'company', that two fields must required and validated.
you need a dynamic validation schema
validations () {
if (!this.select === 'company') {
return {
company_name: { required },
company_position_title: { required }
}
}
}
More info: Dynamic validation schema
Another way is using requiredIf
itemtocheck: {
requiredIf: requiredIf(function () {
return this.myitem !== 'somevalue'
}),
minLength: minLength(2) },

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?

Resources