Skip to content

Commit

Permalink
feat(query): used useQuery in demo app
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim-mhn committed Oct 17, 2024
1 parent 35596ae commit 6cb26a2
Show file tree
Hide file tree
Showing 23 changed files with 201 additions and 94 deletions.
21 changes: 21 additions & 0 deletions packages/core/src/common/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,24 @@ class Logger {
}

export const logger = new Logger();

export const logMethod =
(prefix: string): MethodDecorator =>
(
target: object,
methodName: string | symbol,
descriptor: PropertyDescriptor
) => {
const targetMethod = descriptor.value;

descriptor.value = function (...args: never[]) {
console.group(`${prefix}.${methodName.toString()}`);

const res = targetMethod.apply(this, args);

console.groupEnd();
return res;
};

return descriptor;
};
20 changes: 15 additions & 5 deletions packages/core/src/component/Show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
renderChildren,
} from '../render-utils/render-children';
import { logger } from '../common';
import { logMethod } from '../common/logger';

function buildPlaceholderComment() {
const commentText = `placeholder--${crypto.randomUUID()}`;
Expand All @@ -26,26 +27,35 @@ class ConditionallyRenderedComponent implements VComponent {
) {}

readonly __type = 'V_COMPONENT';

private readonly __subtype = 'Show';

private _html!: MaybeArray<HTML>;
get html() {
return this._html;
}

renderOnce() {
if (toValue(this.condition)) {
this.fallback?.destroy?.();
this._html = renderChildren(this.children);
return this._html;
}

destroyChildren(this.children);
try {
destroyChildren(this.children);
} catch (err) {
console.error(err);
}

if (!this.fallback) {
this._html = buildPlaceholderComment();

return this._html;
}


this._html = this.fallback.renderOnce();

return this._html;
}

Expand All @@ -66,20 +76,21 @@ class ConditionallyRenderedComponent implements VComponent {
init(parent: WithHtml) {
this.parent = parent;
this._initChild(parent);

if (!isReactive(this.condition)) return;

const sub = this.condition.valueChanges$.subscribe(() => {
const oldNodes = this._html;

this._initChild(parent);

const newNodes = this.renderOnce();

removeOldNodesAndRenderNewNodes({
oldNodes,
newNodes,
parent,
});

this._initChild(parent);
});

this.subs.add(sub);
Expand Down Expand Up @@ -109,4 +120,3 @@ export const Show = ({
}) => {
return new ConditionallyRenderedComponent(when, children, fallback);
};

2 changes: 2 additions & 0 deletions packages/core/src/component/for-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class ForLoopComponent<T> implements VComponent {
) {}

readonly __type = 'V_COMPONENT';
readonly __subtype = 'For';

private _html!: HTML[];
get html() {
return this._html;
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/component/render-new-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ function safelyInsertNode(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newNode: any
) {
if (!newNode)
throw new Error(
'[TINAF] Error while adding new node. newNode is undefined',
{ cause: { parent } }
);
try {
parent.html.insertBefore(
newNode,
Expand Down
15 changes: 14 additions & 1 deletion packages/core/src/component/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import type { VComponent, WithHtml } from './component';
import { Subscription, distinctUntilChanged, skip, startWith } from 'rxjs';
import { removeOldNodesAndRenderNewNodes } from './render-new-nodes';
import type { AddClassesArgs } from '../dom/create-dom-element';
import { logMethod } from '../common/logger';

function buildPlaceholderComment() {
const commentText = `placeholder--${crypto.randomUUID()}`;
const comment = document.createComment(commentText);
return comment;
}

class SwitchComponent<T> implements VComponent {
constructor(
Expand All @@ -22,8 +29,14 @@ class SwitchComponent<T> implements VComponent {
}

private _currentComponent: VComponent | null = null;

@logMethod('<Switch />')
renderOnce() {
const newHtml = this._currentComponent?.renderOnce?.() || [];
const newHtml =
this._currentComponent?.renderOnce?.() ||
[
// buildPlaceholderComment(),
];
this._html = newHtml;
return this._html;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/component/v-component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { logMethod } from '../common/logger';
import { mergeClasses } from '../dom/classes';
import type {
AddClassesArgs,
Expand Down Expand Up @@ -77,6 +78,8 @@ export class SimpleVComponent<Props extends ComponentProps = NoProps>
private get children() {
return toArray(this.child);
}

@logMethod('VComponent')
renderOnce(): MaybeArray<HTMLElement | Comment> {
const newHtml = this.children.map((child) => {
if (isVComponent(child)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/dom/create-dom-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class VDomComponent<T extends TagName> implements VComponent {
renderOnce(): HTMLElementTagNameMap[T] {
this._html = this._doc.createElement(this.type);

this.html.setAttribute('x-id', crypto.randomUUID());
// this.html.setAttribute('x-id', crypto.randomUUID());
this.children?.forEach((child) => {
if (isVComponent(child)) {
// NOTE: this function breaks state when rerendering a div parent with children with inner state !!
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/dom/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class VInputComponent<T extends string | number> implements VComponent {
private _renderOnce(): HTMLInputElement {
const input = document.createElement('input');

input.setAttribute('x-id', crypto.randomUUID());
// input.setAttribute('x-id', crypto.randomUUID());

objectKeys(this.options).forEach((key) => {
const value = this.options[key];
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/http/use-query.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('useQuery', () => {
},
);

// @ts-expect-error _data
data = _data;
error = _error;

Expand Down
36 changes: 25 additions & 11 deletions packages/core/src/http/use-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,47 @@ async function toPromise<T>(value: T | Promise<T>) {
return value;
}

type QueryState<T> = {
data: Reactive<T | undefined>;
type QueryState<T, InitialValue extends T | undefined = undefined> = {
data: Reactive<T | InitialValue>;
error: Reactive<unknown | undefined>;
isPending: Reactive<boolean>;
isFetching: boolean;
};

type QueryClientPrepareResponse<
T,
InitialValue extends T | undefined
> = undefined extends InitialValue
? QueryState<T, undefined>
: QueryState<T, InitialValue>;
export class QueryClient {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private keyToState = new Map<string, QueryState<any>>();

private _id = crypto.randomUUID();

prepare<T>(queryKey: string, initialValue?: T) {
const state = this.keyToState.get(queryKey) as QueryState<T> | undefined;
prepare<T, InitialValue extends T | undefined>(
queryKey: string,
initialValue?: InitialValue
): QueryClientPrepareResponse<T, InitialValue> {
const state = this.keyToState.get(queryKey) as
| QueryState<T, InitialValue>
| undefined;

if (state) return state;
if (state) return state as QueryClientPrepareResponse<T, InitialValue>;

const newState: QueryState<T> = {
data: reactive<T | undefined>(initialValue),
const newState: QueryState<T, InitialValue> = {
// @ts-expect-error initialValue
data: reactive<T | InitialValue>(initialValue),
error: reactive<unknown | undefined>(undefined),
isPending: reactive(false),
isFetching: false,
};

// @ts-expect-error newState
this.keyToState.set(queryKey, newState);

return newState;
return newState as QueryClientPrepareResponse<T, InitialValue>;
}
getData<T>(queryFn: QueryFn<T>, queryKey: string) {
const state = this.keyToState.get(queryKey);
Expand Down Expand Up @@ -75,21 +89,21 @@ function injectQueryClient() {
return queryClient;
}

export function useQuery<T>(
export function useQuery<T, InitialValue extends T | undefined = undefined>(
{
queryFn,
queryKey,
initialValue,
}: {
queryFn: QueryFn<T>;
queryKey: string;
initialValue?: T;
initialValue?: InitialValue;
},
injections: { queryClient?: QueryClient } = {}
) {
const queryClient = injections?.queryClient || injectQueryClient();

const { isPending, data, error } = queryClient.prepare<T>(
const { isPending, data, error } = queryClient.prepare<T, InitialValue>(
queryKey,
initialValue
);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/reactive/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function bool(initialValue: boolean): [Reactive<boolean>, () => void] {
return [rx, toggle];
}

export function not(condition: MaybeReactive<boolean>) {
export function not(condition: MaybeReactive<boolean | undefined> | undefined) {
if (isReactive(condition)) {
return computed(() => !condition.value);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/utils/array.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type MaybeArray<T> = T | T[];

export function toArray<T>(maybeArr: T | T[]): T[] {
if (maybeArr === undefined) return [];

return Array.isArray(maybeArr) ? maybeArr : [maybeArr];
}

Expand Down
17 changes: 8 additions & 9 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": ["../../tsconfig.json"],
"compilerOptions": {
"extends": ["../../tsconfig.json"],
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"declarationDir": "./dist/types",
Expand All @@ -9,12 +9,11 @@
"jsx": "preserve",
"jsxImportSource": "~",
"types": ["node"],


"paths": {
"~/*": ["src/*"]
}
"~/*": ["src/*"]
},
"include": ["src"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts", "dist", "node_modules"]
}
"experimentalDecorators": true
},
"include": ["src"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts", "dist", "node_modules"]
}
4 changes: 3 additions & 1 deletion packages/demo-todo-app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { createRouter, ROUTER_PROVIDER_KEY } from 'tinaf/router';
import { ProductListPage } from './src/pages/ProductList.page';
import { ProductPage } from './src/pages/Product.page';
import { DashboardRoutes } from './src/dashboard/routes';
import { createQueryClientProvider } from 'tinaf/http';

const app = createApp(App);

ProductPage({});
const queryClientProvider = createQueryClientProvider();
app.use(queryClientProvider);

const router = createRouter([
{
Expand Down
4 changes: 2 additions & 2 deletions packages/demo-todo-app/src/Header/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Product } from 'src/models/product';
import { component, onDestroy } from 'tinaf/component';
import { inputReactive } from 'tinaf/reactive';
import { PRODUCTS } from '../data/products.mock';
import { debounceTime, distinctUntilChanged, map } from 'rxjs';
import { PRODUCTS } from '../api/products';

export const SearchBar = component<{
updateProducts: (products: Product[]) => void;
Expand Down Expand Up @@ -31,7 +31,7 @@ export const SearchBar = component<{
});

return <div className='p-4 border rounded-sm h-9 flex grow items-center justify-center border-slate-800'>
<input placeholder='What are you looking for ?' className='outline-none w-full' value={searchInput} />
<input placeholder='What are you looking for ?' className='outline-none w-full' value={searchInput} />


</div>
Expand Down
Loading

0 comments on commit 6cb26a2

Please sign in to comment.