remix-utils
Advanced tools
Comparing version 2.11.1 to 3.0.0
@@ -5,9 +5,8 @@ export * from "./react/client-only"; | ||
export * from "./react/external-scripts"; | ||
export * from "./react/outlet"; | ||
export * from "./react/revalidate-link"; | ||
export * from "./react/structured-data"; | ||
export * from "./react/use-data"; | ||
export * from "./react/use-data-refresh"; | ||
export * from "./react/use-hydrated"; | ||
export * from "./react/use-locales"; | ||
export * from "./react/use-revalidate"; | ||
export * from "./react/use-route-data"; | ||
export * from "./react/use-should-hydrate"; |
@@ -5,9 +5,8 @@ export * from "./react/client-only"; | ||
export * from "./react/external-scripts"; | ||
export * from "./react/outlet"; | ||
export * from "./react/revalidate-link"; | ||
export * from "./react/structured-data"; | ||
export * from "./react/use-data"; | ||
export * from "./react/use-data-refresh"; | ||
export * from "./react/use-hydrated"; | ||
export * from "./react/use-locales"; | ||
export * from "./react/use-revalidate"; | ||
export * from "./react/use-route-data"; | ||
export * from "./react/use-should-hydrate"; |
import { ReactNode } from "react"; | ||
/** | ||
* @deprecated Pass a function as children to avoid issues with client only | ||
* imported components | ||
*/ | ||
declare type DeprecatedProps = { | ||
children: ReactNode; | ||
fallback?: ReactNode; | ||
}; | ||
declare type Props = DeprecatedProps | { | ||
declare type Props = { | ||
/** | ||
@@ -16,3 +8,3 @@ * You are encouraged to add a fallback that is the same dimensions | ||
*/ | ||
children: () => ReactNode; | ||
children(): ReactNode; | ||
fallback?: ReactNode; | ||
@@ -19,0 +11,0 @@ }; |
@@ -19,6 +19,3 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; | ||
export function ClientOnly({ children, fallback = null }) { | ||
if (typeof children !== "function") { | ||
console.warn("[remix-utils] ClientOnly: Pass a function as children to avoid issues with client-only imported components"); | ||
} | ||
return useHydrated() ? (_jsx(_Fragment, { children: typeof children === "function" ? children() : children }, void 0)) : (_jsx(_Fragment, { children: fallback }, void 0)); | ||
return useHydrated() ? _jsx(_Fragment, { children: children() }) : _jsx(_Fragment, { children: fallback }); | ||
} |
@@ -19,3 +19,3 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
export function AuthenticityTokenProvider({ children, token, }) { | ||
return _jsx(context.Provider, Object.assign({ value: token }, { children: children }), void 0); | ||
return _jsx(context.Provider, { value: token, children: children }); | ||
} | ||
@@ -55,3 +55,3 @@ /** | ||
let token = useAuthenticityToken(); | ||
return _jsx("input", { type: "hidden", value: token, name: name }, void 0); | ||
return _jsx("input", { type: "hidden", value: token, name: name }); | ||
} |
@@ -12,3 +12,3 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; | ||
}); | ||
return (_jsx(_Fragment, { children: links.map((link) => (_createElement("link", Object.assign({}, link, { key: link.integrity || JSON.stringify(link) })))) }, void 0)); | ||
return (_jsx(_Fragment, { children: links.map((link) => (_createElement("link", { ...link, key: link.integrity || JSON.stringify(link) }))) })); | ||
} |
@@ -17,6 +17,5 @@ import { createElement as _createElement } from "react"; | ||
return (_jsx("link", { rel: rel, href: props.src, as: as, crossOrigin: props.crossOrigin, integrity: props.integrity, referrerPolicy: props.referrerPolicy }, props.src)); | ||
}), | ||
scripts.map((props) => { | ||
return _createElement("script", Object.assign({}, props, { key: props.src })); | ||
})] }, void 0)); | ||
}), scripts.map((props) => { | ||
return _createElement("script", { ...props, key: props.src }); | ||
})] })); | ||
} |
@@ -55,3 +55,3 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
__html: renderedScript, | ||
} }, void 0)); | ||
} })); | ||
} |
@@ -1,1 +0,1 @@ | ||
export declare function useRouteData<Data>(route: string): Data | undefined; | ||
export declare function useRouteData<Data>(routeId: string): Data | undefined; |
import { useMatches } from "@remix-run/react"; | ||
export function useRouteData(route) { | ||
export function useRouteData(routeId) { | ||
var _a; | ||
return (_a = useMatches().find((match) => { var _a; return ((_a = match.handle) === null || _a === void 0 ? void 0 : _a.id) === route || match.pathname === route; })) === null || _a === void 0 ? void 0 : _a.data; | ||
return (_a = useMatches().find((match) => match.id === routeId)) === null || _a === void 0 ? void 0 : _a.data; | ||
} |
@@ -1,2 +0,1 @@ | ||
export * from "./server/body-parser"; | ||
export * from "./server/cors"; | ||
@@ -3,0 +2,0 @@ export * from "./server/csrf"; |
@@ -1,2 +0,1 @@ | ||
export * from "./server/body-parser"; | ||
export * from "./server/cors"; | ||
@@ -3,0 +2,0 @@ export * from "./server/csrf"; |
@@ -35,2 +35,5 @@ import { v4 as uuid } from "uuid"; | ||
export async function verifyAuthenticityToken(request, session, sessionKey = "csrf") { | ||
if (request.bodyUsed) { | ||
throw new Error("The body of the request was read before calling verifyAuthenticityToken. Ensure you clone it before reading it."); | ||
} | ||
// We clone the request to ensure we don't modify the original request. | ||
@@ -37,0 +40,0 @@ // This allow us to parse the body of the request and let the original request |
/// <reference types="node" /> | ||
import { JsonValue } from "type-fest"; | ||
export declare type ReplacerFunction = (key: string, value: unknown) => unknown; | ||
export declare type ExtendedResponseInit = ResponseInit & { | ||
replacer?: ReplacerFunction | undefined; | ||
}; | ||
/** | ||
* @deprecated Use the `json` function from Remix directly. | ||
* | ||
* @description | ||
* A wrapper of the `json` function from `remix` which accepts a generic for the | ||
* data to be serialized. This allows you to use the same type for `json` and | ||
* on `useLoaderData` to ensure the type is always in sync. | ||
* A wrapper of the `json` function from `remix` which lets you pass a replacer | ||
* function in the second argument. | ||
* | ||
* The type must extend the JsonValue from type-fest, this means only JSON | ||
* compatible types are allowed inside the data which will help you avoid trying | ||
* to send functions or class instances. | ||
* This helper, will call JSON.stringify against your data with the replacer if | ||
* defined to let you configure how the data is serialized. | ||
* | ||
* @example | ||
* type LoaderData = { user: { name: string } }; | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* type LoaderData = { user: { name: string, createdAt: Date } }; | ||
* | ||
* let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
* if (typeof value !== "Date") return value; | ||
* return { __type: "Date", value: value.toISOString() }; | ||
* } | ||
* | ||
* let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
* if (value.__type === "Date") return new Date(value.value); | ||
* return value; | ||
* } | ||
* | ||
* let validator: ValidatorFunction = data => { | ||
* return schema.parse(data) | ||
* } | ||
* | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let user = await getUser(request); | ||
* return json<LoaderData>({ user }); | ||
* return json<LoaderData>({ user }, { replacer }); | ||
* } | ||
* | ||
* export function Screen() { | ||
* let { user } = useLoaderData<LoaderData>(); | ||
* let { user } = useLoaderData<LoaderData>({ reviver, validator }); | ||
* return <UserProfile user={user} />; | ||
* } | ||
*/ | ||
export declare function json<Data extends JsonValue>(data: Data, init?: number | ResponseInit): Response; | ||
export declare function json<Data>(data: Data, init?: number | ExtendedResponseInit): Response; | ||
/** | ||
* Create a response receiving a JSON object with the status code 201. | ||
* @example | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let result = await doSomething(request); | ||
* return created(result); | ||
* } | ||
*/ | ||
export declare function created<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
* Create a new Response with a redirect set to the URL the user was before. | ||
@@ -52,3 +76,3 @@ * It uses the Referer header to detect the previous URL. It asks for a fallback | ||
*/ | ||
export declare function badRequest<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function badRequest<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -62,3 +86,3 @@ * Create a response receiving a JSON object with the status code 401. | ||
*/ | ||
export declare function unauthorized<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function unauthorized<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -72,3 +96,3 @@ * Create a response receiving a JSON object with the status code 403. | ||
*/ | ||
export declare function forbidden<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function forbidden<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -82,3 +106,3 @@ * Create a response receiving a JSON object with the status code 404. | ||
*/ | ||
export declare function notFound<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function notFound<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -92,3 +116,3 @@ * Create a response receiving a JSON object with the status code 422. | ||
*/ | ||
export declare function unprocessableEntity<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function unprocessableEntity<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -102,3 +126,3 @@ * Create a response receiving a JSON object with the status code 500. | ||
*/ | ||
export declare function serverError<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function serverError<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -105,0 +129,0 @@ * Create a response with only the status 304 and optional headers. |
import { json as remixJson, redirect } from "@remix-run/server-runtime"; | ||
/** | ||
* @deprecated Use the `json` function from Remix directly. | ||
* | ||
* @description | ||
* A wrapper of the `json` function from `remix` which accepts a generic for the | ||
* data to be serialized. This allows you to use the same type for `json` and | ||
* on `useLoaderData` to ensure the type is always in sync. | ||
* A wrapper of the `json` function from `remix` which lets you pass a replacer | ||
* function in the second argument. | ||
* | ||
* The type must extend the JsonValue from type-fest, this means only JSON | ||
* compatible types are allowed inside the data which will help you avoid trying | ||
* to send functions or class instances. | ||
* This helper, will call JSON.stringify against your data with the replacer if | ||
* defined to let you configure how the data is serialized. | ||
* | ||
* @example | ||
* type LoaderData = { user: { name: string } }; | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* type LoaderData = { user: { name: string, createdAt: Date } }; | ||
* | ||
* let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
* if (typeof value !== "Date") return value; | ||
* return { __type: "Date", value: value.toISOString() }; | ||
* } | ||
* | ||
* let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
* if (value.__type === "Date") return new Date(value.value); | ||
* return value; | ||
* } | ||
* | ||
* let validator: ValidatorFunction = data => { | ||
* return schema.parse(data) | ||
* } | ||
* | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let user = await getUser(request); | ||
* return json<LoaderData>({ user }); | ||
* return json<LoaderData>({ user }, { replacer }); | ||
* } | ||
* | ||
* export function Screen() { | ||
* let { user } = useLoaderData<LoaderData>(); | ||
* let { user } = useLoaderData<LoaderData>({ reviver, validator }); | ||
* return <UserProfile user={user} />; | ||
@@ -26,5 +38,23 @@ * } | ||
export function json(data, init) { | ||
return remixJson(data, init); | ||
if (typeof init === "number") { | ||
return remixJson(JSON.stringify(data), init); | ||
} | ||
if (typeof init === "undefined") { | ||
return remixJson(JSON.stringify(data)); | ||
} | ||
let { replacer, ...rest } = init; | ||
return remixJson(JSON.stringify(data, replacer), rest); | ||
} | ||
/** | ||
* Create a response receiving a JSON object with the status code 201. | ||
* @example | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let result = await doSomething(request); | ||
* return created(result); | ||
* } | ||
*/ | ||
export function created(data, init) { | ||
return json(data, { ...init, status: 201 }); | ||
} | ||
/** | ||
* Create a new Response with a redirect set to the URL the user was before. | ||
@@ -31,0 +61,0 @@ * It uses the Referer header to detect the previous URL. It asks for a fallback |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k; |
@@ -5,9 +5,8 @@ export * from "./react/client-only"; | ||
export * from "./react/external-scripts"; | ||
export * from "./react/outlet"; | ||
export * from "./react/revalidate-link"; | ||
export * from "./react/structured-data"; | ||
export * from "./react/use-data"; | ||
export * from "./react/use-data-refresh"; | ||
export * from "./react/use-hydrated"; | ||
export * from "./react/use-locales"; | ||
export * from "./react/use-revalidate"; | ||
export * from "./react/use-route-data"; | ||
export * from "./react/use-should-hydrate"; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -17,9 +21,8 @@ if (k2 === undefined) k2 = k; | ||
__exportStar(require("./react/external-scripts"), exports); | ||
__exportStar(require("./react/outlet"), exports); | ||
__exportStar(require("./react/revalidate-link"), exports); | ||
__exportStar(require("./react/structured-data"), exports); | ||
__exportStar(require("./react/use-data"), exports); | ||
__exportStar(require("./react/use-data-refresh"), exports); | ||
__exportStar(require("./react/use-hydrated"), exports); | ||
__exportStar(require("./react/use-locales"), exports); | ||
__exportStar(require("./react/use-revalidate"), exports); | ||
__exportStar(require("./react/use-route-data"), exports); | ||
__exportStar(require("./react/use-should-hydrate"), exports); |
import { ReactNode } from "react"; | ||
/** | ||
* @deprecated Pass a function as children to avoid issues with client only | ||
* imported components | ||
*/ | ||
declare type DeprecatedProps = { | ||
children: ReactNode; | ||
fallback?: ReactNode; | ||
}; | ||
declare type Props = DeprecatedProps | { | ||
declare type Props = { | ||
/** | ||
@@ -16,3 +8,3 @@ * You are encouraged to add a fallback that is the same dimensions | ||
*/ | ||
children: () => ReactNode; | ||
children(): ReactNode; | ||
fallback?: ReactNode; | ||
@@ -19,0 +11,0 @@ }; |
@@ -22,7 +22,4 @@ "use strict"; | ||
function ClientOnly({ children, fallback = null }) { | ||
if (typeof children !== "function") { | ||
console.warn("[remix-utils] ClientOnly: Pass a function as children to avoid issues with client-only imported components"); | ||
} | ||
return use_hydrated_1.useHydrated() ? (jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: typeof children === "function" ? children() : children }, void 0)) : (jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: fallback }, void 0)); | ||
return (0, use_hydrated_1.useHydrated)() ? (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children() }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback }); | ||
} | ||
exports.ClientOnly = ClientOnly; |
@@ -6,3 +6,3 @@ "use strict"; | ||
const react_1 = require("react"); | ||
let context = react_1.createContext(null); | ||
let context = (0, react_1.createContext)(null); | ||
/** | ||
@@ -23,3 +23,3 @@ * Save the Authenticity Token into context | ||
function AuthenticityTokenProvider({ children, token, }) { | ||
return jsx_runtime_1.jsx(context.Provider, Object.assign({ value: token }, { children: children }), void 0); | ||
return (0, jsx_runtime_1.jsx)(context.Provider, { value: token, children: children }); | ||
} | ||
@@ -40,3 +40,3 @@ exports.AuthenticityTokenProvider = AuthenticityTokenProvider; | ||
function useAuthenticityToken() { | ||
let token = react_1.useContext(context); | ||
let token = (0, react_1.useContext)(context); | ||
if (!token) | ||
@@ -62,4 +62,4 @@ throw new Error("Missing AuthenticityTokenProvider."); | ||
let token = useAuthenticityToken(); | ||
return jsx_runtime_1.jsx("input", { type: "hidden", value: token, name: name }, void 0); | ||
return (0, jsx_runtime_1.jsx)("input", { type: "hidden", value: token, name: name }); | ||
} | ||
exports.AuthenticityTokenInput = AuthenticityTokenInput; |
@@ -8,3 +8,3 @@ "use strict"; | ||
function DynamicLinks() { | ||
let links = react_2.useMatches().flatMap((match) => { | ||
let links = (0, react_2.useMatches)().flatMap((match) => { | ||
var _a; | ||
@@ -16,4 +16,4 @@ let fn = (_a = match.handle) === null || _a === void 0 ? void 0 : _a.dynamicLinks; | ||
}); | ||
return (jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: links.map((link) => (react_1.createElement("link", Object.assign({}, link, { key: link.integrity || JSON.stringify(link) })))) }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: links.map((link) => ((0, react_1.createElement)("link", { ...link, key: link.integrity || JSON.stringify(link) }))) })); | ||
} | ||
exports.DynamicLinks = DynamicLinks; |
@@ -8,3 +8,3 @@ "use strict"; | ||
function ExternalScripts() { | ||
let matches = react_2.useMatches(); | ||
let matches = (0, react_2.useMatches)(); | ||
let scripts = matches.flatMap((match) => { | ||
@@ -17,11 +17,10 @@ var _a; | ||
}); | ||
return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [scripts.map((props) => { | ||
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [scripts.map((props) => { | ||
let rel = props.noModule ? "modulepreload" : "preload"; | ||
let as = !props.noModule ? "script" : undefined; | ||
return (jsx_runtime_1.jsx("link", { rel: rel, href: props.src, as: as, crossOrigin: props.crossOrigin, integrity: props.integrity, referrerPolicy: props.referrerPolicy }, props.src)); | ||
}), | ||
scripts.map((props) => { | ||
return react_1.createElement("script", Object.assign({}, props, { key: props.src })); | ||
})] }, void 0)); | ||
return ((0, jsx_runtime_1.jsx)("link", { rel: rel, href: props.src, as: as, crossOrigin: props.crossOrigin, integrity: props.integrity, referrerPolicy: props.referrerPolicy }, props.src)); | ||
}), scripts.map((props) => { | ||
return (0, react_1.createElement)("script", { ...props, key: props.src }); | ||
})] })); | ||
} | ||
exports.ExternalScripts = ExternalScripts; |
@@ -39,3 +39,3 @@ "use strict"; | ||
function StructuredData() { | ||
const matches = react_1.useMatches(); | ||
const matches = (0, react_1.useMatches)(); | ||
const structuredData = matches.flatMap((match) => { | ||
@@ -57,6 +57,6 @@ const { handle, data } = match; | ||
: JSON.stringify(structuredData); | ||
return (jsx_runtime_1.jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { | ||
return ((0, jsx_runtime_1.jsx)("script", { type: "application/ld+json", dangerouslySetInnerHTML: { | ||
__html: renderedScript, | ||
} }, void 0)); | ||
} })); | ||
} | ||
exports.StructuredData = StructuredData; |
@@ -24,4 +24,4 @@ "use strict"; | ||
function useHydrated() { | ||
let [hydrated, setHydrated] = react_1.useState(() => !hydrating); | ||
react_1.useEffect(function hydrate() { | ||
let [hydrated, setHydrated] = (0, react_1.useState)(() => !hydrating); | ||
(0, react_1.useEffect)(function hydrate() { | ||
hydrating = false; | ||
@@ -28,0 +28,0 @@ setHydrated(true); |
@@ -28,3 +28,3 @@ "use strict"; | ||
function useLocales() { | ||
let matches = react_1.useMatches(); | ||
let matches = (0, react_1.useMatches)(); | ||
if (!matches) | ||
@@ -31,0 +31,0 @@ return undefined; |
@@ -1,1 +0,1 @@ | ||
export declare function useRouteData<Data>(route: string): Data | undefined; | ||
export declare function useRouteData<Data>(routeId: string): Data | undefined; |
@@ -5,6 +5,6 @@ "use strict"; | ||
const react_1 = require("@remix-run/react"); | ||
function useRouteData(route) { | ||
function useRouteData(routeId) { | ||
var _a; | ||
return (_a = react_1.useMatches().find((match) => { var _a; return ((_a = match.handle) === null || _a === void 0 ? void 0 : _a.id) === route || match.pathname === route; })) === null || _a === void 0 ? void 0 : _a.data; | ||
return (_a = (0, react_1.useMatches)().find((match) => match.id === routeId)) === null || _a === void 0 ? void 0 : _a.data; | ||
} | ||
exports.useRouteData = useRouteData; |
@@ -26,3 +26,3 @@ "use strict"; | ||
function useShouldHydrate() { | ||
return react_1.useMatches().some((match) => { | ||
return (0, react_1.useMatches)().some((match) => { | ||
if (!match.handle) | ||
@@ -29,0 +29,0 @@ return false; |
@@ -1,2 +0,1 @@ | ||
export * from "./server/body-parser"; | ||
export * from "./server/cors"; | ||
@@ -3,0 +2,0 @@ export * from "./server/csrf"; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
@@ -13,3 +17,2 @@ if (k2 === undefined) k2 = k; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./server/body-parser"), exports); | ||
__exportStar(require("./server/cors"), exports); | ||
@@ -16,0 +19,0 @@ __exportStar(require("./server/csrf"), exports); |
@@ -18,3 +18,3 @@ "use strict"; | ||
function createAuthenticityToken(session, sessionKey = "csrf") { | ||
let token = uuid_1.v4(); | ||
let token = (0, uuid_1.v4)(); | ||
session.set(sessionKey, token); | ||
@@ -40,2 +40,5 @@ return token; | ||
async function verifyAuthenticityToken(request, session, sessionKey = "csrf") { | ||
if (request.bodyUsed) { | ||
throw new Error("The body of the request was read before calling verifyAuthenticityToken. Ensure you clone it before reading it."); | ||
} | ||
// We clone the request to ensure we don't modify the original request. | ||
@@ -47,3 +50,3 @@ // This allow us to parse the body of the request and let the original request | ||
if (!session.has(sessionKey)) { | ||
throw responses_1.unprocessableEntity({ | ||
throw (0, responses_1.unprocessableEntity)({ | ||
message: "Can't find CSRF token in session.", | ||
@@ -54,3 +57,3 @@ }); | ||
if (!formData.get(sessionKey)) { | ||
throw responses_1.unprocessableEntity({ | ||
throw (0, responses_1.unprocessableEntity)({ | ||
message: "Can't find CSRF token in body.", | ||
@@ -62,3 +65,3 @@ }); | ||
if (formData.get(sessionKey) !== session.get(sessionKey)) { | ||
throw responses_1.unprocessableEntity({ | ||
throw (0, responses_1.unprocessableEntity)({ | ||
message: "Can't verify CSRF token authenticity.", | ||
@@ -65,0 +68,0 @@ }); |
@@ -27,3 +27,3 @@ "use strict"; | ||
function getClientIPAddress(requestOrHeaders) { | ||
let headers = get_headers_1.getHeaders(requestOrHeaders); | ||
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders); | ||
let ipAddress = headerNames | ||
@@ -42,3 +42,3 @@ .flatMap((headerName) => { | ||
return false; | ||
return is_ip_1.default(ip); | ||
return (0, is_ip_1.default)(ip); | ||
}); | ||
@@ -45,0 +45,0 @@ return ipAddress !== null && ipAddress !== void 0 ? ipAddress : null; |
@@ -7,3 +7,3 @@ "use strict"; | ||
function getClientLocales(requestOrHeaders) { | ||
let headers = get_headers_1.getHeaders(requestOrHeaders); | ||
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders); | ||
let acceptLanguage = headers.get("Accept-Language"); | ||
@@ -13,3 +13,3 @@ // if the header is not defined, return undefined | ||
return undefined; | ||
let locales = intl_parse_accept_language_1.parseAcceptLanguage(acceptLanguage, { | ||
let locales = (0, intl_parse_accept_language_1.parseAcceptLanguage)(acceptLanguage, { | ||
validate: Intl.DateTimeFormat.supportedLocalesOf, | ||
@@ -16,0 +16,0 @@ ignoreWildcard: true, |
@@ -6,3 +6,3 @@ "use strict"; | ||
function isPrefetch(requestOrHeaders) { | ||
let headers = get_headers_1.getHeaders(requestOrHeaders); | ||
let headers = (0, get_headers_1.getHeaders)(requestOrHeaders); | ||
let purpose = headers.get("Purpose") || | ||
@@ -9,0 +9,0 @@ headers.get("X-Purpose") || |
/// <reference types="node" /> | ||
import { JsonValue } from "type-fest"; | ||
export declare type ReplacerFunction = (key: string, value: unknown) => unknown; | ||
export declare type ExtendedResponseInit = ResponseInit & { | ||
replacer?: ReplacerFunction | undefined; | ||
}; | ||
/** | ||
* @deprecated Use the `json` function from Remix directly. | ||
* | ||
* @description | ||
* A wrapper of the `json` function from `remix` which accepts a generic for the | ||
* data to be serialized. This allows you to use the same type for `json` and | ||
* on `useLoaderData` to ensure the type is always in sync. | ||
* A wrapper of the `json` function from `remix` which lets you pass a replacer | ||
* function in the second argument. | ||
* | ||
* The type must extend the JsonValue from type-fest, this means only JSON | ||
* compatible types are allowed inside the data which will help you avoid trying | ||
* to send functions or class instances. | ||
* This helper, will call JSON.stringify against your data with the replacer if | ||
* defined to let you configure how the data is serialized. | ||
* | ||
* @example | ||
* type LoaderData = { user: { name: string } }; | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* type LoaderData = { user: { name: string, createdAt: Date } }; | ||
* | ||
* let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
* if (typeof value !== "Date") return value; | ||
* return { __type: "Date", value: value.toISOString() }; | ||
* } | ||
* | ||
* let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
* if (value.__type === "Date") return new Date(value.value); | ||
* return value; | ||
* } | ||
* | ||
* let validator: ValidatorFunction = data => { | ||
* return schema.parse(data) | ||
* } | ||
* | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let user = await getUser(request); | ||
* return json<LoaderData>({ user }); | ||
* return json<LoaderData>({ user }, { replacer }); | ||
* } | ||
* | ||
* export function Screen() { | ||
* let { user } = useLoaderData<LoaderData>(); | ||
* let { user } = useLoaderData<LoaderData>({ reviver, validator }); | ||
* return <UserProfile user={user} />; | ||
* } | ||
*/ | ||
export declare function json<Data extends JsonValue>(data: Data, init?: number | ResponseInit): Response; | ||
export declare function json<Data>(data: Data, init?: number | ExtendedResponseInit): Response; | ||
/** | ||
* Create a response receiving a JSON object with the status code 201. | ||
* @example | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let result = await doSomething(request); | ||
* return created(result); | ||
* } | ||
*/ | ||
export declare function created<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
* Create a new Response with a redirect set to the URL the user was before. | ||
@@ -52,3 +76,3 @@ * It uses the Referer header to detect the previous URL. It asks for a fallback | ||
*/ | ||
export declare function badRequest<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function badRequest<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -62,3 +86,3 @@ * Create a response receiving a JSON object with the status code 401. | ||
*/ | ||
export declare function unauthorized<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function unauthorized<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -72,3 +96,3 @@ * Create a response receiving a JSON object with the status code 403. | ||
*/ | ||
export declare function forbidden<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function forbidden<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -82,3 +106,3 @@ * Create a response receiving a JSON object with the status code 404. | ||
*/ | ||
export declare function notFound<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function notFound<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -92,3 +116,3 @@ * Create a response receiving a JSON object with the status code 422. | ||
*/ | ||
export declare function unprocessableEntity<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function unprocessableEntity<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -102,3 +126,3 @@ * Create a response receiving a JSON object with the status code 500. | ||
*/ | ||
export declare function serverError<Data = unknown>(data: Data, init?: Omit<ResponseInit, "status">): Response; | ||
export declare function serverError<Data = unknown>(data: Data, init?: Omit<ExtendedResponseInit, "status">): Response; | ||
/** | ||
@@ -105,0 +129,0 @@ * Create a response with only the status 304 and optional headers. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.image = exports.html = exports.pdf = exports.stylesheet = exports.javascript = exports.notModified = exports.serverError = exports.unprocessableEntity = exports.notFound = exports.forbidden = exports.unauthorized = exports.badRequest = exports.redirectBack = exports.json = void 0; | ||
exports.image = exports.html = exports.pdf = exports.stylesheet = exports.javascript = exports.notModified = exports.serverError = exports.unprocessableEntity = exports.notFound = exports.forbidden = exports.unauthorized = exports.badRequest = exports.redirectBack = exports.created = exports.json = void 0; | ||
const server_runtime_1 = require("@remix-run/server-runtime"); | ||
/** | ||
* @deprecated Use the `json` function from Remix directly. | ||
* | ||
* @description | ||
* A wrapper of the `json` function from `remix` which accepts a generic for the | ||
* data to be serialized. This allows you to use the same type for `json` and | ||
* on `useLoaderData` to ensure the type is always in sync. | ||
* A wrapper of the `json` function from `remix` which lets you pass a replacer | ||
* function in the second argument. | ||
* | ||
* The type must extend the JsonValue from type-fest, this means only JSON | ||
* compatible types are allowed inside the data which will help you avoid trying | ||
* to send functions or class instances. | ||
* This helper, will call JSON.stringify against your data with the replacer if | ||
* defined to let you configure how the data is serialized. | ||
* | ||
* @example | ||
* type LoaderData = { user: { name: string } }; | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* type LoaderData = { user: { name: string, createdAt: Date } }; | ||
* | ||
* let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
* if (typeof value !== "Date") return value; | ||
* return { __type: "Date", value: value.toISOString() }; | ||
* } | ||
* | ||
* let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
* if (value.__type === "Date") return new Date(value.value); | ||
* return value; | ||
* } | ||
* | ||
* let validator: ValidatorFunction = data => { | ||
* return schema.parse(data) | ||
* } | ||
* | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let user = await getUser(request); | ||
* return json<LoaderData>({ user }); | ||
* return json<LoaderData>({ user }, { replacer }); | ||
* } | ||
* | ||
* export function Screen() { | ||
* let { user } = useLoaderData<LoaderData>(); | ||
* let { user } = useLoaderData<LoaderData>({ reviver, validator }); | ||
* return <UserProfile user={user} />; | ||
@@ -29,6 +41,25 @@ * } | ||
function json(data, init) { | ||
return server_runtime_1.json(data, init); | ||
if (typeof init === "number") { | ||
return (0, server_runtime_1.json)(JSON.stringify(data), init); | ||
} | ||
if (typeof init === "undefined") { | ||
return (0, server_runtime_1.json)(JSON.stringify(data)); | ||
} | ||
let { replacer, ...rest } = init; | ||
return (0, server_runtime_1.json)(JSON.stringify(data, replacer), rest); | ||
} | ||
exports.json = json; | ||
/** | ||
* Create a response receiving a JSON object with the status code 201. | ||
* @example | ||
* export let action: ActionFunction = async ({ request }) => { | ||
* let result = await doSomething(request); | ||
* return created(result); | ||
* } | ||
*/ | ||
function created(data, init) { | ||
return json(data, { ...init, status: 201 }); | ||
} | ||
exports.created = created; | ||
/** | ||
* Create a new Response with a redirect set to the URL the user was before. | ||
@@ -49,3 +80,3 @@ * It uses the Referer header to detect the previous URL. It asks for a fallback | ||
var _a; | ||
return server_runtime_1.redirect((_a = request.headers.get("Referer")) !== null && _a !== void 0 ? _a : fallback, init); | ||
return (0, server_runtime_1.redirect)((_a = request.headers.get("Referer")) !== null && _a !== void 0 ? _a : fallback, init); | ||
} | ||
@@ -52,0 +83,0 @@ exports.redirectBack = redirectBack; |
{ | ||
"name": "remix-utils", | ||
"version": "2.11.1", | ||
"version": "3.0.0", | ||
"license": "MIT", | ||
@@ -56,3 +56,2 @@ "engines": { | ||
"@remix-run/server-runtime": "^1.1.1", | ||
"@size-limit/preset-small-lib": "^4.10.2", | ||
"@testing-library/jest-dom": "^5.15.0", | ||
@@ -65,3 +64,3 @@ "@testing-library/react": "^12.1.2", | ||
"babel-jest": "^27.3.1", | ||
"eslint": "^7.26.0", | ||
"eslint": "^8.12.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
@@ -71,11 +70,11 @@ "eslint-import-resolver-typescript": "^2.5.0", | ||
"eslint-plugin-import": "^2.22.1", | ||
"eslint-plugin-jest": "^24.3.6", | ||
"eslint-plugin-jest-dom": "^3.9.0", | ||
"eslint-plugin-jest": "^26.1.3", | ||
"eslint-plugin-jest-dom": "^4.0.1", | ||
"eslint-plugin-jsx-a11y": "^6.4.1", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"eslint-plugin-promise": "^5.1.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-react": "^7.26.1", | ||
"eslint-plugin-react-hooks": "^4.2.0", | ||
"eslint-plugin-testing-library": "^4.3.0", | ||
"eslint-plugin-unicorn": "^32.0.1", | ||
"eslint-plugin-testing-library": "^5.1.0", | ||
"eslint-plugin-unicorn": "^41.0.1", | ||
"jest": "^27.3.1", | ||
@@ -82,0 +81,0 @@ "jest-fetch-mock": "^3.0.3", |
287
README.md
@@ -442,41 +442,100 @@ # Remix Utils | ||
### Outlet & useParentData | ||
### useActionData | ||
> This feature is now built-in into Remix as `Outlet` & `useOutletContext` so it's marked as deprecated here. The feature will be removed in v3 of Remix Utils. | ||
Wrapper of the useActionData from Remix. It lets you pass a reviver function to convert values from the stringified JSON to any JS object. | ||
This wrapper of the Remix Outlet component lets you pass an optional `data` prop, then using the `useParentData` hook, you can access that data. | ||
It also lets you pass a validator function to ensure the final value has the correct shape and get the type of the data correctly. | ||
Helpful to pass information from parent to child routes, for example, the authenticated user data. | ||
```ts | ||
type ActionData = { user: { name: string; createdAt: Date } }; | ||
```tsx | ||
// parent route | ||
import { Outlet } from "remix-utils"; | ||
let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
if (typeof value !== "Date") return value; | ||
return { __type: "Date", value: value.toISOString() }; | ||
}; | ||
export default function Parent() { | ||
return <Outlet data={{ something: "here" }} />; | ||
let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
if (value.__type === "Date") return new Date(value.value); | ||
return value; | ||
}; | ||
let validator: ValidatorFunction = (data) => { | ||
return schema.parse(data); | ||
}; | ||
export let action: ActionFunction = async ({ request }) => { | ||
let user = await createUser(request); | ||
return created<ActionData>({ user }, { replacer }); | ||
}; | ||
export function Screen() { | ||
let { user } = useActionData<ActionData>({ reviver, validator }); | ||
return <UserProfile user={user} />; | ||
} | ||
``` | ||
```tsx | ||
// child route | ||
import { useParentData } from "remix-utils"; | ||
### useLoaderData | ||
export default function Child() { | ||
const data = useParentData(); | ||
return <div>{data.something}</div>; | ||
Wrapper of the useLoaderData from Remix. It lets you pass a reviver function to convert values from the stringified JSON to any JS object. | ||
It also lets you pass a validator function to ensure the final value has the correct shape and get the type of the data correctly. | ||
```ts | ||
type LoaderData = { user: { name: string; createdAt: Date } }; | ||
let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
if (typeof value !== "Date") return value; | ||
return { __type: "Date", value: value.toISOString() }; | ||
}; | ||
let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
if (value.__type === "Date") return new Date(value.value); | ||
return value; | ||
}; | ||
let validator: ValidatorFunction = (data) => { | ||
return schema.parse(data); | ||
}; | ||
export let loader: LoaderFunction = async ({ request }) => { | ||
let user = await getUser(request); | ||
return json<LoaderData>({ user }, { replacer }); | ||
}; | ||
export function Screen() { | ||
let { user } = useLoaderData<LoaderData>({ reviver, validator }); | ||
return <UserProfile user={user} />; | ||
} | ||
``` | ||
### RevalidateLink | ||
### useDataRefresh | ||
The RevalidateLink link component is a simple wrapper of Remix's Link component. It receives the same props with the exception of the `to` prop; instead, this component will render a Link to `.`. | ||
This hook lets you trigger a refresh of the loaders in the current URL. | ||
By linking to `.`, when clicked, this will tell Remix to fetch again the loaders of the current routes, but instead of creating a new entry on the browser's history stack, it will replace the current one. Basically, it will refresh the page, but only reloading the data. | ||
The way this works is by sending a fetcher.submit to `/dev/null` to trigger all loaders to run.. | ||
If you don't have JS enabled, this will do a full page refresh instead, giving you the exact same behavior. | ||
This Hook is mostly useful if you want to trigger the refresh manually from an effect, examples of this are: | ||
```tsx | ||
<RevalidateLink className="refresh-btn-styles">Refresh</RevalidateLink> | ||
- Set an interval to trigger the refresh | ||
- Refresh when the browser tab is focused again | ||
- Refresh when the user is online again | ||
```ts | ||
import { useDataRefresh } from "remix-utils"; | ||
function useDataRefreshOnInterval() { | ||
let { refresh } = useDataRefresh(); | ||
useEffect(() => { | ||
let interval = setInterval(refresh, 5000); | ||
return () => clearInterval(interval); | ||
}, [refresh]); | ||
} | ||
``` | ||
The return value of `useDataRefresh` is an object with the following keys: | ||
- refresh: a function that trigger the refresh | ||
- type: a string which can be `init`, `refreshRedirect` or `refresh` | ||
- status: a string which can be `loading` or `idle` | ||
### useHydrated | ||
@@ -531,28 +590,2 @@ | ||
### useRevalidate | ||
This hook lets you trigger a revalidation of the loaders in the current routes. | ||
The way this works is by navigating to `.` and adding `replace: true` to avoid creating a new entry on the history stack. | ||
> Check #RevalidateLink for more information and a component version of this feature that works without JS. | ||
This Hook is mostly useful if you want to trigger the revalidation manually from an effect, examples of this are: | ||
- Set an interval to trigger the revalidation | ||
- Revalidate when the browser tab is focused again | ||
- Revalidate when the user is online again | ||
```ts | ||
import { useRevalidate } from "remix-utils"; | ||
function useRevalidateOnInterval() { | ||
let revalidate = useRevalidate(); | ||
useEffect(() => { | ||
let interval = setInterval(revalidate, 5000); | ||
return () => clearInterval(interval); | ||
}, [revalidate]); | ||
} | ||
``` | ||
### useRouteData | ||
@@ -562,3 +595,3 @@ | ||
To use it, call `useRouteData` in your component and pass the route path as a string. As an example, if you had the following routes: | ||
To use it, call `useRouteData` in your component and pass the route ID as a string. As an example, if you had the following routes: | ||
@@ -571,30 +604,15 @@ ``` | ||
Then you need to pass `useRouteData("/articles")` to get the data of `routes/articles.tsx` and `useRouteData("/articles/")` to get the data of `routes/articles/index.tsx`. | ||
Then you need to pass `useRouteData("routes/articles")` to get the data of `routes/articles.tsx`, `useRouteData("routes/articles/index")` to get the data of `routes/articles/index.tsx` and `routes/articles/$slug` to get the data of `routes/articles/$slug.tsx`. | ||
```ts | ||
let parentData = useRouteData("/articles"); | ||
let indexData = useRouteData("/articles/"); | ||
``` | ||
As you can see, the ID is the route file without the extension. | ||
If the pathname is dynamic as with the `routes/articles/$slug.tsx` route, you can attach and ID to the route using the `handle` export, and then use that ID in the `useRouteData` hook. | ||
```ts | ||
// inside routes/articles/$slug.tsx | ||
export let handle = { id: "article-show" }; // the ID can be anything | ||
let parentData = useRouteData("routes/articles"); | ||
let indexData = useRouteData("routes/articles/index"); | ||
``` | ||
```tsx | ||
// inside any other route | ||
import { useRouteData } from "remix-utils"; | ||
export default function Screen() { | ||
let data = useRouteData("article-show"); | ||
// use data here | ||
} | ||
``` | ||
The `useRouteData` hook receives a generic to be used as the type of the route data. Because the route may not be found the return type is `Data | undefined`. This means if you do the following: | ||
```ts | ||
let data = useRouteData<ArticleShowData>("article-show"); | ||
let data = useRouteData<ArticleShowData>("routes/articles"); | ||
``` | ||
@@ -661,78 +679,2 @@ | ||
### Body Parser | ||
These utilities let you parse the request body with a simple function call. You can parse it to a string, a URLSearchParams instance, or an object. | ||
#### toString | ||
This function receives the whole request and returns a promise with the body as a string. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils"; | ||
import type { ActionFunction } from "remix"; | ||
import { updateUser } from "../services/users"; | ||
export let action: ActionFunction = async ({ request }) => { | ||
let body = await bodyParser.toString(request); | ||
body = new URLSearchParams(body); | ||
await updateUser(params.id, { username: body.get("username") }); | ||
return redirectBack(request, { fallback: "/" }); | ||
}; | ||
``` | ||
#### toSearchParams | ||
> A better version of this feature is supported out of the box by Remix. It's recommended to use `await request.formData()` instead. | ||
This function receives the whole request and returns a promise with an instance of `URLSearchParams`, and the request's body is already parsed. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils"; | ||
import type { ActionFunction } from "remix"; | ||
import { updateUser } from "../services/users"; | ||
export let action: ActionFunction = async ({ request, params }) => { | ||
const body = await bodyParser.toSearchParams(request); | ||
await updateUser(params.id, { username: body.get("username") }); | ||
return redirectBack(request, { fallback: "/" }); | ||
}; | ||
``` | ||
This is the same as doing: | ||
```ts | ||
let body = await bodyParser.toString(request); | ||
return new URLSearchParams(body); | ||
``` | ||
#### toJSON | ||
This function receives the whole request and returns a promise with an unknown value. That value is going to be the body of the request. | ||
The result is typed as `unknown` to force you to validate the object to ensure it's what you expect. This is because there's no way for TypeScript to know what the type of the body is since it's an entirely dynamic value. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils"; | ||
import type { ActionFunction } from "remix"; | ||
import { hasUsername } from "../validations/users"; | ||
import { updateUser } from "~/services/users"; | ||
export let action: ActionFunction = async ({ request }) => { | ||
const body = await bodyParser.toJSON(request); | ||
hasUsername(body); // this should throw if body doesn't have username | ||
// from this point you can do `body.username` | ||
await updateUser(params.id, { username: body.username }); | ||
return redirectBack(request, { fallback: "/" }); | ||
}; | ||
``` | ||
This is the same as doing: | ||
```ts | ||
let body = await bodyParser.toSearchParams(request); | ||
return Object.fromEntries(params.entries()) as unknown; | ||
``` | ||
### getClientIPAddress | ||
@@ -823,18 +765,15 @@ | ||
#### Typed JSON | ||
#### json | ||
> This feature is now built-in into Remix so it's marked as deprecated here. The feature will be removed in v3 of Remix Utils. | ||
This function works together with useLoaderData. The function receives any value and returns a response with the value as JSON. | ||
This function is a typed version of the `json` helper provided by Remix. It accepts a generic with the type of data you are going to send in the response. | ||
The difference with the built-in `json` function in Remix is that this one lets you pass a replacer function which will be passed to JSON.stringify to let you control how your values are transformed to string. | ||
This helps ensure that the data you are sending from your loader matches the provided type at the compiler lever. It's more useful when you create an interface or type for your loader so you can share it between `json` and `useLoaderData` to help you avoid missing or extra parameters in the response. | ||
This is useful to support sending BigInt, Date, Error, or any custom class value which is not directly supported on the JSON format. | ||
Again, this is not doing any kind of validation on the data you send. It's just a type checker. | ||
The generic extends JsonValue from [type-fest](https://github.com/sindresorhus/type-fest/blob/ff96fef37b84137e1600eebce108c2b797427c1f/source/basic.d.ts#L45), this limit the type of data you can send to anything that can be serializable, so you are not going to be able to send BigInt, functions, Symbols, etc. If `JSON.stringify` fails to try to stringify that value, it will not be supported. | ||
```tsx | ||
import { useLoaderData } from "remix"; | ||
// ensure you import both json and useLoaderData from Remix Utils | ||
import { json, useLoaderData } from "remix-utils"; | ||
import type { ReplacerFunction, ReviverFunction } from "remix-utils"; | ||
import type { LoaderFunction } from "remix"; | ||
import { json } from "remix-utils"; | ||
@@ -844,17 +783,27 @@ import { getUser } from "../services/users"; | ||
interface LoaderData { | ||
user: User; | ||
} | ||
type LoaderData = { user: User }; | ||
let replacer: ReplacerFunction = (key: string, value: unknown) => { | ||
if (typeof value !== "Date") return value; | ||
return { __type: "Date", value: value.toISOString() }; | ||
}; | ||
let reviver: ReviverFunction = (key: string, value: unknown) => { | ||
if (value.__type === "Date") return new Date(value.value); | ||
return value; | ||
}; | ||
export let loader: LoaderFunction = async ({ request }) => { | ||
const user = await getUser(request); | ||
return json<LoaderData>({ user }); | ||
let user = await getUser(request); | ||
return json<LoaderData>({ user }, { replacer }); | ||
}; | ||
export default function View() { | ||
const { user } = useLoaderData<LoaderData>(); | ||
return <h1>Hello, {user.name}</h1>; | ||
export function Screen() { | ||
let { user } = useLoaderData<LoaderData>({ reviver }); | ||
return <UserProfile user={user} />; | ||
} | ||
``` | ||
> Note: All helpers below use this json function, ensure you always import useLoaderData from Remix Utils | ||
#### Redirect Back | ||
@@ -877,2 +826,16 @@ | ||
#### Created | ||
Helper function to create a Created (201) response with a JSON body. | ||
```ts | ||
import { created } from "remix-utils"; | ||
import type { ActionFunction } from "remix"; | ||
export let action: ActionFunction = async ({ request }) => { | ||
let result = await doSomething(request); | ||
return created(result); | ||
}; | ||
``` | ||
#### Bad Request | ||
@@ -879,0 +842,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
180509
36
3992
106
979