Skip to content

Commit

Permalink
feat: wrapped hook for nextjs and some other improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
asmyshlyaev177 committed Jun 30, 2024
1 parent 150400f commit 03d1cfc
Show file tree
Hide file tree
Showing 21 changed files with 188 additions and 114 deletions.
10 changes: 8 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"url": "https://github.com/asmyshlyaev177/url-state/issues"
},
"workspaces": [
"packages/url-state",
"packages/urlstate",
"packages/example-nextjs"
],
"main": "dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/example-nextjs/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
}
},
};

export default nextConfig;
44 changes: 13 additions & 31 deletions packages/example-nextjs/src/app/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,37 @@
'use client';
import React from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { Button } from './components/Button';
import { Field } from './components/Field';
import { Input } from './components/Input';

import { useUrlState } from './useUrlState';
import { form } from './form';
import { form, useFormState } from './form';

export const Form = ({ className }: { className?: string }) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const { state, updateState, updateUrl } = useFormState(form);

const { parse, stringify } = useUrlState(form);

const [state, setState] = React.useState(() => parse(searchParams));
const [auto, setAuto] = React.useState(true);

const handleChange = React.useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
setState((s) => ({
...s,
updateState((curr) => ({
...curr,
[ev.target.id]:
ev.target.type === 'checkbox' ? ev.target.checked : ev.target.value,
}));
},
[],
[updateState],
);

const handleSave = () => {
router.push(`${pathname}?${stringify(state)}`);
updateUrl(state);
};

// set URI when state change
React.useEffect(() => {
if (auto) {
router.push(`${pathname}?${stringify(state)}`);
}
}, [pathname, router, state, auto, stringify]);
// set state when URI change
React.useEffect(() => {
if (auto) {
setState((curr) => {
const newState = parse(searchParams);
return JSON.stringify(curr) === JSON.stringify(newState)
? curr
: newState;
});
updateUrl(state);
}
}, [searchParams, auto, parse]);
}, [state, auto, updateUrl]);

return (
<div className={`flex flex-col gap-4 justify-between ${className}`}>
Expand Down Expand Up @@ -93,11 +75,11 @@ export const Form = ({ className }: { className?: string }) => {
id={tag.id}
type="checkbox"
onChange={() =>
setState((st) => ({
...st,
tags: st.tags.find((t) => t.id === tag.id)
? st.tags.filter((t) => t.id !== tag.id)
: st.tags.concat(tag),
updateState((curr) => ({
...curr,
tags: curr.tags.find((t) => t.id === tag.id)
? curr.tags.filter((t) => t.id !== tag.id)
: curr.tags.concat(tag),
}))
}
/>
Expand Down
30 changes: 24 additions & 6 deletions packages/example-nextjs/src/app/Status.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use client';
import React from 'react';
import { useSearchParams } from 'next/navigation';
import { Field } from './components/Field';
import { useUrlState } from './useUrlState';
import { form } from './form';
import { form, useFormState } from './form';

export const Status = ({ className }: { className?: string }) => {
const { parse } = useUrlState(form);
const params = useSearchParams();
const { state } = useFormState(form);

return (
<div className={className}>
Expand All @@ -19,9 +16,30 @@ export const Status = ({ className }: { className?: string }) => {
dark:text-black text-wrap break-all whitespace-pre-wrap
self-stretch overflow-y-auto grow-0"
>
{JSON.stringify(parse(params), null, 2)}
{JSON.stringify(state, null, 2)}
</pre>
</Field>
</div>
);
};

// TODO: move to main lib
// function usePrevious<T>(value: T) {
// const ref = React.useRef(value);

// React.useEffect(() => {
// if (JSON.stringify(value) !== JSON.stringify(ref.current)) {
// ref.current = value;
// }
// }, [value]);

// return ref.current;
// }

// function useChangedValue<T>(value: T) {
// const valuePrev = usePrevious(value);

// return JSON.stringify(value) !== JSON.stringify(valuePrev)
// ? value
// : valuePrev;
// }
42 changes: 42 additions & 0 deletions packages/example-nextjs/src/app/form.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import React from 'react';
import { type JSONCompatible, useUrlEncode } from '../../../urlstate';

export const form: Form = {
name: '',
age: '',
Expand All @@ -11,3 +15,41 @@ type Form = {
'agree to terms': boolean;
tags: { id: string; value: { text: string; time: Date } }[];
};

// TODO: to generic function
// useUrlState (obj) ..., rename current useUrlState to useUrlEncoder

export function useFormState<T>(defaultState?: JSONCompatible<T>) {
const router = useRouter();
const pathname = usePathname();
const { parse, stringify } = useUrlEncode(defaultState);
const searchParams = useSearchParams();
const [state, setState] = React.useState(() => parse(searchParams));

React.useEffect(() => {
setState((curr) => {
const newVal = parse(searchParams);
return JSON.stringify(curr) === JSON.stringify(newVal) ? curr : newVal;
});
}, [parse, searchParams]);

const updateState = React.useCallback(
(value: typeof state | ((currState: typeof state) => typeof state)) => {
typeof value === 'function'
? setState((curr) => value(curr))
: setState(value);
},
[],
);

const updateUrl = React.useCallback(
(value: typeof state | ((currState: typeof state) => typeof state)) => {
typeof value === 'function'
? router.push(`${pathname}?${stringify(value(state))}`)
: router.push(`${pathname}?${stringify(value)}`);
},
[pathname, router, stringify, state],
);

return { updateUrl, updateState, state };
}
4 changes: 2 additions & 2 deletions packages/example-nextjs/src/app/test/Comp1.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
'use client';
import React from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { useUrlState } from '../useUrlState';
import { useUrlEncode } from '../../../../urlstate/useUrlEncode';
import { Textarea } from './Textarea';
import { fromJSON, toJSON } from './utils';

export const Comp1 = ({ className }: { className: string }) => {
const router = useRouter();
const pathname = usePathname();

const { stringify } = useUrlState();
const { stringify } = useUrlEncode();
const [state, setState] = React.useState('');

React.useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/example-nextjs/src/app/test/Comp2.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client';
import React from 'react';
import { useSearchParams } from 'next/navigation';
import { useUrlState } from '../useUrlState';
import { useUrlEncode } from '../../../../urlstate/useUrlEncode';
import { Textarea } from './Textarea';
import { toJSON } from './utils';

export const Comp2 = ({ className }: { className: string }) => {
const searchParams = useSearchParams();

const { parse } = useUrlState();
const { parse } = useUrlEncode();

return (
<Textarea
Expand Down
56 changes: 0 additions & 56 deletions packages/example-nextjs/src/app/useUrlState.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { encode, decode, type Type } from './url-state';
export { encode, decode, type Type } from './urlstate';
4 changes: 0 additions & 4 deletions packages/url-state/index.ts

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions packages/urlstate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { encode, decode } from './encoder';
import { type Type, type JSONCompatible, typeOf } from './utils';
import { useUrlEncode } from './useUrlEncode';
export { encode, decode, typeOf, useUrlEncode };
export type { Type, JSONCompatible };
File renamed without changes.
Loading

0 comments on commit 03d1cfc

Please sign in to comment.