diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts index 8843386..624a9cf 100644 --- a/src/api/apiConfig.ts +++ b/src/api/apiConfig.ts @@ -3,5 +3,6 @@ const API_BASE_URL = "https://jsonplaceholder.typicode.com"; export default { endpoints: { userProfile: (userId: string): string => `${API_BASE_URL}/users/${userId}`, + users: `${API_BASE_URL}/users`, }, }; diff --git a/src/api/apiService.ts b/src/api/apiService.ts index 553d651..19fef0f 100644 --- a/src/api/apiService.ts +++ b/src/api/apiService.ts @@ -1,6 +1,6 @@ -import axios, { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios"; +import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; class ApiService { - async callApi(config: AxiosRequestConfig): Promise> { + async callApi(config: AxiosRequestConfig): Promise> { return axios.request(config); } } diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx index 215d71f..ea40ae6 100644 --- a/src/components/App/App.test.tsx +++ b/src/components/App/App.test.tsx @@ -13,6 +13,7 @@ describe("App", () => { it("renders Loader when userProfile not loaded", () => { const { container } = setup({ userProfile: {}, + users: { list: null }, }); const app = container.querySelector(".loader"); expect(app).toBeInTheDocument(); @@ -22,6 +23,7 @@ describe("App", () => { userProfile: { username: "Steve", }, + users: { list: null }, }); const app = container.querySelector(".app"); expect(app).toBeInTheDocument(); diff --git a/src/components/PageContainer/PageContainer.tsx b/src/components/PageContainer/PageContainer.tsx index f693cd5..6817aed 100644 --- a/src/components/PageContainer/PageContainer.tsx +++ b/src/components/PageContainer/PageContainer.tsx @@ -5,7 +5,9 @@ interface PageContainerProps { } const PageContainer: FC = ({ children }) => ( -
{children}
+
+
{children}
+
); export default PageContainer; diff --git a/src/components/UsersTable/UsersTable.tsx b/src/components/UsersTable/UsersTable.tsx index 7df4c36..251eedd 100644 --- a/src/components/UsersTable/UsersTable.tsx +++ b/src/components/UsersTable/UsersTable.tsx @@ -1,23 +1,42 @@ -import React, { FC } from "react"; +import React, { FC, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { getUsersList } from "state/users/selectors"; +import { fetchUsers } from "state/users/actions"; const UsersTable: FC = () => { + const dispatch = useDispatch(); + const usersList = useSelector(getUsersList); + + useEffect(() => { + if (!usersList) { + dispatch(fetchUsers()); + } + }, [dispatch]); + + if (!usersList || usersList.length === 0) { + return null; + } + return ( - - - - + + + + - - - - - - + {usersList.map(({ name, email, company, username }, i) => ( + + + + + + + ))}
asdfasdasdfasdfasdfNameUsernameEmailCompany
aaaa
{name}{email}{company.name}{username}
); diff --git a/src/pages/LandingPage/LandingPage.test.tsx b/src/pages/LandingPage/LandingPage.test.tsx index ee05220..773a72a 100644 --- a/src/pages/LandingPage/LandingPage.test.tsx +++ b/src/pages/LandingPage/LandingPage.test.tsx @@ -1,11 +1,21 @@ import React from "react"; -import { screen, render } from "@testing-library/react"; +import { screen } from "@testing-library/react"; +import renderConnected from "utilities/test/renderConnected"; import LandingPage from "./LandingPage"; +const setup = (initialState = {}) => + renderConnected({ + ui: , + initialState, + }); + describe("LandingPage", () => { it("renders", () => { - render(); + setup({ + userProfile: {}, + users: { list: null }, + }); const heading = screen.getByText("Landing page"); expect(heading).toBeInTheDocument(); diff --git a/src/state/rootReducer.ts b/src/state/rootReducer.ts index 4c4f36f..4f04152 100644 --- a/src/state/rootReducer.ts +++ b/src/state/rootReducer.ts @@ -1,8 +1,9 @@ import { combineReducers } from "@reduxjs/toolkit"; import userProfile from "./userProfile/reducer"; +import users from "./users/reducer"; -const rootReducer = combineReducers({ userProfile }); +const rootReducer = combineReducers({ userProfile, users }); export type RootState = ReturnType; export default rootReducer; diff --git a/src/state/types.ts b/src/state/types.ts new file mode 100644 index 0000000..1be73cf --- /dev/null +++ b/src/state/types.ts @@ -0,0 +1,11 @@ +export interface User { + id?: string; + name?: string; + email?: string; + username?: string; + company?: { + name: string; + }; +} + +export type Users = User[]; diff --git a/src/state/userProfile/actions.ts b/src/state/userProfile/actions.ts index 77c141c..573b0e3 100644 --- a/src/state/userProfile/actions.ts +++ b/src/state/userProfile/actions.ts @@ -2,8 +2,10 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import ApiService from "api/apiService"; import apiConfig from "api/apiConfig"; +import { User } from "../types"; + export const fetchUserById = createAsyncThunk("users/fetchById", async () => { - const response = await ApiService.callApi({ + const response = await ApiService.callApi({ url: apiConfig.endpoints.userProfile("1"), method: "GET", }); diff --git a/src/state/userProfile/reducer.ts b/src/state/userProfile/reducer.ts index ec0751f..a086b31 100644 --- a/src/state/userProfile/reducer.ts +++ b/src/state/userProfile/reducer.ts @@ -1,13 +1,9 @@ import { createReducer } from "@reduxjs/toolkit"; +import { User } from "../types"; import { fetchUserById } from "./actions"; -interface UserState { - name?: string; - username?: string; -} - -export const initialState = {} as UserState; +export const initialState = {} as User; const userProfileReducer = createReducer(initialState, (builder) => { builder.addCase(fetchUserById.fulfilled, (state, action) => { diff --git a/src/state/userProfile/selectors.test.ts b/src/state/userProfile/selectors.test.ts index 603a424..142bb36 100644 --- a/src/state/userProfile/selectors.test.ts +++ b/src/state/userProfile/selectors.test.ts @@ -9,6 +9,7 @@ describe("UserProfile selectors", () => { userProfile: { username: "Steve", }, + users: { list: null }, }; expect(getUsername(state)).toBe("Steve"); }); diff --git a/src/state/users/actions.ts b/src/state/users/actions.ts new file mode 100644 index 0000000..03b7b7d --- /dev/null +++ b/src/state/users/actions.ts @@ -0,0 +1,13 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import ApiService from "api/apiService"; +import apiConfig from "api/apiConfig"; + +import { Users } from "../types"; + +export const fetchUsers = createAsyncThunk("users/fetchUsers", async () => { + const response = await ApiService.callApi({ + url: apiConfig.endpoints.users, + method: "GET", + }); + return response.data; +}); diff --git a/src/state/users/reducer.ts b/src/state/users/reducer.ts new file mode 100644 index 0000000..391625d --- /dev/null +++ b/src/state/users/reducer.ts @@ -0,0 +1,21 @@ +import { createReducer } from "@reduxjs/toolkit"; + +import { Users } from "../types"; +import { fetchUsers } from "./actions"; + +interface UsersState { + list: Users | null; +} + +export const initialState = {} as UsersState; + +const usersReducer = createReducer(initialState, (builder) => { + builder.addCase(fetchUsers.fulfilled, (state, action) => { + return { + ...state, + list: action.payload, + }; + }); +}); + +export default usersReducer; diff --git a/src/state/users/selectors.ts b/src/state/users/selectors.ts new file mode 100644 index 0000000..e2a86be --- /dev/null +++ b/src/state/users/selectors.ts @@ -0,0 +1,4 @@ +import { RootState } from "../rootReducer"; +import { User } from "../types"; + +export const getUsersList = (state: RootState): User[] => state.users.list;