Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Declare fetch side effects as function #3425

Merged
merged 5 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/simple/src/users/UserEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const UserEditToolbar = props => {
return (
<Toolbar {...props} classes={classes}>
<SaveButton />
<DeleteWithConfirmButton />
<DeleteWithConfirmButton {...props} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pass props to both the toolbar and the button ?

</Toolbar>
);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/ra-core/src/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import useGetOne from './useGetOne';
import useGetList from './useGetList';
import useUpdate from './useUpdate';
import useCreate from './useCreate';
import useDelete from './useDelete';

export {
fetchUtils,
Expand All @@ -24,6 +25,7 @@ export {
useGetList,
useUpdate,
useCreate,
useDelete,
useQueryWithStore,
withDataProvider,
};
44 changes: 44 additions & 0 deletions packages/ra-core/src/fetch/useDelete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CRUD_DELETE } from '../actions/dataActions/crudDelete';
import { DELETE } from '../dataFetchActions';
import useMutation from './useMutation';
import { Identifier } from '../types';

/**
* Get a callback to call the dataProvider with a DELETE verb, the result and the loading state.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the result ?

*
* The return value updates according to the request state:
*
* - start: [callback, { loading: true, loaded: false }]
* - success: [callback, { data: [data from response], loading: false, loaded: true }]
* - error: [callback, { error: [error from response], loading: false, loaded: true }]
*
* @param resource The resource name, e.g. 'posts'
* @param id The resource identifier, e.g. 123
* @param data The data to initialize the new record with, e.g. { title: 'hello, world" }
* @param previousData The record before the delete is applied
* @param options Options object to pass to the dataProvider. May include side effects to be executed upon success of failure, e.g. { onSuccess: { refresh: true } }
*
* @returns The current request state. Destructure as [delete, { data, error, loading, loaded }].
*
* @example
*
* import { useDelete } from 'react-admin';
*
* const DeleteButton = ({ record }) => {
* const [deleteOne, { loading, error }] = useDelete('likes', record.id);
* if (error) { return <p>ERROR</p>; }
* return <button disabled={loading} onClick={deleteOne}>Delete</div>;
* };
*/
const useDelete = (
resource: string,
id: Identifier,
previousData: any = {},
options?: any
) =>
useMutation(
{ type: DELETE, resource, payload: { id, previousData } },
{ ...options, action: CRUD_DELETE }
);

export default useDelete;
9 changes: 5 additions & 4 deletions packages/ra-ui-materialui/src/button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
import MuiButton from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';
import IconButton from '@material-ui/core/IconButton';
import { withStyles, createStyles } from '@material-ui/core/styles';
import { makeStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import { useTranslate } from 'ra-core';

import Responsive from '../layout/Responsive';

const styles = createStyles({
const useStyles = makeStyles({
button: {
display: 'inline-flex',
alignItems: 'center',
Expand All @@ -34,7 +34,7 @@ const styles = createStyles({
const Button = ({
alignIcon = 'left',
children,
classes = {},
classes: classesOverride,
className,
color,
disabled,
Expand All @@ -43,6 +43,7 @@ const Button = ({
...rest
}) => {
const translate = useTranslate();
const classes = useStyles({ classes: classesOverride });
return (
<Responsive
xsmall={
Expand Down Expand Up @@ -121,4 +122,4 @@ Button.defaultProps = {
size: 'small',
};

export default withStyles(styles)(Button);
export default Button;
182 changes: 98 additions & 84 deletions packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React, { Fragment, Component } from 'react';
import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import { withStyles, createStyles } from '@material-ui/core/styles';
import { makeStyles } from '@material-ui/core/styles';
import { fade } from '@material-ui/core/styles/colorManipulator';
import ActionDelete from '@material-ui/icons/Delete';
import classnames from 'classnames';
import inflection from 'inflection';
import { translate, crudDelete } from 'ra-core';
import {
useTranslate,
useDelete,
useRefresh,
useNotify,
useRedirect,
} from 'ra-core';

import Confirm from '../layout/Confirm';
import Button from './Button';

const sanitizeRestProps = ({
basePath,
classes,
crudDelete,
filterValues,
handleSubmit,
handleSubmitWithRedirect,
Expand All @@ -30,91 +33,110 @@ const sanitizeRestProps = ({
...rest
}) => rest;

const styles = theme =>
createStyles({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'@media (hover: none)': {
backgroundColor: 'transparent',
},
const useStyles = makeStyles(theme => ({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
});
},
}));

class DeleteWithConfirmButton extends Component {
state = { isOpen: false };
const DeleteWithConfirmButton = ({
basePath,
classes: classesOverride,
className,
icon,
label = 'ra.action.delete',
onClick,
record,
resource,
redirect: redirectTo,
...rest
}) => {
const [open, setOpen] = useState(false);
const translate = useTranslate();
const notify = useNotify();
const redirect = useRedirect();
const refresh = useRefresh();
const classes = useStyles({ classes: classesOverride });
const [deleteOne] = useDelete(resource, record.id, record, {
onSuccess: () => {
notify('ra.notification.deleted', 'info', { smart_count: 1 });
redirect(redirectTo, basePath);
refresh();
},
onFailure: error =>
notify(
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error',
'warning'
),
undoable: false,
});

handleClick = e => {
this.setState({ isOpen: true });
const handleClick = e => {
setOpen(true);
e.stopPropagation();
};

handleDialogClose = () => {
this.setState({ isOpen: false });
const handleDialogClose = e => {
setOpen(false);
e.stopPropagation();
};

handleDelete = () => {
const { crudDelete, resource, record, basePath, redirect } = this.props;
crudDelete(resource, record.id, record, basePath, redirect);
const handleDelete = () => {
deleteOne();
if (typeof onClick === 'function') {
onClick();
}
};

render() {
const {
classes = {},
className,
icon,
label = 'ra.action.delete',
onClick,
record,
resource,
translate,
...rest
} = this.props;
return (
<Fragment>
<Button
onClick={this.handleClick}
label={label}
className={classnames(
'ra-delete-button',
classes.deleteButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}
>
{icon}
</Button>
<Confirm
isOpen={this.state.isOpen}
title="ra.message.delete_title"
content="ra.message.delete_content"
translateOptions={{
name: inflection.humanize(
translate(`resources.${resource}.name`, {
smart_count: 1,
_: inflection.singularize(resource),
}),
true
),
id: record.id,
}}
onConfirm={this.handleDelete}
onClose={this.handleDialogClose}
/>
</Fragment>
);
}
}
return (
<Fragment>
<Button
onClick={handleClick}
label={label}
className={classnames(
'ra-delete-button',
classes.deleteButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}
>
{icon}
</Button>
<Confirm
isOpen={open}
title="ra.message.delete_title"
content="ra.message.delete_content"
translateOptions={{
name: inflection.humanize(
translate(`resources.${resource}.name`, {
smart_count: 1,
_: inflection.singularize(resource),
}),
true
),
id: record.id,
}}
onConfirm={handleDelete}
onClose={handleDialogClose}
/>
</Fragment>
);
};

DeleteWithConfirmButton.propTypes = {
basePath: PropTypes.string,
classes: PropTypes.object,
className: PropTypes.string,
crudDelete: PropTypes.func.isRequired,
label: PropTypes.string,
record: PropTypes.object,
redirect: PropTypes.oneOfType([
Expand All @@ -123,7 +145,6 @@ DeleteWithConfirmButton.propTypes = {
PropTypes.func,
]),
resource: PropTypes.string.isRequired,
translate: PropTypes.func,
icon: PropTypes.element,
};

Expand All @@ -132,11 +153,4 @@ DeleteWithConfirmButton.defaultProps = {
icon: <ActionDelete />,
};

export default compose(
connect(
null,
{ crudDelete }
),
translate,
withStyles(styles)
)(DeleteWithConfirmButton);
export default DeleteWithConfirmButton;
Loading