How to prevent get request from being send if the length of the query field is short in react-admin - react-admin

I'm using react-admin and trying to create a filter with autocomplete field that will make a query as I type and will only start sending the query when the search criteria length is longer then 2.
I'm currently using shouldRenderSuggestions inside of my AutocompleteInput field but this still send two requests with an empty string in the "nickname" filter, this is the code part:
<AutocompleteInput optionText="nickname" shouldRenderSuggestions={(val) => {
return val.trim().length > 2
}}/>
The thing that happens is when I fill in the first and second letters the GET request is sent but with an empty string in the nickname field,
The string input is for example:"abc":
1ST request:
http://website.loc/clients?filter={"nickname":""}&page=1&perPage=25&range=[0,24]&sort=["id","DESC"]
2ND request:
http://website.loc/clients?filter={"nickname":""}&page=1&perPage=25&range=[0,24]&sort=["id","DESC"]
3RD request:
http://website.loc/clients?filter={"nickname":"abc"}&page=1&perPage=25&range=[0,24]&sort=["id","DESC"]
I want to avoid from sending the first two requests entirely.
The full code of the component:
const PostPagination = props => (
<Pagination rowsPerPageOptions={[]} {...props} />
);
const PostFilter = (props) => (
<Filter {...props}>
<ReferenceInput label="Client"
source="client_id"
reference="clients"
allowEmpty
filterToQuery={searchText => ({ nickname: searchText })}>
<AutocompleteInput optionText="nickname" shouldRenderSuggestions={(val) => {
return val.trim().length > 2
}}/>
</ReferenceInput>
</Filter>
);
const PostsList = props => {
return (
<List {...props} perPage={15}
pagination={<PostPagination/>}
filters={<PostFilter/>}
exporter={false}>
<Datagrid>
<TextField source="nickname" sortable={false}/>
<DateField label="Created" source="created_at" showTime/>
</Datagrid>
</List>
);
};
Edit: same question goes for "search-as-you-type" fields like <TextInput> inside a <Filter> field, I started to ask a new question but realized it will be kind of a duplicate,
This is the code that also sends requests starting from 1 char, in this case there isn't even a shouldRenderSuggestions option to force it to send empty requests
const ClientFilter = (props) => (
<Filter {...props}>
<TextInput label="Search" source="str" alwaysOn/>
</Filter>
);
Live example of code in codesandbox.io

I stumbled upon this issue, too. The best I've come up with so far is a small wrapper component that prevents the ReferenceInput from triggering API requests unless a certain condition is met:
const ConditionalFilter = (props) => {
const { children, condition, setFilter } = props;
const conditionalSetFilter = (val) => {
if (setFilter && condition(val)) setFilter(val);
};
return React.cloneElement(children, { ...props, setFilter: conditionalSetFilter });
};
Used like this:
const condition = val => val.trim().length > 2;
return (
<ReferenceInput
source="…"
reference="…"
shouldRenderSuggestions={condition}
>
<ConditionalFilter condition={condition}>
<AutocompleteInput />
</ConditionalFilter>
</ReferenceInput>
);
Update for react-admin v3: (without the wrapper component, which is no longer necessary/useful)
const condition = (val) => !!val && val.trim().length > 2;
return (
<ReferenceInput
source="…"
reference="…"
filterToQuery={(val) => (condition(val) ? { name: val.trim() } : {})}
>
<AutocompleteInput shouldRenderSuggestions={condition} />
</ReferenceInput>
);

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>

In React-admin transform method, Why it is unavailable to get current state?

Hi Im using "react": "^16.13.1" and "react-admin": "^3.8.1"
Im working on page using
const transform = (data) => {
if (option === 'status') {
return {
comment: data.comment,
ids: udiEditDisplayInfo.selectedIds,
status: data.statusToBe,
};
} else if (option === 'customer') {
console.error('Transform option is Customer!!');
//To be implemented
}
};
return (
<Edit {...props} actions={<TopToolbar />} transform={transform} onSuccesss={onSuccess} onFailure={onFailure}>
<SimpleForm toolbar={<CustomToolbar />} redirect={false}>
...
<RadioGroup name="option" value={option} onChange={handleChange}>
<FormControlLabel value="customer" control={<Radio />} label="Customer" />
...
)
when Radio button clicked, 'option' state changed.
the problem is .. in transform method, they can not recognize the change of option state!
options is still default value(customer)
somebody help me please..

