├── .eslintrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── demo ├── .gitignore ├── README.md ├── index.html ├── package.json ├── src │ ├── App.tsx │ ├── index.tsx │ ├── pages │ │ ├── hooks │ │ │ ├── useCookie.tsx │ │ │ ├── useCounter.tsx │ │ │ ├── useEffect.tsx │ │ │ ├── useInterval.tsx │ │ │ ├── useLocalStorage.tsx │ │ │ ├── useSessionStorage.tsx │ │ │ ├── useTimeout.tsx │ │ │ ├── useTitle.tsx │ │ │ ├── useToggle.tsx │ │ │ └── useWindowSize.tsx │ │ └── index.tsx │ └── routes.ts ├── tsconfig.json └── vite.config.ts ├── lerna.json ├── package.json ├── packages └── solidjs-hooks │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ ├── hooks │ │ ├── index.ts │ │ ├── useBoolean.ts │ │ ├── useCookie.ts │ │ ├── useCounter.ts │ │ ├── useEffect.ts │ │ ├── useInterval.ts │ │ ├── useLocalStorage.ts │ │ ├── useNumber.ts │ │ ├── useSessionStorage.ts │ │ ├── useState.ts │ │ ├── useTimeout.ts │ │ ├── useTitle.ts │ │ ├── useToggle.ts │ │ └── useWindowSize.ts │ ├── index.ts │ ├── types │ │ └── index.ts │ └── utils │ │ ├── helpers.ts │ │ ├── index.ts │ │ └── internals.ts │ └── tsconfig.json └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": [ 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier", 7 | "prettier/@typescript-eslint", 8 | "plugin:prettier/recommended" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": 2018, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "@typescript-eslint/explicit-function-return-type": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | yarn.lock -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "trailingComma": "es5", 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present NukeJS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solidjs-hooks 2 | 3 | A collection of useful SolidJS hooks. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | # Using NPM 9 | npm install solidjs-hooks 10 | # Using Yarn 11 | yarn add solidjs-hooks 12 | ``` 13 | 14 | ## Usage 15 | 16 | Usage documentation can be found [here](/packages/solidjs-hooks). -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. 4 | 5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. 6 | 7 | ```bash 8 | $ npm install # or pnpm install or yarn install 9 | ``` 10 | 11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm dev` or `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ## Deployment 33 | 34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) 35 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-template-solid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "typescript": "^4.5.5", 14 | "vite": "^2.7.13", 15 | "vite-plugin-solid": "^2.2.5" 16 | }, 17 | "dependencies": { 18 | "solid-app-router": "^0.2.1", 19 | "solid-js": "^1.3.3", 20 | "solidjs-hooks": "^0.0.9" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useRoutes } from 'solid-app-router'; 3 | 4 | import routes from './routes'; 5 | 6 | const App: Component = () => { 7 | const Routes = useRoutes(routes); 8 | 9 | return ( 10 | <> 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | 3 | import { Router } from 'solid-app-router'; 4 | import App from './App'; 5 | 6 | render( 7 | () => ( 8 | 9 | 10 | 11 | ), 12 | document.getElementById('root') as HTMLElement 13 | ); 14 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useCookie.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import { useCookie } from 'solidjs-hooks'; 4 | 5 | const HookPage: Component = () => { 6 | const [cookie, setCookie, removeCookie] = useCookie('my-cookie'); 7 | const [count, setCount] = createSignal(0); 8 | 9 | const setCookieHandler = () => { 10 | setCount((prevCount) => prevCount + 1); 11 | setCookie(`my-awesome-cookie-${count()}`); 12 | }; 13 | 14 | return ( 15 |
16 |

Cookie value: {cookie()}

