Skip to content

Commit

Permalink
Use React Query for state management (#1518)
Browse files Browse the repository at this point in the history
* Add react-query and react-query devtools

* Add docs

* Use RQ in InformationAdminPage
  • Loading branch information
robines authored Oct 10, 2024
1 parent 0e53f41 commit 71efc24
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 47 deletions.
2 changes: 2 additions & 0 deletions docs/technical/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- [Creating react components (conventions)](/docs/technical/frontend/components.md)
- [Forms and schemas](/docs/technical/frontend/forms.md)
- [Cypress Setup Documentation](/docs/technical/frontend/cypress.md)
- [Data fetching](./frontend/data-fetching.md)

### Backend

- [Billig (payment system)](/docs/technical/backend/billig.md)
Expand Down
19 changes: 19 additions & 0 deletions docs/technical/frontend/data-fetching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Data fetching

We use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (built into browsers) to fetch data.
The `fetch` function is quite simple to use. It returns
a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves to
the response of the request.

We use [React Query](https://tanstack.com/query/v3) for state management. State management in React is notoriously hard
to get 100% right and safe, which is why using a library such as RQ is a good idea. It saves us from a lot of potential
common bugs and headaches with managing state all by ourselves.

If you're not convinced, read this [great article](https://tkdodo.eu/blog/why-you-want-react-query) by TkDodo on Why You
~~Want~~ Need React Query. It explores a lot of common pitfalls/bugs. At the time of me writing this, these
pitfalls/bugs are found absolutely everywhere we do data fetching in Samfundet4. Hopefully over time, we will replace
these instances with safe state management using RQ.

## Getting started

Please check out the [RQ docs Quick Start](https://tanstack.com/query/latest/docs/framework/react/quick-start).
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"@babel/plugin-transform-react-jsx": "^7.22.15",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"axios": "^1.7.4",
"classnames": "^2.3.2",
"cmdk": "^0.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { useEffect, useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { Button, Link } from '~/Components';
import { CrudButtons } from '~/Components/CrudButtons/CrudButtons';
import { Table } from '~/Components/Table';
import { deleteInformationPage, getInformationPages } from '~/api';
import type { InformationPageDto } from '~/dto';
import { useCustomNavigate, useTitle } from '~/hooks';
import { KEY } from '~/i18n/constants';
import { reverse } from '~/named-urls';
Expand All @@ -15,42 +13,21 @@ import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout';

export function InformationAdminPage() {
const navigate = useCustomNavigate();
const [informationPages, setInformationPages] = useState<InformationPageDto[]>([]);
const [showSpinner, setShowSpinner] = useState<boolean>(true);
const { t } = useTranslation();
useTitle(t(KEY.admin_information_manage_title));

// Stuff to do on first render.
// TODO: add permissions on render
// biome-ignore lint/correctness/useExhaustiveDependencies: t does not need to be in deplist
useEffect(() => {
getInformationPages()
.then((data) => {
setInformationPages(data);
setShowSpinner(false);
})
.catch((error) => {
toast.error(t(KEY.common_something_went_wrong));
console.error(error);
});
}, []);

function deletePage(slug_field: string | undefined) {
if (!slug_field) return;
const { data, isLoading } = useQuery({
queryKey: ['informationpages'],
queryFn: getInformationPages,
});

deleteInformationPage(slug_field)
.then(() => {
getInformationPages().then((data) => {
setInformationPages(data);
setShowSpinner(false);
});
toast.success(t(KEY.common_delete_successful));
})
.catch((error) => {
toast.error(t(KEY.common_something_went_wrong));
console.error(error);
});
}
const deletePageMutation = useMutation({
mutationFn: (slug: string) => {
// if (!slug) return; // TODO: raise err
return deleteInformationPage(slug);
},
});

const tableColumns = [
{ content: t(KEY.common_name), sortable: true },
Expand All @@ -60,7 +37,7 @@ export function InformationAdminPage() {
'', // Buttons
];

const data = informationPages.map((element) => {
const tableData = data?.map((element) => {
const pageUrl = reverse({
pattern: ROUTES.frontend.information_page_detail,
urlParams: { slugField: element.slug_field },
Expand All @@ -87,7 +64,7 @@ export function InformationAdminPage() {
}}
onDelete={() => {
if (window.confirm(t(KEY.admin_information_confirm_delete) ?? '')) {
deletePage(element.slug_field);
deletePageMutation.mutate(element.slug_field);
}
}}
/>
Expand All @@ -105,8 +82,8 @@ export function InformationAdminPage() {
);

return (
<AdminPageLayout title={title} backendUrl={backendUrl} header={header} loading={showSpinner}>
<Table columns={tableColumns} data={data} />
<AdminPageLayout title={title} backendUrl={backendUrl} header={header} loading={isLoading}>
<Table columns={tableColumns} data={tableData || []} />
</AdminPageLayout>
);
}
25 changes: 16 additions & 9 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { AuthContextProvider } from '~/context/AuthContext';
import '~/global.scss';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { RouterProvider } from 'react-router-dom';
import { GlobalContextProvider } from '~/context/GlobalContextProvider';
import { OrganizationContextProvider } from '~/context/OrgContextProvider';
import { reportWebVitals } from '~/reportWebVitals';
import { router } from '~/router/router';

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<AuthContextProvider>
<GlobalContextProvider>
<OrganizationContextProvider>
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
</OrganizationContextProvider>
</GlobalContextProvider>
</AuthContextProvider>,
<QueryClientProvider client={queryClient}>
<AuthContextProvider>
<GlobalContextProvider>
<OrganizationContextProvider>
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
</OrganizationContextProvider>
</GlobalContextProvider>
</AuthContextProvider>
<ReactQueryDevtools />
</QueryClientProvider>,
);

// If you want to start measuring performance in your app, pass a function
Expand Down
39 changes: 39 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5030,6 +5030,43 @@ __metadata:
languageName: node
linkType: hard

"@tanstack/query-core@npm:5.59.0":
version: 5.59.0
resolution: "@tanstack/query-core@npm:5.59.0"
checksum: 10c0/c6b5c935670ea0b4ba1e3936d9f319cae5ba098d82863115546d43d45e9e171b9fce990aecd56cd7ef9a8b1de64cb4f74f6c8f83cc1e61cd62ee99d2c34fbefc
languageName: node
linkType: hard

"@tanstack/query-devtools@npm:5.58.0":
version: 5.58.0
resolution: "@tanstack/query-devtools@npm:5.58.0"
checksum: 10c0/3b0ad21503682f6c452f5f78f5cc9007e902f455c70fd7d537ca2c3b13f41607ab4ad612c0ca19abc24aa2a9fa9bf019101dfeb43abfa53f04782322558f19f6
languageName: node
linkType: hard

"@tanstack/react-query-devtools@npm:^5.59.0":
version: 5.59.0
resolution: "@tanstack/react-query-devtools@npm:5.59.0"
dependencies:
"@tanstack/query-devtools": "npm:5.58.0"
peerDependencies:
"@tanstack/react-query": ^5.59.0
react: ^18 || ^19
checksum: 10c0/19e186faa6cf33e656985be5279317af211a038177862f90dce38564d9f406e92b2ba276f750cea779983b95e3ef70f924928362e8987b0c4419c17b19d3669f
languageName: node
linkType: hard

"@tanstack/react-query@npm:^5.59.0":
version: 5.59.0
resolution: "@tanstack/react-query@npm:5.59.0"
dependencies:
"@tanstack/query-core": "npm:5.59.0"
peerDependencies:
react: ^18 || ^19
checksum: 10c0/035f1039a84cdfa90332c63780894c7b01f3024d17e82a7eb0c148912c6e4913bd5c56897698f3ac7cc5057710895932d45085c447a9d3dda88cf05f8ea7d226
languageName: node
linkType: hard

"@testing-library/dom@npm:^9.0.0":
version: 9.3.3
resolution: "@testing-library/dom@npm:9.3.3"
Expand Down Expand Up @@ -10450,6 +10487,8 @@ __metadata:
"@storybook/react": "npm:^7.4.6"
"@storybook/react-vite": "npm:^7.4.6"
"@storybook/testing-library": "npm:^0.2.2"
"@tanstack/react-query": "npm:^5.59.0"
"@tanstack/react-query-devtools": "npm:^5.59.0"
"@types/node": "npm:^20.8.6"
"@types/react": "npm:^18.2.28"
"@types/react-dom": "npm:^18.2.13"
Expand Down

0 comments on commit 71efc24

Please sign in to comment.