Adding custom success and error messages for the create and edit views

So in the docs it says just to add a prop on the edit component with an onSuccess / onFailure function and I've done that but navigating to the page to edit throws an error like this:
Warning: Unknown event handler property `onSuccess`. It will be ignored.
code here:
export const AffiliateEdit = (props) => {
const notify = useNotify();
const onSuccess = () => {
notify('Affiliate saved successfully');
}
return (
<Edit {...props} onSuccess={onSuccess}>
<SimpleForm redirect="list">
<ColorInput source="color" />
<TextInput source="name" validate={[required()]} />
<ReferenceInput
source="network_id"
reference="networks"
validate={[required()]}
>
<SelectInput optionText="name" />
</ReferenceInput>
<TextInput
source="reference"
validate={[required()]}
helperText="Please use all lower case, no spaces or underscores e.g affiliatename"
/>
</SimpleForm>
</Edit>
)
};
doc ref here: https://marmelab.com/react-admin/CreateEdit.html
Nvm figured it out, just needed to update react-admin lol

React-admin: Can I truncate a <TextField> within a <List>?

Is there a way to truncate text returned by the <TextField> and show ...?
See title column within image below (I want to truncate the title after 20 chars).
Is there a specific prop? Unfortunately, I didn't find any clue within the react-admin doc.
Thank you in advance
You can actually manipulate any field outside a <List>,and then get the <Datagrid> to render that field, as long as it's iterative.
const CustomTitleField = ({ record }) => {
// "record" is a prop received from the Datagrid
let str = record.title;
return record ? (
{/* If length is greater than 20 characters, slice and add ellipsis.*/}
<span>{str.length > 20 ? str.slice(0, 20) + "..." : str}</span>
) : null;
};
// Then, within your list, do this...
export const CommentList => (
<List {...props}>
<Datagrid>
<CustomTitleField /> // this should render with your truncate logic
// ...
</Datagrid>
</List>
);
Let me know how it goes, after try this out!!
You can use the cellClassName to customize the cell style inside a Datagrid.
https://marmelab.com/react-admin/Fields.html#styling-fields
I had a similar question and here is what works for me.
const useStyles = makeStyles((theme) => ({
tableHeader: {
fontWeight: "bold",
textTransform: "capitalize",
},
tableCell: {
maxWidth: 300,
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}
}));
const MyList = ({ ...props }) => {
const classes = useStyles();
return (
<List
{...props}
>
<Datagrid>
<TextField
headerClassName={classes.tableHeader}
cellClassName={classes.tableCell}
source="detail"
label="Detail"
/>
</Datagrid>
</List>
);
};

React-Admin: How to redirect to a specified `list` after hitting 'save' on a custom route

I have a custom route:
<Route
exact
path="/assetsBulkCreate"
component={ComponentWithPermissions(AssetsBulkCreate)}
/>
It is used to create assets in bulk:
export const AssetsBulkCreate = ({permissions, ...props}) => {
return (
<Create
resource="assets/bulkInsert"
...
This works.
However, after we hit save the page is redirected to the Dashboard.
I would like to redirect it to the list of the resource assets (which is a different resource).
How can this be done?
P.S. The redirect prop does not let us specify a different resource, so I cannot use list as value there (it does not even work because the custom route does not have a list).
The redirect prop also accept a function. See the documentation
For example:
const redirect = (basePath, id, data) => `/author/${data.author_id}/show`;
export const PostEdit = (props) => {
<Edit {...props}>
<SimpleForm redirect={redirect}>
...
</SimpleForm>
</Edit>
);
You can redirect to the list view. See documentation
export const PostCreate = (props) => {
<Create {...props}>
<SimpleForm redirect="list">
...
</SimpleForm>
</Edit>
);