Skip to content

Commit

Permalink
feat: add more react-query examples
Browse files Browse the repository at this point in the history
  • Loading branch information
marceloschreiber committed Oct 7, 2020
1 parent 668fad1 commit 0bcc68c
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 111 deletions.
21 changes: 13 additions & 8 deletions src/App.js
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { BrowserRouter } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { ReactQueryCacheProvider, QueryCache } from 'react-query';
import { ReactQueryDevtools } from 'react-query-devtools';

import ErrorBoundary from './pages/Fallback/ErrorBoundary';
Expand All @@ -10,18 +11,22 @@ import Routes from './routes/Routes';

import './App.css';

const queryCache = new QueryCache();

function App() {
const { t } = useTranslation();

return (
<BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
<Helmet title={t('helmet.title.app')} />
<Shell title={t('shell.title')} />
<ErrorBoundary>
<Routes />
</ErrorBoundary>
</BrowserRouter>
<ReactQueryCacheProvider queryCache={queryCache}>
<BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
<Helmet title={t('helmet.title.app')} />
<Shell title={t('shell.title')} />
<ErrorBoundary>
<Routes />
</ErrorBoundary>
</BrowserRouter>
</ReactQueryCacheProvider>
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useAuthority.js
Expand Up @@ -3,9 +3,9 @@ import Constants from '../util/Constants';
import APIProvider from '../util/api/url/APIProvider';

export function useHasAccess(allowedAuthorities, authorityKey) {
const { data, status } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, APIProvider.getUrl('GET_USER_LOGGED'), null);
const { data, isSuccess } = useGet(Constants.REACT_QUERY.KEYS.RQ_GET_USER_LOGGED, APIProvider.getUrl('GET_USER_LOGGED'), null);

if (status !== Constants.REACT_QUERY.CODES.SUCCESS) {
if (!isSuccess) {
return null;
}

Expand Down
83 changes: 54 additions & 29 deletions src/hooks/useRequest.js
@@ -1,64 +1,89 @@
import { usePaginatedQuery, useQuery } from 'react-query';
import { useMutation, usePaginatedQuery, useQuery } from 'react-query';

import Request from '../util/api/engine/Request';

const FIVE_MINUTES_IN_MILLISECONDS = 1000 * 60 * 5;

const STALE_TIME = {
staleTime: FIVE_MINUTES_IN_MILLISECONDS,
};

const REQUEST = {
GET: 'get',
POST: 'get',
const REQUEST_TYPE = {
POST: 'post',
PUT: 'put',
PATCH: 'patch',
DELETE: 'delete',
};

const _fetchData = (operation, url, dataParam, config) => {
const _fetchData = (url, dataParam, config) => {
return async () => {
const res = await Request[operation](url, dataParam, config);
const res = await Request.get(url, dataParam, config);
return res.data;
};
};

const _useOperation = (reactQueryKey, operation, url, dataParam, config) => {
const { data, status } = useQuery(reactQueryKey, _fetchData(operation, url, dataParam, config), STALE_TIME);
return { data, status };
const _performRequest = ({ operation, url, dataParam, requestConfig }) => {
return Request[operation](url, dataParam, requestConfig);
};

const _useOperation = (reactQueryKey, url, config) => {
return useQuery(reactQueryKey, _fetchData(url, null, config));
};

const _useMutation = ({ operation, url, requestConfig, mutationOptions }) => {
return useMutation((dataParam) => {
_performRequest({
operation,
url,
dataParam,
requestConfig,
});
}, mutationOptions);
};

const _usePaginatedOperation = (reactQueryKey, pageDependency, operation, url, dataParam, config) => {
const _usePaginatedOperation = (reactQueryKey, pageDependency, url, config) => {
const parameters = {
params: {
...dataParam,
page: pageDependency,
},
};
const { resolvedData, latestData, status } = usePaginatedQuery([reactQueryKey, pageDependency], _fetchData(operation, url, parameters, config), STALE_TIME);
return { resolvedData, latestData, status };
return usePaginatedQuery([reactQueryKey, pageDependency], _fetchData(url, parameters, config));
};

export function useGet(reactQueryKey, url, config) {
return _useOperation(reactQueryKey, REQUEST.GET, url, null, config ? config : null);
return _useOperation(reactQueryKey, url, config);
}

export function usePaginatedGet(reactQueryKey, pageDependency, url, config) {
return _usePaginatedOperation(reactQueryKey, pageDependency, REQUEST.GET, url, null, config ? config : null);
return _usePaginatedOperation(reactQueryKey, pageDependency, url, config);
}

export function usePost(reactQueryKey, url, dataParam, config) {
return _useOperation(reactQueryKey, REQUEST.POST, url, dataParam ? dataParam : null, config ? config : null);
export function usePost({ url, requestConfig, mutationOptions }) {
return _useMutation({
operation: REQUEST_TYPE.POST,
url,
requestConfig,
mutationOptions,
});
}

export function usePut(reactQueryKey, url, dataParam, config) {
return _useOperation(reactQueryKey, REQUEST.PUT, url, dataParam ? dataParam : null, config ? config : null);
export function usePut({ url, requestConfig, mutationOptions }) {
return _useMutation({
operation: REQUEST_TYPE.PUT,
url,
requestConfig,
mutationOptions,
});
}

export function usePatch(reactQueryKey, url, dataParam, config) {
return _useOperation(reactQueryKey, REQUEST.PATCH, url, dataParam ? dataParam : null, config ? config : null);
export function usePatch({ url, requestConfig, mutationOptions }) {
return _useMutation({
operation: REQUEST_TYPE.PATCH,
url,
requestConfig,
mutationOptions,
});
}

export function useDelete(reactQueryKey, url, config) {
return _useOperation(reactQueryKey, REQUEST.DELETE, url, null, config ? config : null);
export function useDelete({ url, requestConfig, mutationOptions }) {
return _useMutation({
operation: REQUEST_TYPE.DELETE,
url,
requestConfig,
mutationOptions,
});
}
65 changes: 65 additions & 0 deletions src/pages/Todo/Add/AddTodoForm.js
@@ -0,0 +1,65 @@
import React from 'react';
import { Form, Formik } from 'formik';
import { Button, ButtonDesign, FlexBox, FlexBoxDirection, FlexBoxAlignItems } from '@ui5/webcomponents-react';
import { useQueryCache } from 'react-query';

import APIProvider from '../../../util/api/url/APIProvider';
import Constants from '../../../util/Constants';
import { usePost } from '../../../hooks/useRequest';

import TodoForm from '../Form/TodoForm';
import TodoFormValidationSchema from '../Form/TodoFormValidationSchema';
import FormData from '../Form/Data';

const styles = {
flexBox: {
width: '100%',
margin: '10px 0',
},
buttons: {
marginRight: '10px',
},
form: {
margin: '0 15px',
},
};

const AddTodoForm = ({ dialogClose }) => {
const queryCache = useQueryCache();
const [addTodo] = usePost({
url: APIProvider.getUrl('CREATE_TODO'),
mutationOptions: {
onSuccess: () => {
queryCache.invalidateQueries([Constants.REACT_QUERY.KEYS.RQ_GET_TODO_LIST]);
},
},
});

const onSubmitEditForm = async (values, actions) => {
actions.setSubmitting(true);
await addTodo({ ...values, isCompleted: values.completed });
actions.setSubmitting(false);
actions.resetForm(true);
dialogClose();
};

return (
<Formik enableReinitialize initialValues={FormData} validationSchema={TodoFormValidationSchema} onSubmit={onSubmitEditForm}>
{({ isSubmitting, handleSubmit }) => (
<Form style={styles.form}>
<TodoForm />
<FlexBox direction={FlexBoxDirection.RowReverse} alignItems={FlexBoxAlignItems.Center} style={styles.flexBox}>
<Button type="submit" disabled={isSubmitting} design={ButtonDesign.Emphasized} icon="paper-plane" style={styles.buttons} onClick={handleSubmit}>
Submit
</Button>
<Button style={styles.buttons} onClick={dialogClose}>
Cancel
</Button>
</FlexBox>
</Form>
)}
</Formik>
);
};

export default AddTodoForm;
13 changes: 3 additions & 10 deletions src/pages/Todo/Edit/TodoEdit.js
Expand Up @@ -9,23 +9,16 @@ import TodoEditForm from './TodoEditForm';
import Constants from '../../../util/Constants';
import APIProvider from '../../../util/api/url/APIProvider';

const onSubmitEditForm = (values, actions) => {
actions.setSubmitting(true);
alert(JSON.stringify(values, null, 2));
actions.resetForm(true);
actions.setSubmitting(false);
};

export default function TodoEdit({ match }) {
const { data, status } = useGet(Constants.REACT_QUERY.KEYS.GET_TODO_BY_ID, APIProvider.getUrl('GET_TODO_BY_ID', [{ value: match.params.id }]));
const { data, isLoading, isSuccess } = useGet(Constants.REACT_QUERY.KEYS.GET_TODO_BY_ID, APIProvider.getUrl('GET_TODO_BY_ID', [{ value: match.params.id }]));

return (
<>
<Helmet title="Edit - TodoList App" />
<NavBack />
<CenteredContent>
{status === Constants.REACT_QUERY.CODES.LOADING && <Spinner />}
{status === Constants.REACT_QUERY.CODES.SUCCESS && <TodoEditForm data={data.data.todos} onSubmitHandler={onSubmitEditForm} />}
{isLoading && <Spinner />}
{isSuccess && <TodoEditForm data={data} />}
</CenteredContent>
</>
);
Expand Down
67 changes: 29 additions & 38 deletions src/pages/Todo/Edit/TodoEditForm.js
@@ -1,58 +1,49 @@
import React from 'react';
import { Form, Formik } from 'formik';
import { useHistory } from 'react-router-dom';
import { Button, ButtonDesign, FlexBox, FlexBoxAlignItems, FlexBoxDirection } from '@ui5/webcomponents-react';

import { Field, Form, Formik } from 'formik';
import { Button, ButtonDesign, FlexBox, FlexBoxAlignItems, FlexBoxDirection, InputType } from '@ui5/webcomponents-react';
import Input from '../../../components/Form/Input/Input';
import Switch from '../../../components/Form/Switch/Switch';
import TextArea from '../../../components/Form/TextArea/TextArea';
import Select from '../../../components/Form/Select/Select';
import TodoEditFormValidationSchema from './TodoEditFormValidationSchema';
import NavBack, { NavBackIcon } from '../../../components/NavBack/NavBack';
import APIProvider from '../../../util/api/url/APIProvider';
import BrowserProvider from '../../../util/browser/BrowserProvider';
import { usePut } from '../../../hooks/useRequest';

import TodoForm from '../Form/TodoForm';
import TodoFormValidationSchema from '../Form/TodoFormValidationSchema';

const style = {
putWrapperUp: {
marginTop: '-50px',
},
};

const typeOptions = [
{ id: 'WORK', text: 'Work' },
{ id: 'PERSONAL', text: 'Personal' },
{ id: 'SCHOOL', text: 'School' },
];
export default function TodoEditForm({ data }) {
const history = useHistory();
const [editTodo] = usePut({
url: APIProvider.getUrl('UPDATE_TODO', [{ value: data.id }]),
});

const priorityOptions = [
{ id: 'LOW', text: 'Low' },
{ id: 'MEDIUM', text: 'Medium' },
{ id: 'HIGH', text: 'High' },
];
const onSubmitEditForm = async (values, actions) => {
actions.setSubmitting(true);
await editTodo({ ...values, isCompleted: values.completed });
actions.setSubmitting(false);
actions.resetForm(true);
history.push(BrowserProvider.getUrl('TODO_LIST'));
};

export default function TodoEditForm({ data, onSubmitHandler }) {
return (
<div style={style.putWrapperUp}>
<h1>Todo Edit Form</h1>
<Formik enableReinitialize initialValues={data} validationSchema={TodoEditFormValidationSchema} onSubmit={onSubmitHandler}>
<Formik enableReinitialize initialValues={data} validationSchema={TodoFormValidationSchema} onSubmit={onSubmitEditForm}>
{({ isSubmitting, handleSubmit }) => (
<Form>
<div>
<h3>Basic Info</h3>
<Field labelText="Name" name="name" required placeholder="Name goes here" type={InputType.Text} component={Input} />
<Field labelText="Description" name="description" required placeholder="Add your description here" rows={5} component={TextArea} />
<Field labelText="Completed" name="completed" required component={Switch} graphical={true} />
</div>
<div>
<h3>Custom Info</h3>
<Field labelText="Priority" name="priority" required component={Select} options={priorityOptions} />
<Field labelText="Type" name="type" required component={Select} options={typeOptions} />
</div>
<div>
<FlexBox direction={FlexBoxDirection.RowReverse} alignItems={FlexBoxAlignItems.Center}>
<Button type="submit" disabled={isSubmitting} onClick={handleSubmit} design={ButtonDesign.Emphasized} icon="paper-plane">
Submit
</Button>
<NavBack text="Cancel" icon={NavBackIcon.NONE} />
</FlexBox>
</div>
<TodoForm />
<FlexBox direction={FlexBoxDirection.RowReverse} alignItems={FlexBoxAlignItems.Center}>
<Button type="submit" disabled={isSubmitting} onClick={handleSubmit} design={ButtonDesign.Emphasized} icon="paper-plane">
Submit
</Button>
<NavBack text="Cancel" icon={NavBackIcon.NONE} />
</FlexBox>
</Form>
)}
</Formik>
Expand Down
10 changes: 10 additions & 0 deletions src/pages/Todo/Form/Data.js
@@ -0,0 +1,10 @@
import TypeOptions from './TypeOptions';
import PriorityOptions from './PriorityOptions';

export default {
name: '',
description: '',
completed: false,
priority: PriorityOptions[0].id,
type: TypeOptions[0].id,
};
5 changes: 5 additions & 0 deletions src/pages/Todo/Form/PriorityOptions.js
@@ -0,0 +1,5 @@
export default [
{ id: 'LOW', text: 'Low' },
{ id: 'MEDIUM', text: 'Medium' },
{ id: 'HIGH', text: 'High' },
];
29 changes: 29 additions & 0 deletions src/pages/Todo/Form/TodoForm.js
@@ -0,0 +1,29 @@
import React from 'react';
import { Field } from 'formik';
import { InputType } from '@ui5/webcomponents-react';

import Input from '../../../components/Form/Input/Input';
import Switch from '../../../components/Form/Switch/Switch';
import TextArea from '../../../components/Form/TextArea/TextArea';
import Select from '../../../components/Form/Select/Select';

import TypeOptions from './TypeOptions';
import PriorityOptions from './PriorityOptions';

const TodoForm = () => (
<>
<div>
<h3>Basic Info</h3>
<Field labelText="Name" name="name" required placeholder="Name goes here" type={InputType.Text} component={Input} />
<Field labelText="Description" name="description" required placeholder="Add your description here" rows={5} component={TextArea} />
<Field labelText="Completed" name="completed" required component={Switch} graphical={true} />
</div>
<div>
<h3>Custom Info</h3>
<Field labelText="Priority" name="priority" required component={Select} options={PriorityOptions} />
<Field labelText="Type" name="type" required component={Select} options={TypeOptions} />
</div>
</>
);

export default TodoForm;

0 comments on commit 0bcc68c

Please sign in to comment.