Skip to content

Instantly share code, notes, and snippets.

@lucianobarauna
Created July 29, 2025 20:22
Show Gist options
  • Save lucianobarauna/8d759e6c05eb1d2d9c8dca5abd450015 to your computer and use it in GitHub Desktop.
Save lucianobarauna/8d759e6c05eb1d2d9c8dca5abd450015 to your computer and use it in GitHub Desktop.
Provider para queryStrings - Feito para next a princípio
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useLayoutEffect,
useState,
} from 'react';
interface IQueryParamsState<TApi, TUi> {
api: TApi;
ui: TUi;
}
interface IQueryParamsContext<TApi, TUi> {
rawParams: IQueryParamsState<TApi, TUi>;
setRawParams: React.Dispatch<
React.SetStateAction<IQueryParamsState<TApi, TUi>>
>;
}
const QueryParamsContext = createContext<
IQueryParamsContext<any, any> | undefined
>(undefined);
const UI_PREFIX = 'ui_';
export const QueryParamsProvider = <
TApi extends Record<string, any>,
TUi extends Record<string, any>,
>({
children,
}: {
children: ReactNode;
}) => {
const searchParams = useSearchParams();
const router = useRouter();
const getInitialParams = () => {
const entries = Object.fromEntries(searchParams.entries());
const api: Partial<TApi> = {};
const ui: Partial<TUi> = {};
for (const [key, value] of Object.entries(entries)) {
if (key.startsWith(UI_PREFIX)) {
//const cleanKey = key.slice(UI_PREFIX.length);
ui[key as keyof TUi] = value as TUi[keyof TUi];
} else {
api[key as keyof TApi] = value as TApi[keyof TApi];
}
}
return {
api: api as TApi,
ui: ui as TUi,
};
};
const [rawParams, setRawParams] = useState<IQueryParamsState<TApi, TUi>>({
api: {} as TApi,
ui: {} as TUi,
});
useLayoutEffect(() => {
const initial = getInitialParams();
setRawParams(initial);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
useEffect(() => {
const query = new URLSearchParams();
Object.entries(rawParams.api).forEach(([key, value]) => {
if (value != null && value !== '') {
query.set(key, value);
}
});
Object.entries(rawParams.ui).forEach(([key, value]) => {
if (value != null && value !== '') {
query.set(`${key}`, value);
}
});
router.replace(`?${query.toString()}`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rawParams]);
return (
<QueryParamsContext.Provider value={{ rawParams, setRawParams }}>
{children}
</QueryParamsContext.Provider>
);
};
export function useQueryParams<
TApi extends Record<string, any>,
TUi extends Record<string, any>,
>() {
const context = useContext(
QueryParamsContext as React.Context<
IQueryParamsContext<TApi, TUi> | undefined
>
);
if (!context) {
throw new Error('useQueryParams must be used within a QueryParamsProvider');
}
const { rawParams, setRawParams } = context;
const updateParams = useCallback(
(
next: Partial<
{ [K in keyof TApi]: TApi[K] } & {
[K in keyof TUi]: TUi[K];
}
>
) => {
setRawParams((prev) => {
const updatedApi = { ...prev.api };
const updatedUi = { ...prev.ui };
Object.entries(next).forEach(([key, value]) => {
if (key.startsWith(UI_PREFIX)) {
//const cleanKey = key.slice(UI_PREFIX.length);
updatedUi[key as keyof TUi] = value as TUi[keyof TUi];
} else {
updatedApi[key as keyof TApi] = value as TApi[keyof TApi];
}
});
return {
api: updatedApi,
ui: updatedUi,
};
});
},
[setRawParams]
);
const setParams = useCallback(
(
updater: (
prev: IQueryParamsState<TApi, TUi>
) => IQueryParamsState<TApi, TUi>
) => {
setRawParams((prev) => updater(prev));
},
[setRawParams]
);
return {
params: rawParams,
updateParams,
setParams,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment