@namphuongtechnologi/react
v0.2.3
Published
Packages of react for namphuongtechnologies
Downloads
251
Readme
@namphuongtechnologi/react
Introduction
This project is designed to facilitate the reusability of React libraries. It includes a collection of reusable components, hooks, and logics. The main goal of this project is to streamline the development process by providing a set of pre-built elements that can be incorporated into various React applications. This not only saves time but also ensures consistency across different projects.
Usage
Table of Contents
Context
import { Context } from '@namphuongtechnologi/react';
type ContextType = {
value: string;
};
const exampleContext = new Context<ContextType>({
name: 'example-context',
});
const App = () => {
const ctxValue: ContextType = {
value: 'Hello World',
};
return (
<exampleContext.Provider value={ctxValue}>
<Child />
</exampleContext.Provider>
);
};
const Child = () => {
const context = exampleContext.useContext();
console.log(context.value);
return <>Child</>;
};
Hook-form
import { HookForm, InferForm } from '@namphuongtechnologi/react';
import { z } from 'zod';
const exampleForm = new HookForm({
schema: z.object({
className: z.string(),
students: z.array(
z.object({
name: z.string(),
})
),
}),
});
type ExampleForm = InferForm<typeof exampleForm>;
const App = () => {
const form = exampleForm.useForm({
defaultValues: {
className: 'Class A',
students: [{ name: 'Student A' }, { name: 'Student B' }],
},
});
const students = exampleForm.useFieldArray({
control: form.control,
name: 'students',
});
const handleSubmit = (data: ExampleForm) => {
console.log(data);
};
return (
<exampleForm.Provider {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)}>
<exampleForm.Field name='className' render={({ field }) => <input {...field} />} />
{students.fields.map((field, index) => (
<exampleForm.Field
key={field.__id}
name={`students.${index}.name`}
render={({ field: { onChange, value } }) => ({
/* Render Content */
})}
/>
))}
</form>
</exampleForm.Provider>
);
};
Hooks
useBeforeLeave
import { useBeforeLeave } from '@namphuongtechnologi/react';
import { useRef, useState } from 'react';
const App = () => {
const [canLeave, setCanLeave] = useState(false);
const blocker = useBeforeLeave({
when: canLeave,
message: 'Are you sure you want to leave?',
beforeUnload: true,
});
console.log(blocker);
const toggleClick = () => setCanLeave((prev) => !prev);
return (
<>
<button onClick={toggleClick}>{canLeave ? 'Disable' : 'Enable'} beforeunload</button>
<button onClick={() => window.location.reload()}>Reload</button>
</>
);
};
usePDF
PromiseDebounceFunction
import { usePDF } from '@namphuongtechnologi/react';
import { useRef, useState } from 'react';
type PDFValue = {
value: string;
};
const App = () => {
const ref = useRef<string[]>([]);
const PDFClick = usePDF<PDFValue>(({ value }) => {
const index = ref.current.indexOf(value);
if (index === -1) {
ref.current.push(value);
} else {
ref.current.splice(index, 1);
}
return {
commit: () => {
console.log(ref.current);
ref.current = [];
return Promise.resolve();
},
resolved: () => {},
error: (error) => {
console.log(error);
},
};
}, 2000);
return (
<>
{Array.from({ length: 10 }, (_, i) => (
<Child key={i} no={i + 1} onClick={PDFClick} />
))}
</>
);
};
const Child = ({ no, onClick }: { no: number; onClick: ReturnType<typeof usePDF<PDFValue>> }) => {
const [clicked, setClicked] = useState(false);
const handleClick = async () => {
setClicked(true);
await onClick({ value: `${no}` });
setClicked(false);
};
return (
<button onClick={handleClick} disabled={clicked}>
{clicked ? 'Loading' : `Button ${no}`}
</button>
);
};
Router
import { s, useSearchParams } from '@namphuongtechnologi/react';
import { z } from 'zod';
type Student = {
id: number;
name: string;
};
const App = () => {
const [params, setParams] = useSearchParams({
schema: z.object({
keyword: s.string(),
page: s.number().default(20),
textArr: s.array<string>(['123']),
numArr: s.array<number>([345]),
dynamicArr: s.array<Student>(),
active: s.boolean(),
}),
});
const handleClick = () => {
setParams({
keyword: 'John Doe',
page: 30,
textArr: ['Marry', 'Jane', 'Wick'],
numArr: [1, 2, 3],
dynamicArr: [
{ id: 1, name: 'student_1' },
{ id: 2, name: 'student_2' },
],
active: true,
});
};
return (
<>
<pre style={{ textAlign: 'left' }}>{JSON.stringify(params, null, 2)}</pre>
<button onClick={handleClick}>Set new params</button>
</>
);
};
Tanstack-Query
import { createQueryService, createInfiniteQueryService, createMutationService } from '@namphuongtechnologi/react';
import { QueryClient } from '@tanstack/react-query';
import { z } from 'zod';
import { axios } from './config/axios';
// Config
const queryClient = new QueryClient();
const QueryService = createQueryService(queryClient);
const InfiniteQueryService = createInfiniteQueryService(queryClient);
const MutationService = createMutationService(queryClient);
type InfiniteRecord<T> = {
data: T[]
totalRecord: number
}
declare module '@namphuongtechnologi/react' {
interface Tanstack_InfiniteQuery_Service<T> extends InfiniteRecord<T> {}
}
// Define Type
interface IPost {
id: number;
title: string;
}
// API
const getPosts = async (payload?: { signal?: AbortSignal }) => {
const { signal } = payload || {};
// baseURL: 'https://jsonplaceholder.typicode.com'
const { data } = await axios.instance.get<IPost[]>('/posts', { signal });
return data;
};
const getPost = async (postId: PostSchema['postId'], payload?: { signal?: AbortSignal }) => {
const { signal } = payload || {};
const { data } = await axios.instance.get<IPost>(`/posts/${postId}`, { signal });
return data;
};
const createPost = async (payload: Omit<IPost, 'id'>) => {
await axios.instance.post<IPost>('/posts', payload);
};
// Define Schema
const postSchema = z.object({
postId: z.number(),
});
type PostSchema = z.infer<typeof postSchema>;
// Define Query-service
const postsQuery = new QueryService({
name: 'posts',
fn: ({ signal }) => getPosts({ signal }),
});
const postQuery = new QueryService({
name: 'post',
schema: postSchema
fn: ({ signal, payload: { postId } }) => getPost(postId, { signal }),
});
const CreatePost = new MutationService({
name: 'create-post',
fn: ({ payload }) => createPost(payload),
onSuccess: () => {
postsQuery.invalidate();
},
});
// App
const App = () => {
const createPost = CreatePost.useMutation();
const { data, isLoading, isError } = postsQuery.useQuery();
const handleCreatePost = () => {
createPost.mutate(
{
title: 'New Post',
},
{
onSuccess: () => alert('Success!'),
}
);
};
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error</div>;
if (!data) return <div>No Data</div>;
return (
<>
<ul>
{data.map((post) => (
<li key={post.id} onClick={() => postQuery.ensureQueryData({ postId: post.id })}>
{post.title}
</li>
))}
</ul>
<button disabled={createPost.isPending} onClick={handleCreatePost}>
Create Post
</button>
</>
);
};
Permission
// config.ts
export enum User_Permission_Code {
PERMISSION_A = 'permission-a',
PERMISSION_B = 'permission-b',
}
declare module '@namphuongtechnologi/react' {
interface IUser_Permission {
code: User_Permission_Code;
c: boolean;
r: boolean;
u: boolean;
d: boolean;
}
}
// PermissionProvider.tsx
import {
createRecordPermission,
PermissionProvider as NPPermissionProvider,
} from '@namphuongtechnologi/react';
import type { PropsWithChildren } from 'react';
export default function PermissionProvider({ children }: PropsWithChildren) {
const permission = createRecordPermission('<permissions>', '<key>')
return (
<NPPermissionProvider permission={permission} notFoundPath='/not-found' notFoundComponent={<h1>404</h1>}>
{children}
</NPPermissionProvider>
);
}
// App.tsx
import PermissionProvider from './PermissionProvider';
const App = () => {
return <PermissionProvider>{/* Your App */}</PermissionProvider>;
};
// ExampleRoutes.tsx
import { Permission } from '@namphuongtechnologi/react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { User_Permission_Code } from './config';
import ExamplePage from './ExamplePage';
const ExampleRoutes = () => {
return (
<Routes>
<Route
path=''
element={
<Permission
isRoute
record={{
[User_Permission_Code['PERMISSION_A']]: ({ r }) => r,
}}
>
<ExamplePage />
</Permission>
}
/>
<Route path='*' element={<Navigate to='/not-found' />} />
</Routes>
);
};
// ExamplePage.tsx
import { useHavePermission } from '@namphuongtechnologi/react';
import { User_Permission_Code } from './config';
const ExamplePage = () => {
const havePermission = useHavePermission();
const isHaveCreatePermissionA = havePermission({
[User_Permission_Code['PERMISSION_A']]: ({ c }) => c,
});
return (
<div>
ExamplePage
{isHaveCreatePermissionA && (
<span className='text-red-500'>If you see this, you are have create permission A</span>
)}
</div>
);
};
Calendar Timeline
import {
headerLabelFormat,
primaryHeaderLabelFormat,
Timeline,
timelineBoundsChange,
} from '@namphuongtechnologi/react';
import type { RenderGroupProps, RenderItemProps } from '@namphuongtechnologi/react';
import { DB_DATE_TIME_FORMAT } from '@namphuongtechnologi/utils';
import dayjs from 'dayjs';
import type { TimelineGroupBase, TimelineItemBase } from 'react-calendar-timeline';
import { DateHeader, SidebarHeader, TimelineHeaders } from 'react-calendar-timeline';
import '@namphuongtechnologi/react/style.css';
interface Item {
id: string;
name: string;
start: string;
end: string;
}
interface Data {
id: string;
name: string;
items: Item[];
}
type ItemType = TimelineItemBase<dayjs.Dayjs> & { data: Item };
type GroupType = TimelineGroupBase & { data: Omit<Data, 'items'> };
const defaultTimeStart = dayjs().startOf('month').toDate();
const defaultTimeEnd = dayjs().endOf('month').toDate();
const data: Data[] = Array.from({ length: 10 }).map((_, i) => ({
id: `${i + 1}`,
name: `user${i + 1}`,
items: Array.from({ length: 5 }).map((_, j) => {
const start = ~~(Math.random() * 25);
const extra = 1 + ~~(Math.random() * 3);
return {
id: `${++id}`,
name: `Code${i + j + 1}`,
start: dayjs().startOf('M').add(start, 'day').format(DB_DATE_TIME_FORMAT),
end: dayjs()
.startOf('M')
.add(start + extra, 'day')
.format(DB_DATE_TIME_FORMAT),
};
}),
}));
const App = () => {
const { groups, items } = data.reduce<{
items: ItemType[];
groups: GroupType[];
}>(
(acc, { items, ...cur }) => {
acc.groups.push({
id: cur.id,
title: cur.name,
data: cur,
});
items.forEach((item) => {
acc.items.push({
id: item.id,
group: cur.id,
title: item.name,
start_time: dayjs(item.start),
end_time: dayjs(item.end),
data: item,
});
});
return acc;
},
{ items: [], groups: [] }
);
const onBoundsChange = (start: number, end: number) => {
const value = timelineBoundsChange(start, end);
if (!value) {
return;
}
const { month, year } = value;
console.log(month, year);
};
return (
<Timeline>
<Timeline.Calendar<ItemType, GroupType['data']>
groups={groups}
items={items}
renderItem={Item}
renderGroup={Group}
fallback={<>Không có dữ liệu</>}
defaultTimeStart={defaultTimeStart}
defaultTimeEnd={defaultTimeEnd}
onBoundsChange={onBoundsChange}
>
<TimelineHeaders>
<SidebarHeader>
{({ getRootProps }) => (
<Timeline.SidebarHeader {...getRootProps()}>Tiêu đề</Timeline.SidebarHeader>
)}
</SidebarHeader>
<DateHeader labelFormat={primaryHeaderLabelFormat} unit='primaryHeader' />
<DateHeader labelFormat={headerLabelFormat} />
</TimelineHeaders>
<Timeline.Markers showNow />
</Timeline.Calendar>
</Timeline>
);
};
const Item = ({ getItemProps, item }: RenderItemProps<ItemType>) => {
// @ts-expect-error comment for unused
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onMouseDown, onMouseUp, onTouchStart, onTouchEnd, className, title, ...itemProps } =
getItemProps({});
return <p {...itemProps}>{item.title}</p>;
};
const Group = ({ group }: RenderGroupProps<GroupType['data']>) => {
return <>{group.title}</>;
};