{"id":22612260,"url":"https://github.com/rcrdk/utils","last_synced_at":"2026-04-04T08:43:42.579Z","repository":{"id":265261055,"uuid":"853314847","full_name":"rcrdk/utils","owner":"rcrdk","description":"Day by day helpers including code snippets, scripts and anything else that could be useful 😅","archived":false,"fork":false,"pushed_at":"2025-02-12T19:18:37.000Z","size":19,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-12T20:26:51.871Z","etag":null,"topics":["functions","helpers","lint","nextjs","packages","react","shell","tailwindcss","typescript","utils","vscode"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rcrdk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-06T12:15:24.000Z","updated_at":"2025-02-12T19:18:41.000Z","dependencies_parsed_at":"2025-02-12T20:24:13.464Z","dependency_job_id":"2e26f8d3-256a-4fd9-979c-f8842056dcdf","html_url":"https://github.com/rcrdk/utils","commit_stats":null,"previous_names":["rcrdk/utils"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrdk%2Futils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrdk%2Futils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrdk%2Futils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrdk%2Futils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcrdk","download_url":"https://codeload.github.com/rcrdk/utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246117690,"owners_count":20726068,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functions","helpers","lint","nextjs","packages","react","shell","tailwindcss","typescript","utils","vscode"],"created_at":"2024-12-08T17:11:45.063Z","updated_at":"2025-12-30T20:31:31.288Z","avatar_url":"https://github.com/rcrdk.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛟 Utils\nI created this repository to put together all utils and helpers that I need from time to time:\n\n---\n### 1️⃣ Functions\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eGroupBy\u003c/summary\u003e\n   \n  ---\n  [`group-by.ts`](https://github.com/rcrdk/utils/blob/main/functions/group-by.ts)\n\n  Groups an array of objects by a specific key. From time to time I need to use the newly or not so `Object.groupBy(items, callbackFn)`, and the problem was that this method is only supported in [newly versions of Node (21.x)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy#browser_compatibility). I faced this issue after deploying an app at Vercel where the Node version at the time was the 20.x as the newest available or when I was coding a React Native App.\n\n  **Usage:**\n  ```typescript\n  const data = [\n    { id: 1, category: 'A' },\n    { id: 2, category: 'B' },\n    { id: 3, category: 'A' },\n  ];\n\n  groupBy(data, 'category')\n\n  // Result:\n  // {\n  //   A: [\n  //     { id: 1, category: 'A' },\n  //     { id: 3, category: 'A' },\n  //   ],\n  //   B: [\n  //     { id: 2, category: 'B' },\n  //   ],\n  // }\n   ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eTailwind ClassName Merge\u003c/summary\u003e\n\n  ---\n  [`tw-cn-merge.ts`](https://github.com/rcrdk/utils/blob/main/functions/tw-cn-merge.ts)\n\n  Combines class names into a single string, handling Tailwind CSS class conflicts.\n\n  **Dependencies:**\n  ```JSX\n  npm i clsx tailwind-merge\n  ```\n\n  **Usage:**\n  ```JSX\n    \u003cdiv className={cn('some-classes', 'more classes')} /\u003e\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003ePrice Formatted to PT-BR\u003c/summary\u003e\n   \n  ---\n  [`price-formatted-ptbr.ts`](https://github.com/rcrdk/price-formatted-ptbr.ts/copy-to-clipboard.ts)\n\n  Formats a numeric amount as a currency string in Brazilian Real (BRL).\n\n  **Usage:**\n  ```js\n  priceFormatted(10000); // '100,00'\n  priceFormatted(123456, 100); // '1.234,56'\n  priceFormatted(5000, 1); // '5.000,00'\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eGenerate Slug\u003c/summary\u003e\n   \n  ---\n  [`generate-slug.ts`](https://github.com/rcrdk/utils/blob/main/functions/generate-slug.ts)\n\n  Generates a URL-friendly slug from a given string.\n\n  **Usage:**\n  ```js\n  generateSlug('Hello World!'); // 'hello-world'\n  generateSlug('Café au lait!'); // 'cafe-au-lait'\n  generateSlug('   Multiple   Spaces   '); // 'multiple-spaces'\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eIs Query Included?\u003c/summary\u003e\n   \n  ---\n  [`is-query-included.ts`](https://github.com/rcrdk/utils/blob/main/is-query-included.ts.ts)\n\n  Checks if the query string is included in the stringToCompare after normalization.\n\n  **Example:**\n  ```js\n  const query1 = 'café';\n  const stringToCompare1 = 'O café está ótimo';\n\n  const result1 = isQueryIncluded(query1, stringToCompare1);\n  console.log(result1); // true\n  ```\n\n  **Example:**\n  ```js\n  const query2 = 'hello';\n  const stringToCompare2 = '   HeLLo   World!';\n\n  const result2 = isQueryIncluded(query2, stringToCompare2);\n  console.log(result2); // true\n  ```\n\n  **Example:**\n  ```js\n  const query3 = 'ça';\n  const stringToCompare3 = 'O café está aqui';\n\n  const result3 = isQueryIncluded(query3, stringToCompare3);\n  console.log(result3); // true\n  ```\n\n  **Example:**\n  ```js\n  const query4 = 'world';\n  const stringToCompare4 = 'Goodbye, everyone!';\n\n  const result4 = isQueryIncluded(query4, stringToCompare4);\n  console.log(result4); // false\n  ```\n\u003c/details\u003e\n\n---\n### 2️⃣ React\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eComponent with 'as' prop\u003c/summary\u003e\n\n  ---\n  [`component-as-prop.tsx`](https://github.com/rcrdk/utils/blob/main/react/component-as-prop.tsx)\n\n  Create custom components with `as` prop with TypeScript.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003euseFormState hook\u003c/summary\u003e\n\n  ---\n  [`use-form-state.ts`](https://github.com/rcrdk/utils/blob/main/react/use-form-state/use-form-state.ts)\n\n  A custom React hook for managing form state, inspired by React DOM’s useFormState.\n\n  - Handles form submissions with async actions\n  - Manages success, error, and loading states\n  - Supports automatic form resets\n  - Supports clearing form state message after a delay, useful for toasts.\n  - Allows custom success callbacks\n\n  **foo-form.tsx**\n  ```js\n  'use client'\n\n  export function FooForm() {\n    const [{ success, error, message }, handleSubmit, isSubmitting] = useFormState(\n      createPageAction,\n      {\n        onSuccess() {},\n        resetFormOnSuccess: false,\n        resetStateMessage: true,\n      },\n    )\n\n    useEffect(() =\u003e {\n      if (!success \u0026\u0026 message) {\n        toast.error(message, { id: 'foo-action' })\n      }\n      if (success \u0026\u0026 message) {\n        toast.success(message, { id: 'foo-action' })\n      }\n    }, [success, message, isSubmitting])\n\n    return (\n      \u003cform onSubmit={handleSubmit}\u003e\n        \u003c!-- ... --\u003e\n      \u003c/form\u003e      \n    )\n  }\n  ```\n\n  **foo-action.ts:**\n  ```js\n  'use server'\n\n  import { z } from 'zod'\n\n  const createFooSchema = z.object({\n    name: z.string().min(1, 'Enter a name.'),\n  })\n\n  export async function createFooAction(data: FormData) {\n    const result = createFooSchema.safeParse(Object.fromEntries(data))\n\n    if (!result.success) {\n      const errors = result.error.flatten().fieldErrors\n\n      return {\n        success: false,\n        message: null,\n        errors,\n      }\n    }\n\n    const { name } = result.data\n    \n    try {\n      // action\n    } catch (error) {\n      return {\n        success: false,\n        message: 'An unexpected error occured.',\n        errors: null,\n      }\n    }\n\n    return {\n      success: true,\n      message: 'Action successfully executed.',\n      errors: null,\n    }\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eValidated actions\u003c/summary\u003e\n\n  ---\n  [`validated-actions.tsx`](https://github.com/rcrdk/utils/blob/main/react/validated-actionstsx)\n\n  Validate actions with user session.\n\u003c/details\u003e\n\n\n---\n### 3️⃣ Shell\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eConvert an entire folder of images to WEBP format\u003c/summary\u003e\n  \n  ---\n  [`convert-image-directory-to-webp.sh`](https://github.com/rcrdk/utils/blob/main/shell/convert-image-directory-to-webp.sh)\n  \n  I was having trouble and wasting my time by converting file by file in command line. This script could help you too:  \n\n  **Requirements:**\n  - This script works on Unix based systems such as MacOS and Linux.\n  - You'll need `cwebp` command available.\n\n  **Steps:**\n  1. Download `webp-convert-directory.sh`;\n  2. Open terminal;\n  3. Access folder containing images;\n  4. Run `sh [file_path]`. You can drag and drop file to terminal to get full path:\n     - Example: `sh /Users/ricardo/Desktop/webp-convert-directory.sh`\n  5. And voilà! All files converted!\n\u003c/details\u003e\n\n\n---\n### 4️⃣ TypeScript\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptional properties on types utility\u003c/summary\u003e\n\n  ---\n  [`optional.ts`](https://github.com/rcrdk/utils/blob/main/typescript/optional.ts)\n\n  Make some properties optional on type\n\n  ```typescript\n  type Post {\n    id: string;\n    name: string;\n    category: string;\n  }\n   \n  Optional\u003cPost, 'id' | 'category'\u003e\n  ```\n\u003c/details\u003e\n\n---\n### 5️⃣ Linting\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eReact Native + Expo\u003c/summary\u003e\n\n  ---\n  [`react-native-expo/*`](https://github.com/rcrdk/utils/tree/main/linting/react-native-expo)\n\n  Read the [Expo Docs](https://docs.expo.dev/guides/using-eslint/) on using ESlint and Prettier.\n\n  **Extra Dependencies:**\n  ```shell\n  npm i -D eslint-plugin-simple-import-sort\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eReact 19 + Next.js 15 + ESlint 9\u003c/summary\u003e\n\n  ---\n  [`react19-nextjs15-eslint9`](https://github.com/rcrdk/utils/tree/main/linting/react19-nextjs15-eslint9)\n\n  Here is a basic setup with the latest versions of React, Next.js and ESlint.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003ePrettier Sort imports and exports\u003c/summary\u003e\n   \n  ---\n  That's a great plugin for who always keep reordering and formatting imports and exports. Check it out the [plugin repository](https://github.com/IanVS/prettier-plugin-sort-imports).\n\n  **Depependencies:**\n  ```shell\n    pnpm add -D @ianvs/prettier-plugin-sort-imports\n  ```\n   **Setup:**\n  ```json\n  {\n    \"plugins\": [\"@ianvs/prettier-plugin-sort-imports\", \"prettier-plugin-sort-json\"],\n    \"importOrder\": [\n      \"^(react/(.*)$)|^(react$)\",\n      \"^(next/(.*)$)|^(next$)\",\n      \"\u003cTHIRD_PARTY_MODULES\u003e\",\n      \"\",\n      \"^@/(.*)$\",\n      \"^[./]\"\n    ],\n    \"importOrderParserPlugins\": [\"typescript\", \"jsx\", \"decorators-legacy\"]\n  }\n  ```\n\n  \u003e [!NOTE]  \n  \u003e The below plugin was the first wan that I've been using, but recently I discovered a better one.\n\n  ---\n  That's a great plugin for who always keep reordering and formatting imports and exports. Check it out the [plugin repository](https://github.com/lydell/eslint-plugin-simple-import-sort).\n\n  **Depependencies:**\n  ```shell\n  npm i -D eslint-plugin-simple-import-sort\n  ```\n\n  **Setup:**\n  ```json\n  {\n    \"plugins\": [\"simple-import-sort\"],\n    \"rules\": {\n      \"simple-import-sort/imports\": \"error\",\n      \"simple-import-sort/exports\": \"error\"\n    }\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eTailwind Prettier Plugin\u003c/summary\u003e\n   \n  ---\n  Checkout the [Plugin Docs](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).\n\n  **Dependencies:**\n  ```shell\n  npm i -D prettier-plugin-tailwindcss\n  ```\n\n  **Setup:**\n  ```json\n  // .prettierrc\n  {\n    \"plugins\": [\"prettier-plugin-tailwindcss\"]\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eRocketseat Eslint Initial Configs\u003c/summary\u003e\n   \n  ---\n  **Dependencies:**\n  ```shell\n  npm i -D @rocketseat/eslint-config\n  ```\n\n  **Usage:**\n  ```json\n  // [environment]: node, react, next\n  {\n    \"extends\": [\"@rocketseat/eslint-config/[environment]\"]\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eAdd type imports\u003c/summary\u003e\n   \n  ---\n  **Configuration:**\n  ```json\n  // .eslintrc.js\n  {\n    \"rules\": {\n      \"@typescript-eslint/consistent-type-imports\": [\n        \"error\",\n        {\n          \"prefer\": \"type-imports\"\n        }\n      ]\n    }\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eKnip\u003c/summary\u003e\n   \n  ---\n  Copy [`knip.config.ts`](https://github.com/rcrdk/utils/tree/main/linting/knip/knip.config.ts) to the base of the project, customize it.\n\n  **Dependencies:**\n  ```shell\n  npm i -D knip\n  ```\n\n  **Usage:**\n  ```shell\n  npx run knip # And it's time for a cleanup!\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCommit linting\u003c/summary\u003e\n   \n  ---\n  Copy [`commit-lint`](https://github.com/rcrdk/utils/tree/main/linting/commit-lint) to the base of the project, install dependencies and start commiting.\n\u003c/details\u003e\n\n\n---\n### 6️⃣ VScode\n---\n\n\u003cdetails\u003e\n  \u003csummary\u003eSettings\u003c/summary\u003e\n   \n  ---\n  `.vscode/settings.json`: settings for specific projects.\n\n  **Force to use import aliases:**\n  ```json\n  {\n\t  \"typescript.preferences.importModuleSpecifier\": \"non-relative\"\n  }\n  ```\n\n  **Setup Tailwind IntelliSense for custom props:**\n  ```json\n  {\n\t  \"tailwindCSS.classAttributes\": [\"class\", \"className\", \"classNameCenter\", \".*Styles\"]\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eEditorConfig IDE Plugin\u003c/summary\u003e\n   \n  ---\n  EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. Check it out the [tool page](https://editorconfig.org). Just need to install a plugin on code editor and set a `.editorconfig` file:\n\n  **Configuration file example:**\n  ```bash\n  # EditorConfig is awesome: https://EditorConfig.org\n  # top-most EditorConfig file\n  root = true\n\n  [*]\n  indent_style = tab\n  indent_size = 2\n  end_of_line = lf\n  charset = utf-8\n  trim_trailing_whitespace = false\n  insert_final_newline = false\n\n  [*.md]\n  indent_style = space\n  indent_size = 2\n  end_of_line = lf\n  charset = utf-8\n  trim_trailing_whitespace = false\n  insert_final_newline = false\n  ```\n\u003c/details\u003e\n\n\n---\n### 7️⃣ Packages\n---\n\n- [**React Haiku**](https://www.reacthaiku.dev/): a collection of react hooks and utilities\n- [**Day.js**](https://day.js.org/): Day.js is a minimalist JavaScript library that parses, validates, manipulates, and displays dates and times\n- [**Sonner**](https://sonner.emilkowal.ski/): An opinionated toast component for React. Emil and I share the same last name 🤠.\n- [**Swiper**](https://swiperjs.com/): This is the best slider for websites, web apps, and mobile apps.\n- [**T3 Env**](https://env.t3.gg/): Framework agnostic validation for type-safe environment variables.\n- [**Radix Primitives**](https://www.radix-ui.com/primitives): Unstyled, accessible, open source React primitives for high-quality web apps and design systems.\n- [**Sharp**](https://sharp.pixelplumbing.com/): High performance Node.js image processing.\n- [**Auth.js**](https://authjs.dev/): Authentication for the Web.\n- [**TailwindCSS**](https://tailwindcss.com/): A utility-first CSS framework.\n- [**Tablr Icons**](https://tabler.io/icons): A package of thousands icons.\n- [**Tanstack React Query**](https://tanstack.com/query/latest): This is a JavaScript library that helps developers fetch, cache, and update data in React applications\n- [**Fancyapps / Fancybox**](https://fancyapps.com/fancybox/): Fancybox is the ultimate JavaScript lightbox alternative that sets the standard for premium user experience in multimedia display. Supports images, videos, maps, inline content, iframes and any other HTML content.\n- [**KY**](https://github.com/sindresorhus/ky): Tiny \u0026 elegant JavaScript HTTP client based on the Fetch API. Supports experimental next.js cache.\n- [**useDraggableHook**](https://github.com/rfmiotto/react-use-draggable-scroll): scroll content using mouse events.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrdk%2Futils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcrdk%2Futils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrdk%2Futils/lists"}