17 | 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default HookPage; 24 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useCounter.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useCounter } from 'solidjs-hooks'; 3 | 4 | const HookPage: Component = () => { 5 | const { count, increment, decrement, set, reset } = useCounter(); 6 | 7 | return ( 8 |
9 |

Current count: {count()}

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default HookPage; 22 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useEffect.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import { useEffect } from 'solidjs-hooks'; 4 | 5 | const HookPage: Component = () => { 6 | const [count, setCount] = createSignal(0); 7 | const [delay, setDelay] = createSignal(1000); 8 | 9 | useEffect(() => { 10 | const interval = setInterval(() => { 11 | setCount((prevCount) => prevCount + 1); 12 | }, delay()); 13 | 14 | return () => clearInterval(interval); 15 | }); 16 | 17 | return ( 18 |
19 |

Count is {count()}

20 | 23 | 26 |
27 | ); 28 | }; 29 | 30 | export default HookPage; 31 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useInterval.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import { useInterval } from 'solidjs-hooks'; 4 | 5 | const HookPage: Component = () => { 6 | const DEFAULT_DELAY = 1000; 7 | const [count, setCount] = createSignal(0); 8 | const [delay, setDelay] = createSignal(DEFAULT_DELAY); 9 | 10 | useInterval(() => { 11 | setCount((prevCount) => prevCount + 1); 12 | }, delay); 13 | 14 | const toggleInterval = () => { 15 | // @ts-ignore 16 | setDelay((prevDelay) => (prevDelay ? null : DEFAULT_DELAY)); 17 | }; 18 | 19 | return ( 20 |
21 |

Count: {count()}

22 |
23 | 24 |
25 |
26 | ); 27 | }; 28 | 29 | export default HookPage; 30 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useLocalStorage.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useLocalStorage } from 'solidjs-hooks'; 3 | 4 | const HookPage: Component = () => { 5 | const [value, setValue, remove] = useLocalStorage('my-key', 'foo'); 6 | 7 | return ( 8 |
9 |
Value: {value()}
10 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default HookPage; 18 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useSessionStorage.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useSessionStorage } from 'solidjs-hooks'; 3 | 4 | const HookPage: Component = () => { 5 | const [value, setValue, remove] = useSessionStorage('my-key', 'foo'); 6 | 7 | return ( 8 |
9 |
Value: {value()}
10 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default HookPage; 18 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useTimeout.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import { useTimeout } from 'solidjs-hooks'; 4 | 5 | const HookPage: Component = () => { 6 | const [count, setCount] = createSignal(0); 7 | const [delay, setDelay] = createSignal(1000); 8 | 9 | useTimeout(() => { 10 | setCount((prevCount) => prevCount + 1); 11 | }, delay); 12 | 13 | return ( 14 |
15 |

Count is {count()}

16 | 19 | 22 |
23 | ); 24 | }; 25 | 26 | export default HookPage; 27 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useTitle.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import { useTitle } from 'solidjs-hooks'; 4 | 5 | const HookPage: Component = () => { 6 | const [title, setTitle] = createSignal(document.title); 7 | 8 | // Also accepts a string instead of an Accessor. 9 | useTitle(title); 10 | 11 | return ( 12 |
13 |

Document title is: {title()}

14 |
15 | 16 | setTitle(e.currentTarget.value)} 20 | /> 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default HookPage; 27 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useToggle.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useToggle } from 'solidjs-hooks'; 3 | 4 | const HookPage: Component = () => { 5 | const [on, toggle] = useToggle(); 6 | 7 | return ( 8 |
9 |

{on() ? 'ON' : 'OFF'}

10 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default HookPage; 18 | -------------------------------------------------------------------------------- /demo/src/pages/hooks/useWindowSize.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { useWindowSize } from 'solidjs-hooks'; 3 | 4 | const HookPage: Component = () => { 5 | const { width, height } = useWindowSize(); 6 | 7 | return ( 8 |
9 |

Window width: {width()}

10 |

Window height: {height()}

11 |
12 | ); 13 | }; 14 | 15 | export default HookPage; 16 | -------------------------------------------------------------------------------- /demo/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | 3 | const IndexPage: Component = () => { 4 | return ( 5 | <> 6 |

This is the homepage

7 | 8 | ); 9 | }; 10 | 11 | export default IndexPage; 12 | -------------------------------------------------------------------------------- /demo/src/routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteDefinition } from 'solid-app-router'; 2 | import { lazy } from 'solid-js'; 3 | 4 | const routes: RouteDefinition[] = [ 5 | { 6 | path: '/', 7 | component: lazy(() => import('./pages/index')), 8 | }, 9 | 10 | /** 11 | * Hooks Pages 12 | */ 13 | { 14 | path: '/use-boolean', 15 | component: lazy(() => import('./pages/hooks/useToggle')), 16 | }, 17 | { 18 | path: '/use-cookie', 19 | component: lazy(() => import('./pages/hooks/useCookie')), 20 | }, 21 | { 22 | path: '/use-counter', 23 | component: lazy(() => import('./pages/hooks/useCounter')), 24 | }, 25 | { 26 | path: '/use-effect', 27 | component: lazy(() => import('./pages/hooks/useEffect')), 28 | }, 29 | { 30 | path: '/use-interval', 31 | component: lazy(() => import('./pages/hooks/useInterval')), 32 | }, 33 | { 34 | path: '/use-local-storage', 35 | component: lazy(() => import('./pages/hooks/useLocalStorage')), 36 | }, 37 | { 38 | path: '/use-session-storage', 39 | component: lazy(() => import('./pages/hooks/useSessionStorage')), 40 | }, 41 | { 42 | path: '/use-timeout', 43 | component: lazy(() => import('./pages/hooks/useTimeout')), 44 | }, 45 | { 46 | path: '/use-title', 47 | component: lazy(() => import('./pages/hooks/useTitle')), 48 | }, 49 | { 50 | path: '/use-toggle', 51 | component: lazy(() => import('./pages/hooks/useToggle')), 52 | }, 53 | { 54 | path: '/use-window-size', 55 | component: lazy(() => import('./pages/hooks/useWindowSize')), 56 | }, 57 | ]; 58 | 59 | export default routes; 60 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | build: { 7 | target: 'esnext', 8 | polyfillDynamicImport: false, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*", "demo/*"], 3 | "version": "independent", 4 | "npmClient": "yarn", 5 | "useWorkspaces": true 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidjs-hooks-workspace", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "demo/*" 7 | ], 8 | "devDependencies": { 9 | "@types/node": "^17.0.14", 10 | "@typescript-eslint/eslint-plugin": "^5.10.2", 11 | "@typescript-eslint/parser": "^5.10.2", 12 | "eslint": "^8.8.0", 13 | "eslint-config-prettier": "^8.3.0", 14 | "eslint-plugin-prettier": "^4.0.0", 15 | "lerna": "^4.0.0", 16 | "prettier": "^2.5.1", 17 | "typescript": "^4.5.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tsconfig.json -------------------------------------------------------------------------------- /packages/solidjs-hooks/README.md: -------------------------------------------------------------------------------- 1 | # solidjs-hooks 2 | 3 | A collection of useful SolidJS hooks. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | # Using NPM 9 | npm install solidjs-hooks 10 | # Using Yarn 11 | yarn add solidjs-hooks 12 | ``` 13 | 14 | ## Usage 15 | 16 | ### `useBoolean` 17 | 18 | SolidJS state hook that tracks the value of a boolean. 19 | 20 | This is an alias of [`useToggle`](#usetoggle). 21 | 22 | 23 | 24 | ### `useCookie` 25 | 26 | SolidJS hook that returns the current value of a cookie, a callback to set the cookie's value, and a callback to remove the cookie. 27 | 28 | #### Usage 29 | 30 | ```jsx 31 | import { createSignal } from 'solid-js'; 32 | import { useCookie } from 'solidjs-hooks'; 33 | 34 | const Demo = () => { 35 | const [cookie, setCookie, removeCookie] = useCookie('my-cookie'); 36 | const [count, setCount] = createSignal(0); 37 | 38 | const setCookieHandler = () => { 39 | setCount((prevCount) => prevCount + 1); 40 | setCookie(`my-awesome-cookie-${cookie()}`); 41 | } 42 | 43 | return ( 44 |
45 |

Cookie value: {cookie()}

46 | 47 | 48 |
49 | ) 50 | } 51 | ``` 52 | 53 | 54 | 55 | ### `useCounter` 56 | 57 | SolidJS state hook that tracks a numeric value. 58 | 59 | `useNumber` is an alias for `useCounter`. 60 | 61 | #### Usage 62 | 63 | ```jsx 64 | import { useCounter } from 'solidjs-hooks'; 65 | 66 | const Demo = () => { 67 | const { count, increment, decrement, set, reset } = useCounter(); 68 | 69 | return ( 70 |
71 |

Current count: {count()}

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | ) 81 | } 82 | ``` 83 | 84 | 85 | 86 | ### `useEffect` 87 | 88 | A React-like functionality for the `createEffect` hook that SolidJS provides. 89 | 90 | #### Usage 91 | 92 | ```jsx 93 | import { createSignal } from 'solid-js'; 94 | import { useEffect } from 'solidjs-hooks'; 95 | 96 | const Demo = () => { 97 | const [count, setCount] = createSignal(0); 98 | const [delay, setDelay] = createSignal(1000); 99 | 100 | useEffect(() => { 101 | const interval = setInterval(() => { 102 | setCount((prevCount) => prevCount + 1); 103 | }, delay()); 104 | 105 | return () => clearInterval(interval); 106 | }); 107 | 108 | return ( 109 |
110 |

Count is {count()}

111 | 114 | 117 |
118 | ) 119 | } 120 | ``` 121 | 122 | 123 | 124 | ### `useInterval` 125 | 126 | A declarative interval hook. The interval can be paused by setting the delay to `null`. 127 | 128 | #### Usage 129 | 130 | ```jsx 131 | import { createSignal } from 'solid-js'; 132 | import { useInterval } from 'solidjs-hooks'; 133 | 134 | const Demo = () => { 135 | const DEFAULT_DELAY = 1000; 136 | const [count, setCount] = createSignal(0); 137 | const [delay, setDelay] = createSignal(DEFAULT_DELAY); 138 | 139 | useInterval(() => { 140 | setCount((prevCount) => prevCount + 1); 141 | }, delay); 142 | 143 | const toggleInterval = () => { 144 | setDelay((prevDelay) => prevDelay ? null : DEFAULT_DELAY); 145 | } 146 | 147 | return ( 148 |
149 |

Count: {count()}

150 |
151 | 154 |
155 |
156 | ) 157 | } 158 | ``` 159 | 160 | 161 | 162 | ### `useLocalStorage` 163 | 164 | SolidJS side-effect hook that manages a single `localStorage` key. 165 | 166 | #### Usage 167 | 168 | ```jsx 169 | import { useLocalStorage } from 'solidjs-hooks'; 170 | 171 | const Demo = () => { 172 | const [value, setValue, remove] = useLocalStorage('my-key', 'foo'); 173 | 174 | return ( 175 |
176 |
Value: {value()}
177 | 178 | 179 | 180 |
181 | ) 182 | } 183 | ``` 184 | 185 | 186 | 187 | ### `useNumber` 188 | 189 | SolidJS state hook that tracks a numeric value. 190 | 191 | This is an alias of [`useCounter`](#usecounter). 192 | 193 | 194 | 195 | ### `useSessionStorage` 196 | 197 | SolidJS side-effect hook that manages a single `sessionStorage` key. 198 | 199 | #### Usage 200 | 201 | ```jsx 202 | import { useSessionStorage } from 'solidjs-hooks'; 203 | 204 | const Demo = () => { 205 | const [value, setValue, remove] = useSessionStorage('my-key', 'foo'); 206 | 207 | return ( 208 |
209 |
Value: {value()}
210 | 211 | 212 | 213 |
214 | ) 215 | } 216 | ``` 217 | 218 | 219 | 220 | ### `useState` 221 | 222 | This is an alias of the built-in `createSignal` hook in SolidJS. 223 | 224 | 225 | 226 | ### `useTimeout` 227 | 228 | A declarative timeout hook. When the `delay` changes, it will rerun. 229 | 230 | #### Usage 231 | 232 | ```jsx 233 | import { createSignal } from 'solid-js'; 234 | import { useTimeout } from 'solidjs-hooks'; 235 | 236 | const Demo = () => { 237 | const [count, setCount] = createSignal(0); 238 | const [delay, setDelay] = createSignal(1000); 239 | 240 | useTimeout(() => { 241 | setCount((prevCount) => prevCount + 1); 242 | }, delay); 243 | 244 | return ( 245 |
246 |

Count is {count()}

247 | 250 | 253 |
254 | ) 255 | } 256 | ``` 257 | 258 | 259 | 260 | ### `useTitle` 261 | 262 | SolidJS side-effect hook that sets the title of the page. 263 | 264 | #### Usage 265 | 266 | ```jsx 267 | import { createSignal } from 'solid-js'; 268 | import { useTitle } from 'solidjs-hooks'; 269 | 270 | const Demo = () => { 271 | const [title, setTitle] = createSignal(document.title); 272 | 273 | // Also accepts a string instead of an Accessor. 274 | useTitle(title); 275 | 276 | return ( 277 |
278 |

Document title is: {title()}

279 |
280 | 281 | setTitle(e.currentTarget.value)}/> 282 |
283 |
284 | ) 285 | } 286 | ``` 287 | 288 | 289 | 290 | ### `useToggle` 291 | 292 | SolidJS state hook that tracks the value of a boolean. 293 | 294 | `useBoolean` is an alias for `useToggle`. 295 | 296 | #### Usage 297 | 298 | ```jsx 299 | import { useToggle } from 'solidjs-hooks'; 300 | 301 | const Demo = () => { 302 | const [on, toggle] = useToggle(); 303 | 304 | return ( 305 |
306 |

{on() ? "ON" : "OFF"}

307 | 308 | 309 | 310 |
311 | ) 312 | } 313 | ``` 314 | 315 | 316 | 317 | ### `useWindowSize` 318 | 319 | SolidJS sensor hook that tracks dimensions of the browser window. 320 | 321 | #### Usage 322 | 323 | ```jsx 324 | import { useWindowSize } from 'solidjs-hooks'; 325 | 326 | const Demo = () => { 327 | const { width, height } = useWindowSize(); 328 | 329 | return ( 330 |
331 |

Window width: {width()}

332 |

Window height: {height()}

333 |
334 | ) 335 | } 336 | ``` -------------------------------------------------------------------------------- /packages/solidjs-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidjs-hooks", 3 | "description": "A collection of useful SolidJS hooks.", 4 | "private": false, 5 | "version": "1.1.2", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "pridepack clean && pridepack build" 9 | }, 10 | "author": { 11 | "name": "NukeJS", 12 | "url": "https://github.com/NukeJS" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/NukeJS/solidjs-hooks" 17 | }, 18 | "readme": "https://github.com/NukeJS/solidjs-hooks/tree/master/packages/solidjs-hooks#readme", 19 | "bugs": { 20 | "url": "https://github.com/NukeJS/solidjs-hooks/issues" 21 | }, 22 | "keywords": [ 23 | "solid-js", 24 | "hooks", 25 | "use" 26 | ], 27 | "types": "dist/types/index.d.ts", 28 | "main": "dist/cjs/production/index.js", 29 | "module": "dist/esm/production/index.js", 30 | "exports": { 31 | ".": { 32 | "development": { 33 | "require": "./dist/cjs/development/index.js", 34 | "import": "./dist/esm/development/index.js" 35 | }, 36 | "require": "./dist/cjs/production/index.js", 37 | "import": "./dist/esm/production/index.js", 38 | "types": "./dist/types/index.d.ts" 39 | }, 40 | "./dev": { 41 | "production": { 42 | "require": "./dist/cjs/production/index.js", 43 | "import": "./dist/esm/production/index.js" 44 | }, 45 | "require": "./dist/cjs/development/index.js", 46 | "import": "./dist/esm/development/index.js", 47 | "types": "./dist/types/index.d.ts" 48 | }, 49 | "./esm": { 50 | "development": "./dist/esm/development/index.js", 51 | "production": "./dist/esm/production/index.js", 52 | "default": "./dist/esm/production/index.js", 53 | "types": "./dist/types/index.d.ts" 54 | }, 55 | "./cjs": { 56 | "development": "./dist/cjs/development/index.js", 57 | "production": "./dist/cjs/production/index.js", 58 | "default": "./dist/cjs/production/index.js", 59 | "types": "./dist/types/index.d.ts" 60 | } 61 | }, 62 | "files": [ 63 | "dist" 64 | ], 65 | "devDependencies": { 66 | "@types/js-cookie": "^3.0.1", 67 | "@types/node": "^17.0.14", 68 | "pridepack": "1.0.3", 69 | "solid-js": "^1.3.5", 70 | "typescript": "^4.5.5" 71 | }, 72 | "dependencies": { 73 | "js-cookie": "^3.0.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useBoolean } from './useBoolean'; 2 | export { default as useCounter } from './useCounter'; 3 | export { default as useCookie } from './useCookie'; 4 | export { default as useEffect } from './useEffect'; 5 | export { default as useInterval } from './useInterval'; 6 | export { default as useLocalStorage } from './useLocalStorage'; 7 | export { default as useNumber } from './useNumber'; 8 | export { default as useSessionStorage } from './useSessionStorage'; 9 | export { default as useState } from './useState'; 10 | export { default as useTimeout } from './useTimeout'; 11 | export { default as useTitle } from './useTitle'; 12 | export { default as useToggle } from './useToggle'; 13 | export { default as useWindowSize } from './useWindowSize'; 14 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useBoolean.ts: -------------------------------------------------------------------------------- 1 | import useBoolean from './useToggle'; 2 | 3 | export default useBoolean; 4 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useCookie.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | import Cookies from 'js-cookie'; 4 | 5 | function useCookie( 6 | key: string 7 | ): [ 8 | Accessor, 9 | (newValue: string, options?: Cookies.CookieAttributes) => void, 10 | () => void 11 | ] { 12 | const [cookie, setCookie] = createSignal(Cookies.get(key) || null); 13 | 14 | const set = (newValue: string, options?: Cookies.CookieAttributes) => { 15 | Cookies.set(key, newValue, options); 16 | setCookie(newValue); 17 | }; 18 | 19 | const remove = () => { 20 | Cookies.remove(key); 21 | setCookie(null); 22 | }; 23 | 24 | return [cookie, set, remove]; 25 | } 26 | 27 | export default useCookie; 28 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useCounter.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | 4 | interface CounterActions { 5 | count: Accessor; 6 | increment: (amount?: number) => void; 7 | decrement: (amount?: number) => void; 8 | set: (amount: number) => void; 9 | reset: (amount?: number) => void; 10 | } 11 | 12 | function useCounter(initialValue: number = 0): CounterActions { 13 | const [count, setCount] = createSignal(initialValue); 14 | 15 | const increment = (amount: number = 1) => { 16 | if (typeof amount !== 'number') { 17 | throw new Error(`amount has to be a number, got "${typeof amount}".`); 18 | } 19 | 20 | setCount((prevCount) => prevCount + amount); 21 | }; 22 | 23 | const decrement = (amount: number = 1) => { 24 | if (typeof amount !== 'number') { 25 | throw new Error(`amount has to be a number, got "${typeof amount}".`); 26 | } 27 | 28 | setCount((prevCount) => prevCount - amount); 29 | }; 30 | 31 | const set = (amount: number) => { 32 | if (typeof amount !== 'number') { 33 | throw new Error(`amount has to be a number, got "${typeof amount}".`); 34 | } 35 | 36 | setCount(amount); 37 | }; 38 | 39 | const reset = (amount: number = initialValue) => { 40 | set(amount); 41 | }; 42 | 43 | return { 44 | count, 45 | increment, 46 | decrement, 47 | set, 48 | reset, 49 | }; 50 | } 51 | 52 | export default useCounter; 53 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useEffect.ts: -------------------------------------------------------------------------------- 1 | import { createEffect, onCleanup } from 'solid-js'; 2 | import { isDefined, isFunction } from '../utils'; 3 | 4 | function useEffect(callback: () => void | Function): void { 5 | createEffect(() => { 6 | if (isDefined(callback) && isFunction(callback)) { 7 | const cleanup = callback(); 8 | if (isFunction(cleanup)) { 9 | onCleanup(() => cleanup()); 10 | } 11 | } 12 | 13 | return; 14 | }); 15 | } 16 | 17 | export default useEffect; 18 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeAccessor } from '../types'; 2 | import { useEffect } from './'; 3 | import { access, isDefined } from '../utils'; 4 | 5 | function useInterval( 6 | callback: () => void, 7 | delay?: MaybeAccessor | null 8 | ): void { 9 | useEffect(() => { 10 | const _delay = access(delay); 11 | if (!isDefined(_delay) || typeof _delay !== 'number' || _delay < 0) return; 12 | 13 | const interval = setInterval(callback, _delay); 14 | 15 | return () => clearInterval(interval); 16 | }); 17 | } 18 | 19 | export default useInterval; 20 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createRenderEffect, createSignal } from 'solid-js'; 3 | 4 | interface LocalStorageOptions { 5 | serializer?: (value: T) => string; 6 | deserializer?: (value: string) => T; 7 | } 8 | 9 | function useLocalStorage( 10 | key: string, 11 | initialValue: T, 12 | options?: LocalStorageOptions 13 | ): [Accessor, (value: T) => void, () => void]; 14 | function useLocalStorage( 15 | key: string, 16 | initialValue?: T, 17 | options?: LocalStorageOptions 18 | ): [Accessor, (value: T) => void, () => void]; 19 | function useLocalStorage( 20 | key: string, 21 | initialValue?: T, 22 | options?: LocalStorageOptions 23 | ): [Accessor, (value: T) => void, () => void] { 24 | const serializer = (value: T) => { 25 | if (options?.serializer) { 26 | return options.serializer(value); 27 | } 28 | return JSON.stringify(value); 29 | }; 30 | 31 | const deserializer = (value: string) => { 32 | if (options?.deserializer) { 33 | return options.deserializer(value); 34 | } 35 | 36 | try { 37 | return JSON.parse(value); 38 | } catch { 39 | return value; 40 | } 41 | }; 42 | 43 | const getStoredValue = () => { 44 | try { 45 | const raw = localStorage.getItem(key); 46 | if (raw) { 47 | return deserializer(raw); 48 | } 49 | } catch (error) { 50 | console.error(error); 51 | } 52 | 53 | return initialValue; 54 | }; 55 | 56 | const [state, setState] = createSignal(getStoredValue()); 57 | 58 | const set = (value: T) => { 59 | try { 60 | localStorage.setItem(key, serializer(value)); 61 | setState(() => value); 62 | } catch (error) { 63 | console.error(error); 64 | } 65 | }; 66 | 67 | const remove = () => { 68 | try { 69 | localStorage.removeItem(key); 70 | setState(undefined); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }; 75 | 76 | createRenderEffect(() => { 77 | set(getStoredValue()); 78 | }); 79 | 80 | return [state, set, remove]; 81 | } 82 | 83 | export default useLocalStorage; 84 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useNumber.ts: -------------------------------------------------------------------------------- 1 | import useNumber from './useCounter'; 2 | 3 | export default useNumber; 4 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useSessionStorage.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createRenderEffect, createSignal } from 'solid-js'; 3 | 4 | interface SessionStorageOptions { 5 | serializer?: (value: T) => string; 6 | deserializer?: (value: string) => T; 7 | } 8 | 9 | function useSessionStorage( 10 | key: string, 11 | initialValue: T, 12 | options?: SessionStorageOptions 13 | ): [Accessor, (value: T) => void, () => void]; 14 | function useSessionStorage( 15 | key: string, 16 | initialValue?: T, 17 | options?: SessionStorageOptions 18 | ): [Accessor, (value: T) => void, () => void]; 19 | function useSessionStorage( 20 | key: string, 21 | initialValue?: T, 22 | options?: SessionStorageOptions 23 | ): [Accessor, (value: T) => void, () => void] { 24 | const serializer = (value: T) => { 25 | if (options?.serializer) { 26 | return options.serializer(value); 27 | } 28 | return JSON.stringify(value); 29 | }; 30 | 31 | const deserializer = (value: string) => { 32 | if (options?.deserializer) { 33 | return options.deserializer(value); 34 | } 35 | 36 | try { 37 | return JSON.parse(value); 38 | } catch { 39 | return value; 40 | } 41 | }; 42 | 43 | const getStoredValue = () => { 44 | try { 45 | const raw = sessionStorage.getItem(key); 46 | if (raw) { 47 | return deserializer(raw); 48 | } 49 | } catch (error) { 50 | console.error(error); 51 | } 52 | 53 | return initialValue; 54 | }; 55 | 56 | const [state, setState] = createSignal(getStoredValue()); 57 | 58 | const set = (value: T) => { 59 | try { 60 | sessionStorage.setItem(key, serializer(value)); 61 | setState(() => value); 62 | } catch (error) { 63 | console.error(error); 64 | } 65 | }; 66 | 67 | const remove = () => { 68 | try { 69 | sessionStorage.removeItem(key); 70 | setState(undefined); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }; 75 | 76 | createRenderEffect(() => { 77 | set(getStoredValue()); 78 | }); 79 | 80 | return [state, set, remove]; 81 | } 82 | 83 | export default useSessionStorage; 84 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useState.ts: -------------------------------------------------------------------------------- 1 | import { createSignal as useState } from 'solid-js'; 2 | 3 | export default useState; 4 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeAccessor } from '../types'; 2 | import { useEffect } from './'; 3 | import { access, isDefined } from '../utils'; 4 | 5 | function useTimeout( 6 | callback: () => void, 7 | delay?: MaybeAccessor | null 8 | ): void { 9 | useEffect(() => { 10 | const _delay = access(delay); 11 | if (!isDefined(_delay) || typeof _delay !== 'number' || _delay < 0) return; 12 | 13 | const timeout = setTimeout(callback, _delay); 14 | 15 | return () => clearTimeout(timeout); 16 | }); 17 | } 18 | 19 | export default useTimeout; 20 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useTitle.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeAccessor } from '../types'; 2 | import { createEffect, onCleanup } from 'solid-js'; 3 | import { isBrowser, access } from '../utils'; 4 | 5 | export interface TitleOptions { 6 | restoreOnCleanup?: boolean; 7 | } 8 | 9 | const DEFAULT_OPTIONS: TitleOptions = { 10 | restoreOnCleanup: false, 11 | }; 12 | 13 | function useTitle( 14 | title: MaybeAccessor, 15 | options: TitleOptions = DEFAULT_OPTIONS 16 | ): void { 17 | const _title = isBrowser ? document.title : ''; 18 | 19 | createEffect(() => { 20 | document.title = access(title); 21 | }); 22 | 23 | onCleanup(() => { 24 | if (options.restoreOnCleanup) { 25 | document.title = _title; 26 | } 27 | }); 28 | } 29 | 30 | export default useTitle; 31 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createSignal } from 'solid-js'; 3 | 4 | function useToggle( 5 | initialValue: boolean = false 6 | ): [Accessor, (newValue?: boolean) => void] { 7 | const [value, setValue] = createSignal(initialValue); 8 | 9 | const toggle = (newValue?: boolean) => { 10 | setValue((prevValue) => 11 | typeof newValue === 'boolean' ? newValue : !prevValue 12 | ); 13 | }; 14 | 15 | return [value, toggle]; 16 | } 17 | 18 | export default useToggle; 19 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | import { createSignal, createRenderEffect } from 'solid-js'; 3 | import { useEffect } from './'; 4 | 5 | function useWindowSize(): { 6 | width: Accessor; 7 | height: Accessor; 8 | } { 9 | const [width, setWidth] = createSignal(0); 10 | const [height, setHeight] = createSignal(0); 11 | 12 | const handler = () => { 13 | setWidth(window.innerWidth); 14 | setHeight(window.innerHeight); 15 | }; 16 | 17 | useEffect(() => { 18 | window.addEventListener('resize', handler); 19 | 20 | return () => window.removeEventListener('resize', handler); 21 | }); 22 | 23 | createRenderEffect(() => { 24 | handler(); 25 | }); 26 | 27 | return { width, height }; 28 | } 29 | 30 | export default useWindowSize; 31 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks'; 2 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { Accessor } from 'solid-js'; 2 | 3 | export type Fn = () => R; 4 | 5 | export type MaybeAccessor = T | Accessor; 6 | export type MaybeAccessorValue> = T extends Fn 7 | ? ReturnType 8 | : T; 9 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export function isDefined(value: T | undefined | null): value is T { 2 | return typeof value !== 'undefined' && value !== null; 3 | } 4 | 5 | export function isFunction(value: T | Function): value is Function { 6 | return typeof value === 'function'; 7 | } 8 | 9 | export const isBrowser = typeof window !== 'undefined'; 10 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './helpers'; 2 | export * from './internals'; 3 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/src/utils/internals.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeAccessor, MaybeAccessorValue } from '../types'; 2 | import { isFunction } from './'; 3 | 4 | export function access>( 5 | value: T 6 | ): MaybeAccessorValue { 7 | return isFunction(value) ? value() : value; 8 | } 9 | -------------------------------------------------------------------------------- /packages/solidjs-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["DOM"], 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "jsx": "react" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext"], 4 | "target": "ESNext", 5 | "module": "CommonJS", 6 | "strict": true, 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "resolveJsonModule": true, 11 | "downlevelIteration": true 12 | } 13 | } 14 | --------------------------------------------------------------------------------