Refactor hook names and make their usage more uniform

Also ensure that potential "loading" states and IndexedDB
error states are marked to be handled properly
This commit is contained in:
Thiago Chaves 2022-08-29 21:34:25 +03:00
parent b21ec1b7ac
commit a8f3bbac49
23 changed files with 88 additions and 88 deletions

View File

@ -2,18 +2,22 @@ export interface ExpressionSetCardProps {
name: string;
description: string;
expression_count: number;
expression_count_loading?: boolean;
}
export function ExpressionSetCard({
name,
description,
expression_count,
expression_count_loading,
}: ExpressionSetCardProps) {
return (
<article className="content-card">
<h2 className="content-row text-title margin-small">{name}</h2>
<span className="content-row text-meta margin-small">
{expression_count} expressions(s)
{expression_count_loading
? "loading"
: `${expression_count} expressions(s)`}
</span>
<p className="content-row text-details margin-paragraph">{description}</p>
</article>

View File

@ -1,9 +1,9 @@
export * from "./useExpressionCategories";
export * from "./useAllExpressions";
export * from "./useAllExpressionSets";
export * from "./useCategoriesByExpressionId";
export * from "./useExpressionById";
export * from "./useExpressionFilterQueryIds";
export * from "./useExpressionQueryId";
export * from "./useExpressions";
export * from "./useExpressionSet";
export * from "./useExpressionSetQueryId";
export * from "./useExpressionSets";
export * from "./useExpressionsInSet";
export * from "./useExpressionsByExpressionSetId";
export * from "./useExpressionSetById";
export * from "./useQueryExpressionId";
export * from "./useQueryExpressionIdFilters";
export * from "./useQueryExpressionSetId";

View File

@ -1,6 +1,6 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressionSets() {
export function useAllExpressionSets() {
return useLiveQuery(() => database.expression_sets.toArray());
}

View File

@ -0,0 +1,6 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useAllExpressions() {
return useLiveQuery(() => database.expressions.toArray());
}

View File

@ -1,7 +1,7 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressionCategories(expression_id: number) {
export function useCategoriesByExpressionId(expression_id: number) {
return useLiveQuery(() => {
return database.expression_to_category
.where({ expression_id })

View File

@ -1,9 +1,8 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database, IndexedExpression } from "../model";
import { database } from "../model";
export function useExpressionById(id?: number): IndexedExpression | undefined {
export function useExpressionById(expression_id: number) {
return useLiveQuery(() => {
if (id === undefined) return undefined;
return database.expressions.where({ id }).first();
}, [id]);
return database.expressions.where({ id: expression_id }).first();
}, [expression_id]);
}

View File

@ -1,9 +0,0 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressionSet(id: number) {
return useLiveQuery(
() => database.expression_sets.where({ id }).first(),
[id]
);
}

View File

@ -0,0 +1,9 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressionSetById(expression_set_id: number) {
return useLiveQuery(
() => database.expression_sets.where({ id: expression_set_id }).first(),
[expression_set_id]
);
}

View File

@ -1,9 +0,0 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressions() {
return useLiveQuery(
() => database.expressions.toArray(),
[database.expressions]
);
}

View File

@ -1,7 +1,7 @@
import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model";
export function useExpressionsInSet(expression_set_id: number) {
export function useExpressionsByExpressionSetId(expression_set_id: number) {
return useLiveQuery(() => {
return database.expression_to_expression_set
.where({ expression_set_id })

View File

@ -1,7 +1,7 @@
import { useContext } from "react";
import { AppRouting } from "../model/routing";
export function useExpressionQueryId(): number | undefined {
export function useQueryExpressionId() {
const { route } = useContext(AppRouting);
return route.options?.expression_card_id;
return route.options?.expression_id || 0;
}

View File

@ -1,7 +1,7 @@
import { useContext } from "react";
import { AppRouting } from "../model/routing";
export function useExpressionFilterQueryIds(): number[] {
export function useQueryExpressionIdFilters() {
const { route } = useContext(AppRouting);
return route.options?.expression_id_filters || [];
return route.options?.expression_id_filters || ([] as number[]);
}

View File

@ -1,7 +1,7 @@
import { useContext } from "react";
import { AppRouting } from "../model/routing";
export function useExpressionSetQueryId(): number {
export function useQueryExpressionSetId() {
const { route } = useContext(AppRouting);
return route.options?.expression_set_id || 0;
}

View File

@ -14,7 +14,7 @@ export enum AppPath {
export interface RouteOptions {
// Used in cards view
expression_card_id?: number;
expression_id?: number;
// Used in practice view
expression_set_id?: number;

View File

@ -17,6 +17,8 @@ export function AddExpressionView() {
useState<string | undefined>(undefined);
const [error, setError] = useState<any>(undefined);
// TODO waiting fetch completion elements
return (
<form className="page-with-bottom-navigation">
<section className="padding-small scroll">

View File

@ -1,19 +1,21 @@
import { useContext, useMemo } from "react";
import { ExpressionCard } from "../../components";
import { useExpressions } from "../../hooks";
import { useAllExpressions } from "../../hooks";
import { IndexedExpression } from "../../model";
import { AppPath, AppRouting } from "../../model/routing";
import { ErrorView } from "../ErrorView";
export function ExpressionCardListView() {
const { setRoute } = useContext(AppRouting);
const expressions = useExpressions();
const expressions = useAllExpressions();
const expression_list = useMemo(
() => (expressions || []).concat().sort(sort_function),
[expressions]
);
if (!expressions) return null; // LOADING
// TODO query error view?
if (!expressions.length) {
return <ErrorView message="No expression cards yet" />;
}
@ -27,7 +29,7 @@ export function ExpressionCardListView() {
onClick={() =>
setRoute({
path: AppPath.CardView,
options: { expression_card_id: id },
options: { expression_id: id },
})
}
>

View File

@ -1,12 +1,14 @@
import { ExpressionCard } from "../../components";
import { ExpressionDescription } from "../../components/ExpressionDescription";
import { useExpressionById, useExpressionQueryId } from "../../hooks";
import { ErrorView } from "../ErrorView";
import { useExpressionById, useQueryExpressionId } from "../../hooks";
export function ExpressionCardView() {
const expression_id = useExpressionQueryId();
const expression_id = useQueryExpressionId();
const expression = useExpressionById(expression_id);
if (!expression) return <ErrorView message="No valid card selected" />;
if (expression === undefined) return null; // LOADING
// TODO query error view?
return (
<div className="page-with-padding content-list scroll">
<ExpressionCard

View File

@ -1,12 +1,12 @@
import { useCallback, useContext } from "react";
import {
useExpressionFilterQueryIds,
useExpressionSetQueryId,
useQueryExpressionIdFilters,
useQueryExpressionSetId,
} from "../../hooks";
import { assignExpressionToSet } from "../../model";
import { AppRouting } from "../../model/routing";
// TODO fix promotion algorithm so it uses a destination expression_set_id
// TODO ensure that the destination expression set exists
export interface DemoteExpressionButtonProps {
expression_id: number;
@ -16,8 +16,8 @@ export function DemoteExpressionButton({
expression_id,
}: DemoteExpressionButtonProps) {
const { route, setRoute } = useContext(AppRouting);
const expression_set_id = useExpressionSetQueryId();
const expression_id_filters = useExpressionFilterQueryIds();
const expression_set_id = useQueryExpressionSetId();
const expression_id_filters = useQueryExpressionIdFilters();
const handleClick = useCallback(() => {
if (expression_set_id === 1) {
setRoute({

View File

@ -1,9 +1,9 @@
import { useMemo } from "react";
import {
useExpressionCategories,
useExpressionFilterQueryIds,
useExpressionSetQueryId,
useExpressionsInSet,
useCategoriesByExpressionId,
useQueryExpressionIdFilters,
useQueryExpressionSetId,
useExpressionsByExpressionSetId,
} from "../../hooks";
import { IndexedExpression } from "../../model";
import { sample } from "../../util";
@ -11,18 +11,13 @@ import { ErrorView } from "../ErrorView";
import { ExpressionPracticeCardView } from "./ExpressionPracticeCardView";
export function ExpressionPracticeView() {
const expression_set_id = useExpressionSetQueryId();
const filter_ids = useExpressionFilterQueryIds();
const expressions = useExpressionsInSet(expression_set_id);
const expression_set_id = useQueryExpressionSetId();
const filter_ids = useQueryExpressionIdFilters();
const expressions = useExpressionsByExpressionSetId(expression_set_id);
// Fallback rendering for content not yet fetched
if (!expressions) return null;
if (!filter_ids) return null;
if (!expressions) return null; // LOADING
// TODO query error view?
// Fallback views for expression set content not found
if (!expression_set_id) {
return <ErrorView message="Expression set not found" />;
}
const filtered_expressions = expressions.filter(
(expression) => !filter_ids.includes(expression.id!)
);
@ -45,7 +40,7 @@ function ExpressionPracticeViewImpl({
select_from_expressions: expressions,
}: ExpressionPracticeViewImplProps) {
const expression = useMemo(() => sample(expressions), [expressions]);
const categories = useExpressionCategories(expression.id!);
const categories = useCategoriesByExpressionId(expression.id!);
if (!categories) return null; // Loading
// Delegate internal interaction state to next component so it resets

View File

@ -1,5 +1,5 @@
import { useCallback } from "react";
import { useExpressionSetQueryId } from "../../hooks";
import { useQueryExpressionSetId } from "../../hooks";
import { assignExpressionToSet, database, removeExpression } from "../../model";
export interface PromoteExpressionButtonProps {
@ -12,7 +12,7 @@ export interface PromoteExpressionButtonProps {
export function PromoteExpressionButton({
expression_id,
}: PromoteExpressionButtonProps) {
const expression_set_id = useExpressionSetQueryId();
const expression_set_id = useQueryExpressionSetId();
const handleClick = useCallback(async () => {
const existing_next_level = await database.expression_sets
.where({ id: expression_set_id + 1 })

View File

@ -1,26 +1,21 @@
import { useContext } from "react";
import { ExpressionSetInfo } from "../../components";
import {
useExpressionSet,
useExpressionSetQueryId,
useExpressionsInSet,
useExpressionSetById,
useQueryExpressionSetId,
useExpressionsByExpressionSetId,
} from "../../hooks";
import { IndexedExpressionSet } from "../../model";
import { AppPath, AppRouting } from "../../model/routing";
import { ErrorView } from "../ErrorView";
export function ExpressionSetDetailsView() {
const expression_set_id = useExpressionSetQueryId();
const expression_set = useExpressionSet(expression_set_id);
const expressions = useExpressionsInSet(expression_set_id);
const expression_set_id = useQueryExpressionSetId();
const expression_set = useExpressionSetById(expression_set_id);
const expressions = useExpressionsByExpressionSetId(expression_set_id);
// Fallback for expression set not found
if (!expressions) return null;
if (!expression_set) {
return <ErrorView message="Expression set not found" />;
}
if (!expression_set || !expressions) return null; // LOADING
// TODO query error view?
// Fallback for expression set empty
return (
<ExpressionSetDetailsViewImpl
expression_set={expression_set}

View File

@ -1,6 +1,6 @@
import { useContext } from "react";
import { ExpressionSetCard } from "../../components";
import { useExpressionsInSet } from "../../hooks";
import { useExpressionsByExpressionSetId } from "../../hooks";
import { IndexedExpressionSet } from "../../model";
import { AppPath, AppRouting } from "../../model/routing";
@ -10,7 +10,7 @@ export function ExpressionSetLink({
name,
}: IndexedExpressionSet) {
const { setRoute } = useContext(AppRouting);
const expressions = useExpressionsInSet(id!) || [];
const expressions = useExpressionsByExpressionSetId(id!);
return (
<div
@ -24,7 +24,8 @@ export function ExpressionSetLink({
<ExpressionSetCard
name={name}
description={description}
expression_count={expressions.length}
expression_count={expressions?.length || 0}
expression_count_loading={expressions === undefined}
/>
</div>
);

View File

@ -1,10 +1,13 @@
import { useExpressionSets } from "../../hooks";
import { useAllExpressionSets } from "../../hooks";
import { ErrorView } from "../ErrorView";
import { ExpressionSetLink } from "./ExpressionSetLink";
export function ExpressionSetListView() {
const expression_sets = useExpressionSets();
const expression_sets = useAllExpressionSets();
if (!expression_sets) return null; // LOADING
// TODO query error view?
if (!expression_sets.length) {
return <ErrorView message="No expression sets found" />;
}