Skip to content
Open
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
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

# dependencies
/node_modules

# testing
/coverage

# build
/build

# envs
/.env

# Misc
.DS_Store

# Editors and IDEs
.idea
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# Logs
npm-debug.log*
npm-error.log*
yarn-debug.log*
yarn-error.log*

# awesome-typescript-loader + babel-loader cache
.awcache


# videoshots
videoshot-with-error/

.eslintinfo
.stylelintinfo
.todo
todo
183 changes: 183 additions & 0 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useCallback, useState } from "react";

import { Api } from "../utils/Api";

/*
Легенда:
Необходимо отобразить список ключей (произвольные строки), запрашиваемый с бэкенда.
При нажатии кнопки "Сгенерировать ключ" необходимо сгенерировать новый ключ (апи запрос) и отобразить его в списке.
Каждый ключ можно использовать. В таком случае он должен изменить свое визуальное отображение и увеличить счетчик использованных ключей.
Каждый ключ можно удалить. При удалении использованного ключа, счетчик использованных ключей уменьшается (счетчику важны только неудаленные использованные ключи)

Для поддержания актуального списка ключей необходимо обновлять список каждые 30 секунд. При получении от апи новых ключей считать их неиспользованными
*/

export function App() {
const [keys, setKeys] = useState(null);
const [isKeysRequested, setIsKeysRequested] = useState(false);

const [isLoading, setLoadingState] = useState(false);
const [countUsedKeys, setCountUsedKeys] = useState(0);

const toggleLoading = useCallback(() => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Удалить toggleLoading

setLoadingState(!isLoading);
}, [isLoading]);

const incrementUsedKeys = () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

добавить useCallback

setCountUsedKeys((prevValue) => prevValue++);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++prevValue

}

const decrementUsedKeys = useCallback(() => {
setCountUsedKeys((prevValue) => prevValue--);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--prevValue

}, []);

const addKey = useCallback(async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

убрать useCallback

toggleLoading();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLoadingState(true)


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

необходимо добавить обработку ошибок try/catch/finally
в finally хотим опустить флаг загрузки (setLoadingState(false))

const key = await Api.generateKey();
setKeys((prevKeys) => prevKeys.push(key));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

убрать пуш, и использовать [...prevKeys, key]


toggleLoading();
}, [toggleLoading]);

const removeKey = useCallback((value) => {
setKeys((prevKeys) => prevKeys.filter((key) => key !== value));
}, []);

if (!isKeysRequested) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. вынести в useEffect
  2. добавить return с clearInterval
    3)* Дублирование кода из-за отсутсвия нулевого вызова колбэка в интервале (рассмотреть свою имплеменатцию интервала)

setIsKeysRequested(true);
Api.loadKeys().then((response) => {
setKeys(response);
setInterval(() => {
Api.loadKeys().then((response) => {
setKeys(response);
})
}, 30000);
}).catch(e => {
setIsKeysRequested(false);
})
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вынести keys?.length в отдельную переменную, чтобы не дублировать проверку и вызов !!

return (
<main>
<div>
<h3>Всего ключей: {keys.length}</h3>
<h3>Использовано текущих ключей: {countUsedKeys}</h3>
</div>

{!keys.length && <div>Список ключей пуст</div>}

{keys.length && (
<div className="keys">
{keys.map((key) => (
<Key

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key={key}

value={key}
removeKey={removeKey(key)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeKey={removeKey}

decrementUsedKeys={decrementUsedKeys}
incrementUsedKeys={incrementUsedKeys}
/>
))}
</div>
)}

<div>
<Button
onClick={addKey}
isLoading={isLoading}
label="Сгенерировать ключ"
/>
</div>
</main>
);
};

export function Button({
size,
label,
color,
onClick,
disabled,
isLoading,
}) {
const className = useMemo(
() => cn([size && `size_${size}`, color && `color_${color}`]),
[color, size]
);

return (
<button
onClick={onClick}
className={className}
disabled={disabled || isLoading}
>
{isLoading ? "Загрузка..." : label}
</button>
);
};

export function Key({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

добавить memo

value,
removeKey,
incrementUsedKeys,
decrementUsedKeys,
}) {
const [isUsed, setUsedState] = useState(false);
const [isLoading, setLoadingState] = useState(false);

const toggleLoading = useCallback(() => {
setLoadingState(!isLoading);
}, [isLoading]);

const applyKey = useCallback(
async (value) => {
if (isUsed) return;

toggleLoading();
await Api.addUsedKey(value);
toggleLoading();

setUsedState(true);
incrementUsedKeys();
},
[incrementUsedKeys, isUsed, toggleLoading]
);

useEffect(async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

убрать async

return async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подход удаления на сервер результатом демонтирования технически допустим, но является антипаттерном, поэтому всю логику удаления ключа необхидмо описать в одном месте. Убрать useEffect

if (!isUsed) return;

decrementUsedKeys();
await Api.removeUsedKey(value);
};
}, [decrementUsedKeys, isUsed, value]);

const valueClassNames = useMemo(
() => cn(["key__value", isUsed && "key__value_used"]),
[isUsed]
);

const buttonLabel = isUsed ? "Использован" : "Использовать";

return (
<div className="key">
<div className={valueClassNames}>{value}</div>

<div className="key__buttons">
<Button
size="s"
disabled={isUsed}
label={buttonLabel}
isLoading={isLoading}
onClick={() => applyKey(value)}
/>

<Button
size="s"
color="danger"
label="Удалить"
onClick={removeKey}
/>
</div>
</div>
);
};