Skip to content

Commit e8caa63

Browse files
add user list
1 parent e41d2ca commit e8caa63

17 files changed

+206
-129
lines changed

.idea/prettier.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

-44
Original file line numberDiff line numberDiff line change
@@ -1,44 +0,0 @@
1-
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2-
3-
## Available Scripts
4-
5-
In the project directory, you can run:
6-
7-
### `yarn start`
8-
9-
Runs the app in the development mode.<br />
10-
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11-
12-
The page will reload if you make edits.<br />
13-
You will also see any lint errors in the console.
14-
15-
### `yarn test`
16-
17-
Launches the test runner in the interactive watch mode.<br />
18-
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19-
20-
### `yarn build`
21-
22-
Builds the app for production to the `build` folder.<br />
23-
It correctly bundles React in production mode and optimizes the build for the best performance.
24-
25-
The build is minified and the filenames include the hashes.<br />
26-
Your app is ready to be deployed!
27-
28-
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29-
30-
### `yarn eject`
31-
32-
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33-
34-
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35-
36-
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37-
38-
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39-
40-
## Learn More
41-
42-
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43-
44-
To learn React, check out the [React documentation](https://reactjs.org/).

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
"@types/react": "^16.9.0",
1212
"@types/react-dom": "^16.9.0",
1313
"@types/react-redux": "^7.1.9",
14+
"@types/react-router-dom": "^5.1.5",
1415
"connected-react-router": "^6.8.0",
15-
"history": "^5.0.0",
16+
"history": "4.10.1",
1617
"react": "^16.13.1",
1718
"react-dom": "^16.13.1",
1819
"react-redux": "^7.2.1",
1920
"react-router": "^5.2.0",
21+
"react-router-dom": "^5.2.0",
2022
"react-scripts": "3.4.2",
2123
"redux": "^4.0.5",
2224
"redux-devtools-extension": "^2.13.8",

src/App.tsx

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import * as React from 'react';
2-
import { Provider } from 'react-redux';
3-
import { ConnectedRouter } from 'connected-react-router';
4-
import store from './redux/store';
5-
import history from './utils/history';
2+
import UserList from 'components/UserList';
3+
import { Route, NavLink, Switch } from 'react-router-dom';
4+
import Home from 'components/routes/Home';
5+
import Users from 'components/routes/Users';
66

77
const App: React.FC = () => (
8-
<Provider store={store}>
9-
<ConnectedRouter history={history}>Hello</ConnectedRouter>
10-
</Provider>
8+
<div>
9+
<div>
10+
<NavLink to="/home" activeStyle={{ color: 'red' }}>
11+
Home
12+
</NavLink>
13+
<NavLink to="/users" activeStyle={{ color: 'red' }}>
14+
Users
15+
</NavLink>
16+
</div>
17+
<div>
18+
<Switch>
19+
<Route path="/home" component={Home} />
20+
<Route path="/users" component={Users} />
21+
</Switch>
22+
</div>
23+
<UserList />
24+
</div>
1125
);
1226

1327
export default App;

src/components/UserList/UserList.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
3+
import { userListSelector, fetchUserList } from 'modules/users';
4+
5+
const UserList: React.FC = () => {
6+
const userList = useSelector(userListSelector, shallowEqual);
7+
const dispatch = useDispatch();
8+
9+
const handleClick = () => dispatch(fetchUserList());
10+
11+
return (
12+
<div>
13+
<button type="button" onClick={handleClick}>
14+
GET USER LIST
15+
</button>
16+
17+
<ul>
18+
{userList.map((user) => (
19+
<li>{user.name}</li>
20+
))}
21+
</ul>
22+
</div>
23+
);
24+
};
25+
26+
export default UserList;

src/components/UserList/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import UserList from './UserList';
2+
3+
export default UserList;

src/components/routes/Home.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as React from 'react';
2+
3+
const Home: React.FC = () => {
4+
return <h1>Home</h1>;
5+
};
6+
7+
export default Home;

src/components/routes/Users.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as React from 'react';
2+
3+
const Users: React.FC = () => {
4+
return <h1>Users</h1>;
5+
};
6+
7+
export default Users;

src/index.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import React from 'react';
2+
import { Provider, ReactReduxContext } from 'react-redux';
3+
import { ConnectedRouter } from 'connected-react-router';
4+
import configureStore, { history } from 'redux/configureStore';
25
import ReactDOM from 'react-dom';
36
import App from './App';
47
import * as serviceWorker from './serviceWorker';
58

9+
const store = configureStore(/* provide initial state if any */);
10+
611
ReactDOM.render(
712
<React.StrictMode>
8-
<App />
13+
<Provider store={store} context={ReactReduxContext}>
14+
<ConnectedRouter history={history} context={ReactReduxContext}>
15+
<App />
16+
</ConnectedRouter>
17+
</Provider>
918
</React.StrictMode>,
1019
document.getElementById('root'),
1120
);

src/modules/users.ts

+35-39
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,50 @@
11
import { Reducer } from 'redux';
22
import { createSelector } from 'reselect';
33
import { ThunkAction } from 'redux-thunk';
4-
import { RootState } from '../redux/reducer';
4+
import { RootState } from 'redux/createRootReducer';
5+
import http from 'utils/api';
56

67
/**
78
* Constants
89
*/
910
export enum ACTION {
10-
LIST_REQUEST = '@@themes/LIST_REQUEST',
11-
LIST_SUCCESS = '@@themes/LIST_SUCCESS',
12-
LIST_FAILURE = '@@themes/LIST_FAILURE',
11+
USERS_REQUEST = '@@users/USERS_REQUEST',
12+
USERS_SUCCESS = '@@users/USERS_SUCCESS',
13+
USERS_FAILURE = '@@users/USERS_FAILURE',
1314
}
1415

1516
/**
1617
* Reducer
1718
*/
18-
export interface ThemeState {
19-
readonly list: Theme[];
19+
export interface UsersState {
20+
readonly list: User[];
2021
readonly loading: boolean;
2122
readonly error: string;
2223
}
23-
export interface Theme {
24+
25+
export interface User {
2426
id: number;
25-
title: string;
26-
description: string;
27+
name: string;
28+
username: string;
2729
}
28-
const initialState: ThemeState = {
30+
31+
const initialState: UsersState = {
2932
list: [],
3033
loading: false,
3134
error: '',
3235
};
33-
const reducer: Reducer<ThemeState, ActionType> = (
36+
37+
const reducer: Reducer<UsersState, ActionType> = (
3438
state = initialState,
3539
action,
3640
) => {
3741
switch (action.type) {
38-
case ACTION.LIST_REQUEST:
42+
case ACTION.USERS_REQUEST:
3943
return {
4044
...state,
4145
loading: true,
4246
};
43-
case ACTION.LIST_SUCCESS:
47+
case ACTION.USERS_SUCCESS:
4448
return {
4549
...state,
4650
list: action.payload,
@@ -55,48 +59,40 @@ export { reducer as usersReducer };
5559
/**
5660
* Selectors
5761
*/
58-
export const stateSelector = (state: RootState): ThemeState => state.users;
59-
export const themeListSelector = createSelector(
62+
export const stateSelector = (state: RootState): UsersState => state.users;
63+
export const userListSelector = createSelector(
6064
stateSelector,
6165
(state) => state.list,
6266
);
6367

6468
/**
6569
* Action Creators
6670
*/
67-
interface ThemesRequest {
68-
type: typeof ACTION.LIST_REQUEST;
71+
interface UserRequest {
72+
type: typeof ACTION.USERS_REQUEST;
6973
}
70-
interface ThemesSuccess {
71-
type: typeof ACTION.LIST_SUCCESS;
72-
payload: Theme[];
74+
75+
interface UserSuccess {
76+
type: typeof ACTION.USERS_SUCCESS;
77+
payload: User[];
7378
}
74-
type ActionType = ThemesRequest | ThemesSuccess;
7579

76-
type ThunkResult<R> = ThunkAction<R, RootState, void, ActionType>;
80+
type ActionType = UserRequest | UserSuccess;
7781

78-
const fetchPosts = (): ThunkResult<void> => {
79-
return async (dispatch) => {
80-
const posts = await fetch('https://jsonplaceholder.typicode.com/posts');
81-
dispatch({
82-
type: ACTION.LIST_REQUEST,
83-
});
84-
console.log('[POSTS]', posts);
85-
};
86-
};
82+
type ThunkResult<R> = ThunkAction<R, RootState, void, ActionType>;
8783

88-
export const fetchThemeList = (): ThunkResult<void> => {
84+
export const fetchUserList = (): ThunkResult<void> => {
8985
return async (dispatch) => {
90-
dispatch(fetchPosts());
9186
dispatch({
92-
type: ACTION.LIST_REQUEST,
87+
type: ACTION.USERS_REQUEST,
9388
});
94-
const themes = await fetch(
95-
`${process.env.PUBLIC_URL}/mock/themes.json`,
96-
).then((res) => res.json());
89+
const userList = await http<User[]>(
90+
'https://jsonplaceholder.typicode.com/users',
91+
);
92+
console.log('userList', userList);
9793
dispatch({
98-
type: ACTION.LIST_SUCCESS,
99-
payload: themes,
94+
type: ACTION.USERS_SUCCESS,
95+
payload: userList,
10096
});
10197
};
10298
};

src/redux/configureStore.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createBrowserHistory } from 'history';
2+
import { applyMiddleware, createStore, AnyAction, Store } from 'redux';
3+
import { routerMiddleware } from 'connected-react-router';
4+
import { composeWithDevTools } from 'redux-devtools-extension';
5+
import thunk, { ThunkMiddleware } from 'redux-thunk';
6+
import { createRootReducer, RootState } from 'redux/createRootReducer';
7+
8+
export const history = createBrowserHistory();
9+
10+
const configureStore = (preloadedState?: RootState): Store => {
11+
const store = createStore(
12+
createRootReducer(history),
13+
preloadedState,
14+
composeWithDevTools(
15+
applyMiddleware(
16+
routerMiddleware(history),
17+
thunk as ThunkMiddleware<RootState, AnyAction>,
18+
),
19+
),
20+
);
21+
22+
return store;
23+
};
24+
25+
export default configureStore;

src/redux/createRootReducer.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { combineReducers } from 'redux';
2+
import { connectRouter } from 'connected-react-router';
3+
import { History } from 'history';
4+
import { usersReducer } from '../modules/users';
5+
6+
export const createRootReducer = (history: History) => {
7+
return combineReducers({
8+
router: connectRouter(history),
9+
users: usersReducer,
10+
});
11+
};
12+
13+
export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;

src/redux/reducer.ts

-11
This file was deleted.

src/redux/store.ts

-17
This file was deleted.

src/utils/api.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const http = <T>(request: RequestInfo): Promise<T> => {
2+
return new Promise((resolve) => {
3+
fetch(request)
4+
.then((response) => response.json())
5+
.then((body) => {
6+
resolve(body);
7+
});
8+
});
9+
};
10+
11+
export default http;

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "es5",
4+
"baseUrl": "./src",
45
"lib": [
56
"dom",
67
"dom.iterable",

0 commit comments

Comments
 (0)