Skip to content

Commit 182e372

Browse files
committed
feat: add text list example
1 parent 11953cd commit 182e372

File tree

13 files changed

+285
-41
lines changed

13 files changed

+285
-41
lines changed

public/index.html

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="utf-8" />
5-
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6-
<meta name="viewport" content="width=device-width, initial-scale=1" />
7-
<meta name="theme-color" content="#000000" />
8-
<meta
9-
name="description"
10-
content="Web site created using create-react-app"
11-
/>
12-
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13-
<!--
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
<meta name="theme-color" content="#000000" />
9+
<meta name="description" content="Web site created using create-react-app" />
10+
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
11+
<!--
1412
manifest.json provides metadata used when your web app is installed on a
1513
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
1614
-->
17-
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18-
<!--
15+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
16+
<!--
1917
Notice the use of %PUBLIC_URL% in the tags above.
2018
It will be replaced with the URL of the `public` folder during the build.
2119
Only files inside the `public` folder can be referenced from the HTML.
@@ -24,12 +22,13 @@
2422
work correctly both with client-side routing and a non-root public URL.
2523
Learn how to configure a non-root public URL by running `npm run build`.
2624
-->
27-
<title>React App</title>
28-
</head>
29-
<body>
30-
<noscript>You need to enable JavaScript to run this app.</noscript>
31-
<div id="root"></div>
32-
<!--
25+
<title>React virtualized examples</title>
26+
</head>
27+
28+
<body>
29+
<noscript>You need to enable JavaScript to run this app.</noscript>
30+
<div id="root"></div>
31+
<!--
3332
This HTML file is a template.
3433
If you open it directly in the browser, you will see an empty page.
3534
@@ -39,5 +38,6 @@
3938
To begin the development, run `npm start` or `yarn start`.
4039
To create a production bundle, use `npm run build` or `yarn build`.
4140
-->
42-
</body>
43-
</html>
41+
</body>
42+
43+
</html>

src/App.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
2-
import { ChakraProvider, Flex, Container, Menu, MenuButton, MenuList, MenuItem, IconButton } from '@chakra-ui/react';
2+
import { ChakraProvider, Flex, Container } from '@chakra-ui/react';
33

4-
import { HamburgerIcon } from '@chakra-ui/icons';
4+
import Navigation from './components/Navigation';
55
import TextList from './pages/TextList';
66
import ImageList from './pages/ImageList';
7+
import TextListVirtualized from './pages/TextListVirtualized';
8+
import ImageListVirtualized from './pages/ImageListVirtualized';
79

