Skip to content

Commit a79f56f

Browse files
committed
Update dependencies, go to GitHub Actions and fix lint problems
1 parent 24831f6 commit a79f56f

File tree

10 files changed

+3779
-6796
lines changed

10 files changed

+3779
-6796
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"extends": [
88
"eslint:recommended",
99
"plugin:react/recommended",
10-
"plugin:@typescript-eslint/recommended"
10+
"plugin:@typescript-eslint/recommended",
11+
"prettier"
1112
],
1213
"parser": "@typescript-eslint/parser",
1314
"parserOptions": {

.github/workflows/test.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: GitHub Action Status
2+
3+
on:
4+
push:
5+
branches: [ "master" ]
6+
pull_request:
7+
branches: [ "master" ]
8+
9+
jobs:
10+
test:
11+
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: "node"
19+
- run: npm ci
20+
- run: npm run lint
21+
- run: npm test
22+
- uses: coverallsapp/github-action@v2

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

README.md

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,98 @@
11
# React Router Lazy Page
22

3-
[![Build Status](https://app.travis-ci.com/LeMarck/react-router-lazy-page.svg?branch=master)](https://app.travis-ci.com/LeMarck/react-router-lazy-page)
3+
![GitHub Action Status](https://github.com/LeMarck/react-router-lazy-page/actions/workflows/test.yaml/badge.svg)
44
[![Coverage Status](https://coveralls.io/repos/github/LeMarck/react-router-lazy-page/badge.svg?branch=master)](https://coveralls.io/github/LeMarck/react-router-lazy-page?branch=master)
55

6-
[React.lazy](https://ru.reactjs.org/docs/code-splitting.html) alternative for pages with data fetching
6+
> Используется в связке с `react-router@5`
7+
>
8+
> В `react-router@>=6` появились `clientLoader/loader` для загрузки данных
79
8-
## Page
10+
HOC для загрузки данных на страницу
911

10-
**Page** is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.jsx`, `.ts`
11-
, or `.tsx`
12+
## Альтернативы
1213

13-
**pages/About.page.tsx**
14+
Стразу же укажу альтернативы, хотя это решение использовалось в боевом проекте (сейчас его судьба мне не известна, проект был запущен мной в 2021 году), но это приложение имеет свои особенности которые и подтолкнули меня к созданию этого решения
1415

15-
```tsx
16-
const About = () => <div>About</div>;
17-
18-
export default About;
19-
```
16+
- [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/overview)
17+
- [SWR](https://swr.vercel.app)
18+
- [React Router](https://reactrouter.com/start/framework/data-loading)
2019

21-
[**Next.js – Pages**](https://nextjs.org/docs/basic-features/pages)
20+
## Предпосылки
2221

23-
## Data Fetching
22+
Хотел получить механизм единой загрузки данных, чтобы избавиться от необходимости `if/else` с лоудером
2423

25-
**pages/Blog.page.tsx**
24+
Пример:
2625

2726
```tsx
28-
29-
const Blog = ({ posts }) => <ul>
30-
{posts.map((post) => (
31-
<li>{post.title}</li>
32-
))}
33-
</ul>;
34-
35-
export async function getStaticProps() {
36-
const res = await fetch('https://.../posts')
37-
const posts = await res.json()
38-
39-
return { props: { posts } };
27+
const Page = () => {
28+
const {profile, isLoadingProfile} = useProfile();
29+
const {todoList, isLoadingToDoList} = useToDoList();
30+
31+
if (isLoadingProfile) {
32+
return <div>Loading...</div>;
33+
}
34+
35+
if (isLoadingToDoList) {
36+
return <div>Loading...</div>;
37+
}
38+
39+
return (
40+
<div>
41+
<div>{profile.name}</div>
42+
<ul>
43+
{todoList.map((todo) => (
44+
<li>{todo.title}</li>
45+
))}
46+
</ul>
47+
</div>
48+
)
4049
}
41-
42-
export default Blog
4350
```
4451

45-
[**Next.js – Data Fetching**](https://nextjs.org/docs/basic-features/data-fetching)
52+
На момент 2020-2021 годов ещё не было React Router v6, а о существовании `swr` и `@tanstack/react-query` я не знал. Судя по npmjs.com они только появлялись и набирали популярность. Мне понравился подход Next.js с загрузкой данных через функцию `getInitialProps` (сейчас так уже не делают), который я и решил повторить
4653

47-
## Usage
48-
49-
**App.tsx**
54+
В итоге получился вот такой подход
5055

5156
```tsx
52-
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
53-
import { lazyPage } from 'react-lazy-page';
54-
import { Suspense } from 'react';
57+
// pages/TodoList.tsx
58+
const TodoList = ({
59+
profile,
60+
todoList
61+
}) => (
62+
<div>
63+
<div>{profile.name}</div>
64+
<ul>
65+
{todoList.map((todo) => (
66+
<li>{todo.title}</li>
67+
))}
68+
</ul>
69+
</div>
70+
)
71+
72+
export async function getInitialProps() {
73+
const profileResponse = await fetch("https://.../profile");
74+
const todoListResponse = await fetch("https://.../todolist");
75+
76+
return {
77+
props: {
78+
profile: await profileResponse.json(),
79+
todoList: await todoListResponse.json()
80+
}
81+
};
82+
}
83+
84+
export default Page;
5585

56-
export const App = (): JSX.Element => <BrowserRouter>
86+
// pages/index.tsx
87+
const TodoList = lazyPage(() => import("./TodoList"));
88+
89+
const App = () => (
5790
<Suspense fallback={<div>Loading...</div>}>
58-
<Switch>
59-
<Route path={'/about'} component={lazyPage(() => import('./pages/About.page'))} exact/>
60-
<Route path={'/blog'} component={lazyPage(() => import('./pages/Blog.page'))} exact/>
61-
</Switch>
91+
<TodoList />
6292
</Suspense>
63-
</BrowserRouter>;
93+
)
6494
```
6595

66-
## TODO
67-
68-
- [ ] Write **documentation**
69-
- [ ] **Publish** a package
70-
7196
## License
7297

7398
[**MIT License**](LICENSE)

index.spec.tsx

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { PureComponent, ReactNode, Suspense as ReactSuspense } from 'react';
1+
import {
2+
PureComponent,
3+
ReactNode,
4+
Suspense as ReactSuspense,
5+
} from 'react';
26
import { render, screen } from '@testing-library/react';
37
import { MemoryRouter, Route } from 'react-router';
48
import { lazyPage } from './index';
@@ -12,23 +16,33 @@ class Wrapper extends PureComponent<{ children: ReactNode }> {
1216

1317
render(): ReactNode {
1418
if (this.state.error) {
15-
return <div data-testid={'ErrorComponent'}>{this.state.error.message}</div>;
19+
return (
20+
<div data-testid={'ErrorComponent'}>{this.state.error.message}</div>
21+
);
1622
}
1723

18-
return <ReactSuspense fallback={<div data-testid={'LoadingComponent'}>Loading...</div>}>
19-
{this.props.children}
20-
</ReactSuspense>;
24+
return (
25+
<ReactSuspense
26+
fallback={<div data-testid={'LoadingComponent'}>Loading...</div>}
27+
>
28+
{this.props.children}
29+
</ReactSuspense>
30+
);
2131
}
2232
}
2333

24-
const ContentComponent = ({ message = 'DefaultMessage' }: { message: string }): JSX.Element =>
25-
<div data-testid={'ContentComponent'}>{message}</div>;
34+
const ContentComponent = ({
35+
message = 'DefaultMessage',
36+
}: {
37+
message: string;
38+
}): JSX.Element => <div data-testid={'ContentComponent'}>{message}</div>;
2639

2740
describe('lazyPage', () => {
2841
let spy: jest.SpyInstance;
2942

3043
beforeAll(() => {
31-
spy = jest.spyOn(console, 'error').mockImplementation(() => { /* */
44+
spy = jest.spyOn(console, 'error').mockImplementation(() => {
45+
/* */
3246
});
3347
});
3448

@@ -37,9 +51,16 @@ describe('lazyPage', () => {
3751
});
3852

3953
it('Should render the "ContentComponent" component with default message', async () => {
40-
const Component = lazyPage(() => Promise.resolve({ default: ContentComponent }));
54+
const Component = lazyPage(() =>
55+
Promise.resolve({ default: ContentComponent }),
56+
);
4157

42-
render(<Wrapper><Component/></Wrapper>, { wrapper: MemoryRouter });
58+
render(
59+
<Wrapper>
60+
<Component />
61+
</Wrapper>,
62+
{ wrapper: MemoryRouter },
63+
);
4364

4465
expect(screen.queryByTestId('LoadingComponent')).toBeInTheDocument();
4566

@@ -50,13 +71,19 @@ describe('lazyPage', () => {
5071
});
5172

5273
it('Should render the "ContentComponent" component with custom message', async () => {
53-
const Component = lazyPage(() => Promise.resolve({
54-
default: ContentComponent,
55-
getInitialProps: async () => ({ props: { message: 'TestMessage' } })
56-
}));
57-
74+
const Component = lazyPage(() =>
75+
Promise.resolve({
76+
default: ContentComponent,
77+
getInitialProps: async () => ({ props: { message: 'TestMessage' } }),
78+
}),
79+
);
5880

59-
render(<Wrapper><Component/></Wrapper>, { wrapper: MemoryRouter });
81+
render(
82+
<Wrapper>
83+
<Component />
84+
</Wrapper>,
85+
{ wrapper: MemoryRouter },
86+
);
6087

6188
expect(screen.queryByTestId('LoadingComponent')).toBeInTheDocument();
6289

@@ -67,15 +94,21 @@ describe('lazyPage', () => {
6794
});
6895

6996
test('Should render the "ErrorComponent" component with Error message', async () => {
70-
const Component = lazyPage(() => Promise.resolve({
71-
default: ContentComponent,
72-
getInitialProps: () => {
73-
throw Error('TestError');
74-
}
75-
}));
76-
97+
const Component = lazyPage(() =>
98+
Promise.resolve({
99+
default: ContentComponent,
100+
getInitialProps: () => {
101+
throw Error('TestError');
102+
},
103+
}),
104+
);
77105

78-
render(<Wrapper><Component/></Wrapper>, { wrapper: MemoryRouter });
106+
render(
107+
<Wrapper>
108+
<Component />
109+
</Wrapper>,
110+
{ wrapper: MemoryRouter },
111+
);
79112

80113
expect(screen.queryByTestId('LoadingComponent')).toBeInTheDocument();
81114

@@ -86,18 +119,24 @@ describe('lazyPage', () => {
86119
});
87120

88121
test('Should cause a redirect to the "/test" page', async () => {
89-
const Component = lazyPage(() => Promise.resolve({
90-
default: ContentComponent,
91-
getInitialProps: async () => ({ redirect: '/test' })
92-
}));
93-
122+
const Component = lazyPage(() =>
123+
Promise.resolve({
124+
default: ContentComponent,
125+
getInitialProps: async () => ({ redirect: '/test' }),
126+
}),
127+
);
94128

95129
render(
96130
<Wrapper>
97-
<Route path={'/'} exact component={Component}/>);
98-
<Route path={'/test'} exact render={() => <ContentComponent message={'RedirectMessage'}/>}/>
131+
<Route path={'/'} exact component={Component} />
132+
);
133+
<Route
134+
path={'/test'}
135+
exact
136+
render={() => <ContentComponent message={'RedirectMessage'} />}
137+
/>
99138
</Wrapper>,
100-
{ wrapper: MemoryRouter }
139+
{ wrapper: MemoryRouter },
101140
);
102141

103142
expect(screen.queryByTestId('LoadingComponent')).toBeInTheDocument();

0 commit comments

Comments
 (0)