Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/apiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
},
};
4 changes: 2 additions & 2 deletions src/api/apiService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios, { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios";
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
class ApiService {
async callApi(config: AxiosRequestConfig): Promise<AxiosPromise<AxiosResponse>> {
async callApi<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return axios.request(config);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/App/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -22,6 +23,7 @@ describe("App", () => {
userProfile: {
username: "Steve",
},
users: { list: null },
});
const app = container.querySelector(".app");
expect(app).toBeInTheDocument();
Expand Down
4 changes: 3 additions & 1 deletion src/components/PageContainer/PageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ interface PageContainerProps {
}

const PageContainer: FC<PageContainerProps> = ({ children }) => (
<div className="container my-10 mx-auto">{children}</div>
<div className="px-8">
<div className="container my-10 mx-auto">{children}</div>
</div>
);

export default PageContainer;
41 changes: 30 additions & 11 deletions src/components/UsersTable/UsersTable.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<table>
<thead>
<tr>
<th>asdfasd</th>
<th>asdf</th>
<th>asdf</th>
<th>asdf</th>
<th>Name</th>
<th>Username</th>
<th>Email</th>
<th>Company</th>
</tr>
</thead>
<tbody>
<tr>
<td>a</td>
<td>a</td>
<td>a</td>
<td>a</td>
</tr>
{usersList.map(({ name, email, company, username }, i) => (
<tr key={i}>
<td>{name}</td>
<td>{email}</td>
<td>{company.name}</td>
<td>{username}</td>
</tr>
))}
</tbody>
</table>
);
Expand Down
14 changes: 12 additions & 2 deletions src/pages/LandingPage/LandingPage.test.tsx
Original file line number Diff line number Diff line change
@@ -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: <LandingPage />,
initialState,
});

describe("LandingPage", () => {
it("renders", () => {
render(<LandingPage />);
setup({
userProfile: {},
users: { list: null },
});

const heading = screen.getByText("Landing page");
expect(heading).toBeInTheDocument();
Expand Down
3 changes: 2 additions & 1 deletion src/state/rootReducer.ts
Original file line number Diff line number Diff line change
@@ -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<typeof rootReducer>;
export default rootReducer;
11 changes: 11 additions & 0 deletions src/state/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface User {
id?: string;
name?: string;
email?: string;
username?: string;
company?: {
name: string;
};
}

export type Users = User[];
4 changes: 3 additions & 1 deletion src/state/userProfile/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<User>({
url: apiConfig.endpoints.userProfile("1"),
method: "GET",
});
Expand Down
8 changes: 2 additions & 6 deletions src/state/userProfile/reducer.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions src/state/userProfile/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe("UserProfile selectors", () => {
userProfile: {
username: "Steve",
},
users: { list: null },
};
expect(getUsername(state)).toBe("Steve");
});
Expand Down
13 changes: 13 additions & 0 deletions src/state/users/actions.ts
Original file line number Diff line number Diff line change
@@ -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<Users>({
url: apiConfig.endpoints.users,
method: "GET",
});
return response.data;
});
21 changes: 21 additions & 0 deletions src/state/users/reducer.ts
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions src/state/users/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { RootState } from "../rootReducer";
import { User } from "../types";

export const getUsersList = (state: RootState): User[] => state.users.list;