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

View File

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

View File

@ -1,6 +1,6 @@
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model"; import { database } from "../model";
export function useExpressionSets() { export function useAllExpressionSets() {
return useLiveQuery(() => database.expression_sets.toArray()); 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 { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model"; import { database } from "../model";
export function useExpressionCategories(expression_id: number) { export function useCategoriesByExpressionId(expression_id: number) {
return useLiveQuery(() => { return useLiveQuery(() => {
return database.expression_to_category return database.expression_to_category
.where({ expression_id }) .where({ expression_id })

View File

@ -1,9 +1,8 @@
import { useLiveQuery } from "dexie-react-hooks"; 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(() => { return useLiveQuery(() => {
if (id === undefined) return undefined; return database.expressions.where({ id: expression_id }).first();
return database.expressions.where({ id }).first(); }, [expression_id]);
}, [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 { useLiveQuery } from "dexie-react-hooks";
import { database } from "../model"; import { database } from "../model";
export function useExpressionsInSet(expression_set_id: number) { export function useExpressionsByExpressionSetId(expression_set_id: number) {
return useLiveQuery(() => { return useLiveQuery(() => {
return database.expression_to_expression_set return database.expression_to_expression_set
.where({ expression_set_id }) .where({ expression_set_id })

View File

@ -1,7 +1,7 @@
import { useContext } from "react"; import { useContext } from "react";
import { AppRouting } from "../model/routing"; import { AppRouting } from "../model/routing";
export function useExpressionQueryId(): number | undefined { export function useQueryExpressionId() {
const { route } = useContext(AppRouting); 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 { useContext } from "react";
import { AppRouting } from "../model/routing"; import { AppRouting } from "../model/routing";
export function useExpressionFilterQueryIds(): number[] { export function useQueryExpressionIdFilters() {
const { route } = useContext(AppRouting); 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 { useContext } from "react";
import { AppRouting } from "../model/routing"; import { AppRouting } from "../model/routing";
export function useExpressionSetQueryId(): number { export function useQueryExpressionSetId() {
const { route } = useContext(AppRouting); const { route } = useContext(AppRouting);
return route.options?.expression_set_id || 0; return route.options?.expression_set_id || 0;
} }

View File

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

View File

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

View File

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

View File

@ -1,12 +1,14 @@
import { ExpressionCard } from "../../components"; import { ExpressionCard } from "../../components";
import { ExpressionDescription } from "../../components/ExpressionDescription"; import { ExpressionDescription } from "../../components/ExpressionDescription";
import { useExpressionById, useExpressionQueryId } from "../../hooks"; import { useExpressionById, useQueryExpressionId } from "../../hooks";
import { ErrorView } from "../ErrorView";
export function ExpressionCardView() { export function ExpressionCardView() {
const expression_id = useExpressionQueryId(); const expression_id = useQueryExpressionId();
const expression = useExpressionById(expression_id); 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 ( return (
<div className="page-with-padding content-list scroll"> <div className="page-with-padding content-list scroll">
<ExpressionCard <ExpressionCard

View File

@ -1,12 +1,12 @@
import { useCallback, useContext } from "react"; import { useCallback, useContext } from "react";
import { import {
useExpressionFilterQueryIds, useQueryExpressionIdFilters,
useExpressionSetQueryId, useQueryExpressionSetId,
} from "../../hooks"; } from "../../hooks";
import { assignExpressionToSet } from "../../model"; import { assignExpressionToSet } from "../../model";
import { AppRouting } from "../../model/routing"; 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 { export interface DemoteExpressionButtonProps {
expression_id: number; expression_id: number;
@ -16,8 +16,8 @@ export function DemoteExpressionButton({
expression_id, expression_id,
}: DemoteExpressionButtonProps) { }: DemoteExpressionButtonProps) {
const { route, setRoute } = useContext(AppRouting); const { route, setRoute } = useContext(AppRouting);
const expression_set_id = useExpressionSetQueryId(); const expression_set_id = useQueryExpressionSetId();
const expression_id_filters = useExpressionFilterQueryIds(); const expression_id_filters = useQueryExpressionIdFilters();
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (expression_set_id === 1) { if (expression_set_id === 1) {
setRoute({ setRoute({

View File

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

View File

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

View File

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

View File

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

View File

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