{"version":"0.0.0-XhUgS5tz","license":"MIT","main":"dist/index.js","typings":"dist/index.d.ts","engines":{"node":">=10"},"scripts":{"start":"tsc --watch","prebuild":"rimraf dist","build":"tsc","prepare":"npm run build","test":"jest","test:watch":"jest --watchAll","test:co":"jest --coverage","get-command":"echo \"npm install @hilma/forms@$(npm pkg get version | sed \"s/\\\"//g\")\"","randomize-version":"npm pkg set version=\"0.0.0-$(openssl rand -base64 6)\" ","beta":"npm run randomize-version && npm publish --tag beta && npm run get-command"},"peerDependencies":{"react":">=16","react-dom":">=16"},"name":"@hilma/tools","author":{"name":"Yona Ben-Reuven"},"devDependencies":{"@swc/jest":"^0.2.26","@testing-library/react":"^14.0.0","@types/jest":"^29.5.1","@types/react":"^16.9.35","@types/react-dom":"^16.9.8","jest":"^29.5.0","jest-environment-jsdom":"^29.5.0","rimraf":"^5.0.1","ts-jest":"^29.1.0","typescript":"^4.0.5"},"dependencies":{"validate.js":"^0.13.1"},"readme":"# Tools\n\nTools is a package that has some useful client side tools.\n\n- [Tools](#tools)\n    - [Installation](#installation)\n    - [Usage](#usage)\n\t    - [`provide` and `wrap`](#provide-and-wrap)\n\t    - [`withContext`](#withcontext)\n\t    - [`createContextHook`](#createcontexthook)\n\t    - [`createMobXContext`](#createmobxcontext)\n\t    - [`useAsyncState`](#useasyncstate)\n\t    - [`useAsyncEffect`](#useasynceffect)\n        - [`useLocalStorage` and `useSessionStorage`](#uselocalstorage-and-usesessionstorage)\n\t    - [`isCapacitor`](#iscapacitor)\n\t    - [`getDisplayName`](#getdisplayname)\n    -  [API](#api)\n\t    - [`provide`](#provide)\n            - [`wrap`](#wrap)\n\t    - [`withContext`](#withcontext-1)\n\t    - [`createContextHook`](#createcontexthook-1)\n\t    - [`createMobXContext`](#createmobxcontext-1)\n\t    - [`useAsyncState`](#useasyncstate-1)\n\t    - [`useAsyncEffect`](#useasynceffect-1)\n        - [`useLocalStorage`](#uselocalstorage)\n        - [`useSessionStorage`](#usesessionstorage)\n\t    - [`isCapacitor`](#iscapacitor-1)\n\t    - [`getDisplayName`](#getdisplayname-1)\n\n## Installation\n\n``` bash\nnpm install @hilma/tools\n```\n\n## Usage\n\n### `provide` (and `wrap`)\n\n`provide` is a function that is used to eliminate nested providers in React code.\n\nOften we have a component with tons of wrappers or providers, where all we care about is the component logic itself, like this:\n\n```tsx\nimport React from 'react';\n\nconst App = () => {\n    return (\n        <AuthProvider>\n            <ThemeProvider>\n                <StylesProvider>\n                    <div className=\"App\">\n                        {/* .... */}\n                    </div>\n                </StylesProvider>\n            </ThemeProvider>\n        </AuthProvider>\n    );\n}\n\nexport default App;\n```\n\nNot even taking into account that `App` might want to use the context provided by `AuthProvider`, `ThemeProvider`, and the like, this code is already looking much more complicated than it should be. `provide` aims to simplify code like this.\n\n__With `provide`:__\n\n```tsx\nimport React from 'react';\nimport { provide } from '@hilma/tools';\n\nconst App = () => {\n    return (\n        <div className=\"App\">\n            {/* .... */}\n        </div>\n    );\n}\n\nexport default provide(AuthProvider, ThemeProvider, StylesProvider)(App);\n```\n\nThis allows us to access the context provided by `AuthProvider`, `ThemeProvider`, etc. from inside of `App`, and makes our code much simpler to read and understand.\n\nIf you want to pass props to a provider, you can use a tuple where the first item is the provider and the second is the props:\n\n```tsx\nimport React from 'react';\nimport { provide } from '@hilma/tools';\n\nconst App = () => {\n    return (\n        <div className=\"App\">\n            {/* .... */}\n        </div>\n    );\n}\n\nexport default provide(\n    [AuthProvider, { basename: \"/\" }],\n    ThemeProvider,\n    [StylesProvider, { style: \"dark\" }]\n)(App);\n```\n\n### `withContext`\n\n`withContext` is used to consume multiple contexts via props in some component.\n\n__Example__:\n\n```tsx\nimport React, { createContext } from 'react';\nimport { withContext } from '@hilma/tools';\n\nconst ThemeContext = createContext(\"black\");\nconst ApiContext = createContext(\"http://localhost:8080\"):\n\nconst MyComponent: React.FC<{ theme: string; api: string; }> = (props) => {\n\t// instead of doing\n\t// const theme = useContext(ThemeContext);\n\t// const api = useContext(ApiContext);\n\t// we can just get the context values from the props\n\tconst { theme, api } = props;\n\n\treturn (\n\t\t<div>\n\t\t\t{/* ... */}\n\t\t</div>\n\t);\n}\n\nconst mapContextToProps = {\n    theme: ThemeContext,\n    api: ApiContext\n}\n\nexport default withContext(mapContextToProps)(MyComponent);\n```\n\nOur component here takes a `theme` and `api` prop that correlate to the values provided by `ThemeContext` and `ApiContext`. We then define a `mapContextToProps` object that connects the contexts to the props.\n\n### `createContextHook`\n\nWe often can't populate our React contexts with values when we're creating them. A common solution to this problem is to define our context's type with `SomeType | null`, like this:\n\n```tsx\nconst UsernameContext = createContext<string | null>(null);\n```\n\nAnd then building our own custom `useMyContext` hook that can ignore this `null`, like so:\n\n```tsx\n// either\nconst useMyContext = () => useContext(UsernameContext)!;\n\n// or\nconst useMyContext = () => {\n\tconst value = useContext(UsernameContext);\n\tif (value === null) throw new Error(\"You called useUsername while not inside of the UsernameProvider\");\n\treturn value;\n}\n```\n\n`createContextHook` simplifies this process by automating the second approach. So the second version of `useMyContext` in the example above is the same as doing:\n\n```tsx\nimport { createContextHook } from '@hilma/tools';\n\nUsernameContext.displayName = \"Username\";\nconst useMyContext = createContextHook(UsernameContext);\n```\n\n### createMobXContext\n\n`createMobXContext` is a function that eliminates the boilerplate needed to use `mobx` with `React`. It works with `createContextHook`, and is based on the fact that when using `mobx`, we _do_ have a starting value (the store itself).\n\n```tsx\nimport { makeAutoObservable } from 'mobx';\n\nclass ThemeStore {\n    color = \"dark\";\n\n    constructor() {\n        makeAutoObservable(this);\n    }\n\n    setColor = color => {\n        this.color = color;\n    }\n}\n\nconst theme = new ThemeStore();\n\nexport const [ThemeContext, ThemeProvider, useTheme] = createMobXContext(theme);\n```\n\nHere we pass to  `createMobXContext` our store instance and get back a tuple with three items: \n1) A context for the `ThemeStore`\n2) A provider that we can wrap our application with\n3) A hook that uses that context\n\nLet's look at an example of using these items:\n\n```tsx\nimport { ThemeContext, ThemeProvider, useTheme } from './ThemeContext';\n\nconst App = () => (\n    <ThemeProvider>\n        <FuncComp />\n        <ClassComp />\n    </ThemeProvider>\n);\n\nexport default App;\n\nconst FuncComp = () => {\n    const theme = useTheme();\n    \n    return (\n        <div>{theme.color}</div>\n    );\n}\n\nclass ClassComp {\n    static contextType = ThemeContext;\n    \n    render() {\n        return (\n            <div>{this.context.color}</div>\n        );\n    }\n}\n```\n\n### useAsyncState\n\n`useAsyncState` is a hook based on `useState` but with some extra useful asynchronous functionality. It returns a tuple with three items: \n1) `state`,  the actual state, just like with `useState`\n2) `setState`, which sets the value of the state and returns a promise with the new state\n3) `getState`, which returns a promise of the state's current value no matter when. This is needed because the `state` variable is not always up to date with the latest state inside of a `useEffect`.\n\n```tsx\nimport { useAsyncState } from '@hilma/tools';\n\nconst Comp = () => {\n    const [color, setColor, getColor] = useAsyncState('black');\n    \n    const updateColorWithAwait = async (event) => {\n        const newColor = await setColor(event.target.value);\n        console.log(newColor);\n    }\n\n    const updateColorWithCallback = (event) => {\n        setColor(event.target.value, newColor => {\n            console.log(newColor);\n        });\n    }\n\n    const getColorAsync = async () => {\n        const color = await getColor();\n        console.log(color);\n    }\n    \n    return <div></div>;\n}\n```\n\n### useAsyncEffect\n\n`useAsyncEffect` is a hook based on the `useEffect` hook but with some extra useful asynchronous functionality. By default, `useEffect` doesn't accept an `async` function because the function returns a promise. It also doesn't accept an `async` cleanup function. The `useAsyncEffect`  allows you to do both:\n\n```tsx\nimport { useAsyncEffect } from '@hilma/tools';\n\nconst Comp = () => {\n\n    useAsyncEffect(async () => {\n        // stuff\n\n        return async () => {\n            // more stuff\n        }\n    }, []);\n\n    return <div></div>;\n}\n```\n\nYou don't have to call `useAsyncEffect` with an `async` function or with an `async` cleanup function.\n\n__Note:__ Because the behavior of the cleanup can be asynchronous, the cleanup might be called after the next effect (if you don't have an empty dependency array). You shouldn't rely too heavily on state changes and the like occuring due to cleanup functions returned from a `useAsyncEffect`.\n\n### `useLocalStorage` and `useSessionStorage`\n\nThese functions wrap `useState` and create a simple API for working with JSON data stored in either `localStorage` or `sessionStorage`.\n\n```tsx\nconst MyComponent = () => {\n    // the first parameter is the key to store the data in\n    // the second parameter is the value to default to, in case the data\n    // doesn't yet exist or is corrupted somehow\n    const [theme, setTheme] = useLocalStorage<\"dark\" | \"light\">(\"theme\", \"dark\");\n    const [canSendHeart, setCanSendHeart] = useSessionStorage(\"has-seen-popup\", true);\n\n    return (\n        <div className={theme}>\n            <button \n                onClick={() => {\n                    // this will update both the state (causing a re-render) and `localStorage`\n                    setTheme(\"light\");\n                }}\n            >\n                Change Theme\n            </button>\n\n            {canSendHeart && (\n                <button \n                    onClick={() => {\n                        // this will update both the state (causing a re-render) and `sessionStorage`\n                        setCanSendHeart(false);\n                    }}\n                >\n                    Send Heart\n                </button>\n            )}\n        </div>\n    );\n}\n```\n\n### isCapacitor\n\nReturns a boolean value. If `true`, the code is running in a `Capacitor` environment.\n\n```tsx\nimport { isCapacitor } from '@hilma/tools';\n\nconsole.log(isCapacitor());\n```\n\n### getDisplayName\n\nA function that accepts a `React` component and returns its name. (This is mainly for testing and better error messages for developers; don't rely on this function in production).\n\n```tsx\nimport { getDisplayName } from '@hilma/tools';\n\nconst Comp = () => {\n    return <div></div>;\n}\n\nconsole.log(getDisplayName(Comp)); // 'Comp'\n\nconst OtherComp = () => {\n\treturn <div></div>;\n}\n\nOtherComp.displayName = 'MyComponent';\n\nconsole.log(getDisplayName(OtherComp)); // 'MyComponent';\n```\n\n### useMemoOnce\n\n**DEPRECATED** - if you need a `useMemo` that only happens once, call `useMemo` with an empty dependency array. Regardless, you should note that `useMemo` is often an anti-pattern and you should try to avoid it when it isn't needed. Read more [here](https://blog.logrocket.com/when-not-to-use-usememo-react-hook/).\n\n`useMemoOnce` will be removed from `@hilma/tools` by version `1.0.0`.\n\n### useCallbackOnce\n\n**DEPRECATED** - if you need a `useCallback` that only happens once, call `useCallback` with an empty dependency array.\n\n`useCallbackOnce` will be removed from `@hilma/tools` by version `1.0.0`.\n\n### AsyncTools\n\n**DEPRECATED** - use a package like `axios` for simple, well-typed data fetching with error handling, and packages like `@tanstack/react-query` to handle the state of that data, along with caching.\n\n`AsyncTools` will be removed from `@hilma/tools` by version `1.0.0`.\n\n### ValidateFields\n\n**DEPRECATED** - use a package like `yup` or `zod`. Those packages are far more in-depth, provide a TypeScript-supporting interface, and interop much better with other libraries. In particular, `yup` is the recommended solution for validation using `@hilma/forms`.\n\n`ValidateFields` will be removed from `@hilma/tools` by version `1.0.0`.\n\n## API\n\n### `provide`\n\n```typescript\nexport function provide<TParents extends { [key: string]: any }[]>(\n\t...parents: Providers<TParents>\n): <TProps>(\n\tchild: React.ComponentType<TProps>\n) => React.ComponentType<TProps>;\n```\n\nThe `Providers` type maps an array of prop types to an array of either `ComponentType` or `[ComponentType, Props]`, depending on which props are required and whether the props include `children`.\n\n- If a provider does not take the `children` prop, it cannot be used within `parents`\n- If a provider doesn't take any required props, it can either be passed as `Component` or as `[Component, Props]`\n- If a provider takes any required props, it _must_ be passed as `[Component, Props]`\n\n### `wrap`\n\n```typescript\nexport function wrap<TParents extends { [key: string]: any }[]>(\n\t...parents: Provider<TParents>\n): {\n\t(element: Element) => Element;\n\t<TProps>(\n\t\tcomponent: React.ComponentType<TProps>, props: TProps\n\t) => Element;\n}\n```\n\nThe `Providers` type here is the same as for `provide`. See [above](#provide).\n\n### `withContext`\n\n```typescript\nexport function withContext<T extends {}>(\n\tmapContextToProps: MapContextToProps<T>\n): <TProps extends T>(\n\tchild: React.ComponentType<TProps>\n) => React.ComponentType<Omit<TProps, keyof T>>;\n```\n\nThe `MapContextToProps` type takes a generic type `T` (some basic props object) and returns a type with that type's value, mapped to context types.\n\n###  `createContextHook`\n\n```typescript\nexport function createContextHook<T>(\n\tcontext: React.Context<T | null>\n): () => T\n```\n\n### `createMobXContext`\n\n```typescript\nexport function createMobXContext<T extends { [key: string]: any }>(\n\tstoreInstance: T\n): [React.Context<T>, React.FC<{ children?: React.ReactNode }>, () => T]\n```\n\n###  `useAsyncState`\n\n```typescript\nexport function useAsyncState<T>(\n\tinitialState: T | (() => T)\n): [T, (value: T | ((prev: T) => T)) => Promise<T>, () => Promise<T>]\n```\n\n### `useAsyncEffect`\n\n```typescript\nexport function useAsyncEffect(\n\teffect: () => void | (() => void) | Promise<void | (() => void)>,\n\tdeps: React.DependencyList\n): void;\n```\n\n### `useLocalStorage`\n\n```typescript\nexport function useLocalStorage<T>(\n    key: string,\n    fallback: T\n): [T, (value: T | ((prev: T) => T)) => void];\n```\n\n### `useSessionStorage`\n\n```typescript\nexport function useSessionStorage<T>(\n    key: string,\n    fallback: T\n): [T, (value: T | ((prev: T) => T)) => void];\n```\n\n### `isCapacitor`\n\n```typescript\nexport function isCapacitor(): boolean\n```\n\n### `getDisplayName`\n\n```typescript\nexport function getDisplayName(\n\tcomponent: React.ComponentType<unknown>\n): string;\n```\n","readmeFilename":"README.md","gitHead":"662fdf7db2ce455a390082b0d4b9b83967f2fb02","description":"Tools is a package that has some useful client side tools.","_id":"@hilma/tools@0.0.0-XhUgS5tz","_nodeVersion":"18.16.0","_npmVersion":"9.5.1","dist":{"integrity":"sha512-RvO8etd6e8kDMWsoifgJQCVJtzQTbvR7n9HSa8XsBVeDzmvUCtcEWzjftsfmGYyFAr5BMa1BKlqQBK/zmWWp3w==","shasum":"db684e5bef98598de865758d9508bc5c46e5d522","tarball":"https://registry.npmjs.org/@hilma/tools/-/tools-0.0.0-XhUgS5tz.tgz","fileCount":91,"unpackedSize":126362,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBoSRG4Dofb58Uk7S87zD9YEA9l6q02gjKrTscNLUiP2AiAFvQQPANtX4Qb383L+LjC6bOxhavqxXz3pQOFjfgKfiQ=="}]},"_npmUser":{"name":"hilma","email":"carmel6000dev@gmail.com"},"directories":{},"maintainers":[{"name":"hilma","email":"carmel6000dev@gmail.com"},{"name":"elsasebagh","email":"elsa@carmel6000.amitnet.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/tools_0.0.0-XhUgS5tz_1687268887783_0.20525900104557415"},"_hasShrinkwrap":false}