810
const App = () => {
911
return (
1012
<ChakraProvider>
1113
<Flex>
12-
<Container width="700px">
14+
<Container width="700px" padding={`20px 15px`}>
1315
<Router>
14-
<Container>
15-
<Menu>
16-
<MenuButton as={IconButton} aria-label="Options" icon={<HamburgerIcon />} variant="outline" />
17-
<MenuList>
18-
<MenuItem>기본 리스트</MenuItem>
19-
<MenuItem>적용된 리스트</MenuItem>
20-
</MenuList>
21-
</Menu>
22-
</Container>
16+
<Navigation />
2317
<Switch>
2418
<Route exact path="/" component={TextList} />
2519
<Route path="/text-list" component={TextList} />
2620
<Route path="/image-list" component={ImageList} />
21+
<Route path="/text-list-virtualized" component={TextListVirtualized} />
22+
<Route path="/image-list-virtualized" component={ImageListVirtualized} />
2723
<Redirect to="/" />
2824
</Switch>
2925
</Router>

src/components/Navigation/index.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NavLink } from 'react-router-dom';
2+
import { Container, Heading, SimpleGrid } from '@chakra-ui/react';
3+
4+
const Naviagation = () => {
5+
return (
6+
<Container padding={`0 0 20px 0`} marginBottom={5} borderBottom={'solid 1px #bbb'}>
7+
<Heading mb={5} textAlign="center">
8+
React virtualized examples
9+
</Heading>
10+
<SimpleGrid className="navigation" columns={2} spacingX="5px" spacingY="20px">
11+
<NavLink to="/text-list" activeClassName="selected">
12+
텍스트 목록
13+
</NavLink>
14+
<NavLink to="/text-list-virtualized" activeClassName="selected">
15+
텍스트 목록 (with virtualized)
16+
</NavLink>
17+
<NavLink to="/image-list" activeClassName="selected">
18+
이미지 목록
19+
</NavLink>
20+
<NavLink to="/image-list-virtualized" activeClassName="selected">
21+
이미지 목록 (with virtualized)
22+
</NavLink>
23+
</SimpleGrid>
24+
</Container>
25+
);
26+
};
27+
28+
export default Naviagation;

src/index.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
import React from 'react';
21
import ReactDOM from 'react-dom';
32
import App from './App';
4-
53
import './styles/index.scss';
64

7-
ReactDOM.render(
8-
<React.StrictMode>
9-
<App />
10-
</React.StrictMode>,
11-
document.getElementById('root')
12-
);
5+
ReactDOM.render(<App />, document.getElementById('root'));

src/pages/ImageList/index.scss

Whitespace-only changes.

src/pages/ImageList/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
const ImageList = () => {
4+
return <div>ImageList</div>;
5+
};
6+
7+
export default ImageList;

src/pages/ImageListVirtualized/index.scss

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
const ImageListVirtualized = () => {
4+
return <div>ImageListVirtualized</div>;
5+
};
6+
7+
export default ImageListVirtualized;

src/pages/TextList/index.scss

Whitespace-only changes.

src/pages/TextList/index.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { useEffect, useState, useRef, useCallback } from 'react';
2+
import { WindowScroller, CellMeasurer, CellMeasurerCache, AutoSizer, List, ListRowProps } from 'react-virtualized';
3+
import { Container, Heading, Button, Text } from '@chakra-ui/react';
4+
5+
interface Post {
6+
id: number;
7+
userId: number;
8+
title: string;
9+
body: string;
10+
}
11+
12+
const cache = new CellMeasurerCache({
13+
defaultWidth: 100,
14+
fixedWidth: true,
15+
});
16+
17+
const TextList = () => {
18+
const [posts, setPosts] = useState<Post[]>([]);
19+
const listRef = useRef<List>(null);
20+
21+
const rowRenderer = ({ index, key, parent, style }: ListRowProps) => {
22+
return (
23+
<CellMeasurer cache={cache} parent={parent} key={key} columnIndex={0} rowIndex={index}>
24+
<div style={style}>
25+
<div
26+
style={{
27+
padding: 10,
28+
marginBottom: 10,
29+
color: 'white',
30+
backgroundColor: '#282c34',
31+
}}
32+
>
33+
<div>index: {index}</div>
34+
<div>title: {posts[index].title}</div>
35+
<div>body: {posts[index].body}</div>
36+
</div>
37+
</div>
38+
</CellMeasurer>
39+
);
40+
};
41+
42+
const addPosts = useCallback(() => {
43+
fetch('https://jsonplaceholder.typicode.com/posts').then((response) => {
44+
const data = response.json();
45+
46+
data.then((newPosts) => {
47+
setPosts([...posts, ...newPosts]);
48+
});
49+
});
50+
}, [posts, setPosts]);
51+
52+
useEffect(() => {
53+
addPosts();
54+
}, []);
55+
56+
return (
57+
<>
58+
<Container padding={0} marginBottom={5} textAlign="center">
59+
<Heading size="md" mb={5} textAlign="center">
60+
react-virtualized (X)
61+
</Heading>
62+
<Button mb={5} colorScheme="blue" onClick={addPosts}>
63+
목록 추가하기
64+
</Button>
65+
<Text fontSize="20px" color="tomato">
66+
현재목록: {posts.length}
67+
</Text>
68+
</Container>
69+
<WindowScroller>
70+
{({ height, scrollTop, isScrolling, onChildScroll }) => (
71+
<AutoSizer disableHeight>
72+
{({ width }) => (
73+
<List
74+
ref={listRef}
75+
autoHeight
76+
height={height}
77+
width={width}
78+
isScrolling={isScrolling}
79+
overscanRowCount={0}
80+
onScroll={onChildScroll}
81+
scrollTop={scrollTop}
82+
rowCount={posts.length}
83+
rowHeight={cache.rowHeight}
84+
rowRenderer={rowRenderer}
85+
deferredMeasurementCache={cache}
86+
/>
87+
)}
88+
</AutoSizer>
89+
)}
90+
</WindowScroller>
91+
</>
92+
);
93+
};
94+
95+
export default TextList;

src/pages/TextListVirtualized/index.scss

Whitespace-only changes.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useEffect, useState, useRef, useCallback } from 'react';
2+
import { WindowScroller, CellMeasurer, CellMeasurerCache, AutoSizer, List, ListRowProps } from 'react-virtualized';
3+
import { Container, Heading, Button, Text, Stack, Skeleton } from '@chakra-ui/react';
4+
5+
interface Post {
6+
id: number;
7+
userId: number;
8+
title: string;
9+
body: string;
10+
}
11+
12+
const cache = new CellMeasurerCache({
13+
defaultWidth: 100,
14+
fixedWidth: true,
15+
});
16+
17+
const TextListVirtualized = () => {
18+
const [posts, setPosts] = useState<Post[]>([]);
19+
const listRef = useRef<List>(null);
20+
21+
const rowRenderer = ({ index, key, parent, style }: ListRowProps) => {
22+
return (
23+
<CellMeasurer cache={cache} parent={parent} key={key} columnIndex={0} rowIndex={index}>
24+
<div style={style}>
25+
<div
26+
style={{
27+
padding: 10,
28+
marginBottom: 10,
29+
color: 'white',
30+
backgroundColor: '#282c34',
31+
}}
32+
>
33+
<div>index: {index}</div>
34+
<div>title: {posts[index].title}</div>
35+
<div>body: {posts[index].body}</div>
36+
</div>
37+
</div>
38+
</CellMeasurer>
39+
);
40+
};
41+
42+
const addPosts = useCallback(() => {
43+
fetch('https://jsonplaceholder.typicode.com/posts').then((response) => {
44+
const data = response.json();
45+
46+
data.then((newPosts) => {
47+
setPosts([...posts, ...newPosts]);
48+
});
49+
});
50+
}, [posts, setPosts]);
51+
52+
useEffect(() => {
53+
addPosts();
54+
}, []);
55+
56+
return (
57+
<>
58+
<Container padding={0} marginBottom={5} textAlign="center">
59+
<Heading size="md" mb={5} textAlign="center">
60+
react-virtualized (O)
61+
</Heading>
62+
<Button mb={5} colorScheme="blue" onClick={addPosts}>
63+
목록 추가하기
64+
</Button>
65+
<Text fontSize="20px" color="tomato">
66+
현재목록: {posts.length}
67+
</Text>
68+
</Container>
69+
{posts.length ? (
70+
<WindowScroller>
71+
{({ height, scrollTop, isScrolling, onChildScroll }) => (
72+
<AutoSizer disableHeight>
73+
{({ width }) => (
74+
<List
75+
ref={listRef}
76+
autoHeight
77+
height={height}
78+
width={width}
79+
isScrolling={isScrolling}
80+
overscanRowCount={0}
81+
onScroll={onChildScroll}
82+
scrollTop={scrollTop}
83+
rowCount={posts.length}
84+
rowHeight={cache.rowHeight}
85+
rowRenderer={rowRenderer}
86+
deferredMeasurementCache={cache}
87+
/>
88+
)}
89+
</AutoSizer>
90+
)}
91+
</WindowScroller>
92+
) : (
93+
<Stack>
94+
<Skeleton height="150px" />
95+
<Skeleton height="150px" />
96+
<Skeleton height="150px" />
97+
<Skeleton height="150px" />
98+
<Skeleton height="150px" />
99+
</Stack>
100+
)}
101+
</>
102+
);
103+
};
104+
105+
export default TextListVirtualized;

src/styles/modules/base.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,16 @@ body {
1010
color: $black;
1111
background: $bg-color;
1212
}
13+
14+
.navigation {
15+
a {
16+
padding: 10px;
17+
background-color: #bbb;
18+
text-align: center;
19+
20+
&.selected {
21+
background-color: #61dafb;
22+
font-weight: bold;
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)