Skip to content

Commit

Permalink
Merge pull request #6687 from marmelab/remove-declarative-side-effects
Browse files Browse the repository at this point in the history
[BC Break] Remove declarative side effects in dataProvider
  • Loading branch information
djhi authored Oct 15, 2021
2 parents 1e0006e + 95398c5 commit 90a236b
Show file tree
Hide file tree
Showing 11 changed files with 22 additions and 533 deletions.
1 change: 1 addition & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ If you still relied on sagas, you have to port your saga code to react `useEffec
## Removed Deprecated Elements

- Removed `<BulkDeleteAction>` (use `<BulkDeleteButton>` instead)
- Removed declarative side effects in dataProvider hooks (e.g. `{ onSuccess: { refresh: true } }`). Use function side effects instead (e.g. `{ onSuccess: () => { refresh(); } }`)

# Upgrade to 3.0

Expand Down
1 change: 1 addition & 0 deletions docs/Actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ const BulkDeletePostsButton = ({ selectedIds }) => {
```

## Synchronizing Dependant Queries

`useQuery` and all its corresponding specialized hooks support an `enabled` option. This is useful if you need to have a query executed only when a condition is met. For example, in the following example, we only fetch the categories if we have at least one post:
```jsx
// fetch posts
Expand Down
138 changes: 1 addition & 137 deletions packages/ra-core/src/dataProvider/Mutation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { fireEvent, waitFor, act, render } from '@testing-library/react';
import expect from 'expect';

import Mutation from './Mutation';
import { showNotification, refreshView, setListSelectedIds } from '../actions';
import { showNotification } from '../actions';
import DataProviderContext from './DataProviderContext';
import { renderWithRedux, TestContext } from 'ra-test';
import { useNotify } from '../sideEffect';
import { History } from 'history';

describe('Mutation', () => {
it('should render its child function', () => {
Expand Down Expand Up @@ -41,72 +40,6 @@ describe('Mutation', () => {
});
});

it('supports declarative onSuccess side effects', async () => {
let dispatchSpy;
let historyForAssertions: History;

const dataProvider = {
mytype: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })),
};

let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store, history }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
historyForAssertions = history;
return (
<Mutation
type="mytype"
resource="foo"
options={{
onSuccess: {
notification: {
body: 'Youhou!',
level: 'info',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{(mutate, { data }) => (
<button
data-testid="test"
onClick={mutate}
>
{data ? data.foo : 'no data'}
</button>
)}
</Mutation>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitFor(() => {
expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Youhou!', 'info', {
messageArgs: {},
undoable: false,
})
);
expect(historyForAssertions.location.pathname).toEqual('/a_path');
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(
setListSelectedIds('foo', [])
);
});
});

it('supports onSuccess side effects using hooks', async () => {
let dispatchSpy;
const dataProvider = {
Expand Down Expand Up @@ -160,75 +93,6 @@ describe('Mutation', () => {
});
});

it('supports declarative onFailure side effects', async () => {
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
let dispatchSpy;
let historyForAssertions: History;

const dataProvider = {
mytype: jest.fn(() =>
Promise.reject({ message: 'provider error' })
),
};

let getByTestId;
act(() => {
const res = render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store, history }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
historyForAssertions = history;
return (
<Mutation
type="mytype"
resource="foo"
options={{
onFailure: {
notification: {
body: 'Damn!',
level: 'warning',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{(mutate, { error }) => (
<button
data-testid="test"
onClick={mutate}
>
{error ? error.message : 'no data'}
</button>
)}
</Mutation>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
getByTestId = res.getByTestId;
});

const testElement = getByTestId('test');
fireEvent.click(testElement);
await waitFor(() => {
expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Damn!', 'warning', {
messageArgs: {},
undoable: false,
})
);
expect(historyForAssertions.location.pathname).toEqual('/a_path');
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(
setListSelectedIds('foo', [])
);
});
});

it('supports onFailure side effects using hooks', async () => {
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
let dispatchSpy;
Expand Down
7 changes: 1 addition & 6 deletions packages/ra-core/src/dataProvider/Mutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ const Mutation = ({
// This is used to detect options in useDataProvider
options = { onSuccess: undefined },
}: MutationProps) =>
children(
...useMutation(
{ type, resource, payload },
{ ...options, withDeclarativeSideEffectsSupport: true }
)
);
children(...useMutation({ type, resource, payload }, options));

export default Mutation;
136 changes: 1 addition & 135 deletions packages/ra-core/src/dataProvider/Query.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import Query from './Query';
import { CoreAdmin, Resource } from '../core';
import { renderWithRedux, TestContext } from 'ra-test';
import DataProviderContext from './DataProviderContext';
import { showNotification, refreshView, setListSelectedIds } from '../actions';
import { showNotification } from '../actions';
import { useNotify, useRefresh } from '../sideEffect';
import { History } from 'history';

describe('Query', () => {
it('should render its child', () => {
Expand Down Expand Up @@ -252,72 +251,6 @@ describe('Query', () => {
expect(dispatchSpy.mock.calls.length).toEqual(3);
});

it('supports declarative onSuccess side effects', async () => {
let dispatchSpy;
let historyForAssertions: History;

const dataProvider = {
getList: jest.fn(() =>
Promise.resolve({ data: [{ id: 1, foo: 'bar' }], total: 42 })
),
};

act(() => {
render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store, history }) => {
dispatchSpy = jest.spyOn(store, 'dispatch');
historyForAssertions = history;
return (
<Query
type="getList"
resource="foo"
options={{
onSuccess: {
notification: {
body: 'Youhou!',
level: 'info',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{({ loading, data, total }) => (
<div
data-testid="test"
className={
loading ? 'loading' : 'idle'
}
>
{loading ? 'no data' : total}
</div>
)}
</Query>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
});

await waitFor(() => {
expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Youhou!', 'info', {
messageArgs: {},
undoable: false,
})
);
expect(historyForAssertions.location.pathname).toEqual('/a_path');
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(
setListSelectedIds('foo', [])
);
});
});

it('supports onSuccess function for side effects', async () => {
let dispatchSpy;
const dataProvider = {
Expand Down Expand Up @@ -372,73 +305,6 @@ describe('Query', () => {
});
});

it('supports declarative onFailure side effects', async () => {
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
let dispatchSpy;
let historyForAssertions: History;

const dataProvider = {
getList: jest.fn(() =>
Promise.reject({ message: 'provider error' })
),
};

act(() => {
render(
<DataProviderContext.Provider value={dataProvider}>
<TestContext>
{({ store, history }) => {
historyForAssertions = history;
dispatchSpy = jest.spyOn(store, 'dispatch');
return (
<Query
type="getList"
resource="foo"
options={{
onFailure: {
notification: {
body: 'Damn!',
level: 'warning',
},
redirectTo: '/a_path',
refresh: true,
unselectAll: true,
},
}}
>
{({ loading, data, total }) => (
<div
data-testid="test"
className={
loading ? 'loading' : 'idle'
}
>
{loading ? 'no data' : total}
</div>
)}
</Query>
);
}}
</TestContext>
</DataProviderContext.Provider>
);
});

await waitFor(() => {
expect(dispatchSpy).toHaveBeenCalledWith(
showNotification('Damn!', 'warning', {
messageArgs: {},
undoable: false,
})
);
expect(historyForAssertions.location.pathname).toEqual('/a_path');
expect(dispatchSpy).toHaveBeenCalledWith(refreshView());
expect(dispatchSpy).toHaveBeenCalledWith(
setListSelectedIds('foo', [])
);
});
});

it('supports onFailure function for side effects', async () => {
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
let dispatchSpy;
Expand Down
8 changes: 1 addition & 7 deletions packages/ra-core/src/dataProvider/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ const Query: FunctionComponent<QueryProps> = ({
// Provides an undefined onSuccess just so the key `onSuccess` is defined
// This is used to detect options in useDataProvider
options = { onSuccess: undefined },
}) =>
children(
useQuery(
{ type, resource, payload },
{ ...options, withDeclarativeSideEffectsSupport: true }
)
);
}) => children(useQuery({ type, resource, payload }, options));

export default Query;
Loading

0 comments on commit 90a236b

Please sign in to comment.