├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── components └── Button │ ├── Button.docs.mdx │ ├── Button.stories.tsx │ ├── Button.tsx │ └── index.ts ├── gatsby-config.js ├── index.js ├── notes ├── apollo-graphql.md ├── array-methods.md ├── async-cheatsheet.md ├── async-load-css.md ├── autofill.md ├── awesome-web-dev-resources.md ├── axios.md ├── better-link-underlines.mdx ├── better-mobile-form-inputs.mdx ├── big-list-naughty-strings.md ├── button-as-link.mdx ├── change-default-text-editor-mac.md ├── check-if-js-array-empty.md ├── check-if-js-array.md ├── check-if-js-object-empty.md ├── circleci.md ├── clamp-number.md ├── cleanup-node-modules.md ├── clear-dns-cache.md ├── clickable-box.md ├── coding-standards.md ├── command-line-tools.md ├── commit-message-guidelines.md ├── contentful-migrations.md ├── create-local-self-signed-certs.md ├── crypto.mdx ├── css-aspect-ratio.md ├── css-cursors.mdx ├── css-env-vars.md ├── css-filters.md ├── css-font-stacks.md ├── css-grid.md ├── css-logical-properties.md ├── css-media-queries.md ├── css-object-styles.md ├── css-protips.md ├── css-rainbow-links.mdx ├── css-scroll-behaviour.md ├── css-scroll-margin-top.md ├── css-style-siblings-hover.md ├── cutting-the-mustard.md ├── deno.md ├── destructuring.md ├── docker.md ├── es6-module-syntax.md ├── es6-templating.md ├── express-middleware-testing.md ├── favicons.md ├── fetch.md ├── file-size-of-files-in-directory.md ├── firebase.md ├── fish-shell.md ├── fixed-width-numbers.md ├── fluid-typography.md ├── font-face.md ├── formik.md ├── full-bleed-utility.mdx ├── fullscreen.md ├── gatsby.md ├── get-selected-text.md ├── git-branching.md ├── git.md ├── github-actions.md ├── google-fonts.md ├── graphql.md ├── gsap.md ├── hexadecimal-transparency.md ├── http-methods.md ├── ignoring-code.md ├── immer.md ├── inflect.md ├── intersection-observer.md ├── ios-safari-height-bug.md ├── is-mobile-check.md ├── jest-mocks.md ├── jest.md ├── js-body-class.md ├── js-classes.md ├── keycode.md ├── lobotomised-owl-selector.mdx ├── long-words-links.md ├── mac-dark-mode.md ├── make-directory-if-none-exists.md ├── mix-blend-mode.md ├── mock-react-router-storybook.md ├── native-image-lazy-loading.md ├── nextjs.mdx ├── nock.md ├── npm-script-another-directory.md ├── oembed.md ├── opengraph-metacrap.md ├── performance-tips.md ├── picture.md ├── plop.md ├── postman.md ├── pure-esm-package.md ├── quotation-marks.md ├── react-client-only.md ├── react-compound-components.mdx ├── react-conditionally-render-client-server.md ├── react-errorboundary.md ├── react-query-with-graphql.md ├── react-query.md ├── react-ssr.md ├── react-typescript.md ├── react-usecontext.md ├── react-usereducer.md ├── react-useref.md ├── readme.md ├── redux-concepts.md ├── redux-saga.md ├── redux.md ├── remove-array-duplicates.md ├── remove-items-from-arrays.md ├── render-if-role.md ├── save-scroll-position.md ├── security.md ├── showing-hiding-content.md ├── sql.md ├── stimulus-cheatsheet.md ├── storybook.md ├── structured-data.md ├── styled-components.md ├── switch-true.md ├── testing-library.md ├── theme-ui-button.mdx ├── theme-ui.md ├── typescript-catch.md ├── typescript.md ├── url.md ├── useToggle.md ├── useful-js-functions.md ├── vercel-serverless-functions.md ├── video.md ├── vim-cheatsheet.md ├── vitest.md ├── webmentions.md ├── webshare.md └── yalc.md ├── package.json ├── static └── logo.png ├── vercel.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | public 3 | node_modules 4 | yalc.lock 5 | .yalc 6 | 7 | .vercel 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "all", 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#f7cae2", 4 | "activityBar.activeBorder": "#64a619", 5 | "activityBar.background": "#f7cae2", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#64a619", 9 | "activityBarBadge.foreground": "#15202b", 10 | "statusBar.background": "#f09eca", 11 | "statusBar.foreground": "#15202b", 12 | "statusBarItem.hoverBackground": "#e972b2", 13 | "titleBar.activeBackground": "#f09eca", 14 | "titleBar.activeForeground": "#15202b", 15 | "titleBar.inactiveBackground": "#f09eca99", 16 | "titleBar.inactiveForeground": "#15202b99" 17 | }, 18 | "peacock.color": "#f09eca" 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | notes.zander.wtf 5 |

6 | 7 | > Notes on code. My memory bank. 8 | 9 |
10 | 11 | This site uses [gatsby-theme-code-notes](https://github.com/mrmartineau/gatsby-theme-code-notes). 12 | 13 | --- 14 | 15 | ## License 16 | 17 | [MIT](https://choosealicense.com/licenses/mit/) © [Zander Martineau](https://zander.wtf) 18 | 19 | > Made by [Zander ⚡](https://github.com/mrmartineau/) 20 | -------------------------------------------------------------------------------- /components/Button/Button.docs.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs' 2 | import { Button } from './Button' 3 | 4 | # Button 5 | 6 | This button component extends Theme UI's `Button` and add some more props/functionality. It takes all standard ` 16 | 17 | 18 | 19 | ``` 20 | 21 | ## With loading state 22 | 23 | ```tsx 24 | 25 | ``` 26 | 27 | ## Inclusive/accessible disabled state 28 | 29 | Using the guidelines from https://css-tricks.com/making-disabled-buttons-more-inclusive/, this button will show a simple tooltip if the form state is not ready. Instead of passing `disabled=true` to the button, use the `isDisabled` & `disabledText` props. 30 | 31 | ```tsx 32 | 38 | ``` 39 | 40 | ## Icon buttons 41 | 42 | If you need an icon button, do not use this `Button` component. Instead, use the `IconButton` component from `theme-ui`, like so: 43 | 44 | ```tsx 45 | import { IconButton } from 'theme-ui' 46 | import { IconPicker } from '../components' 47 | ; 48 | 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /components/Button/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Story, Meta } from '@storybook/react/types-6-0' 3 | import { Button, ButtonProps } from './Button' 4 | import docs from './Button.docs.mdx' 5 | import { Box } from 'theme-ui' 6 | 7 | export default { 8 | title: 'Components/Button', 9 | component: Button, 10 | parameters: { 11 | docs: { page: docs }, 12 | }, 13 | argTypes: { onClick: { action: 'clicked' } }, 14 | decorators: [ 15 | (Story) => ( 16 | 17 | 18 | 19 | ), 20 | ], 21 | } as Meta 22 | 23 | const Template: Story = (args) => 137 | } 138 | ``` 139 | -------------------------------------------------------------------------------- /notes/nock.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nock 3 | tags: 4 | - testing 5 | - javascript 6 | emoji: 🧪 7 | link: 'https://github.com/nock/nock' 8 | created: 2020-02-27T23:02:00.000Z 9 | modified: 2020-05-11T11:47:16.000Z 10 | --- 11 | 12 | ```js 13 | const nock = require('nock') 14 | 15 | const scope = nock('https://api.github.com') 16 | .get('/repos/atom/atom/license') 17 | .reply(200, { 18 | license: { 19 | key: 'mit', 20 | name: 'MIT License', 21 | spdx_id: 'MIT', 22 | url: 'https://api.github.com/licenses/mit', 23 | node_id: 'MDc6TGljZW5zZTEz', 24 | }, 25 | }) 26 | ``` 27 | -------------------------------------------------------------------------------- /notes/npm-script-another-directory.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Run npm script from another directory 3 | created: 2022-07-19T16:26:06.000Z 4 | modified: 2022-07-19T16:26:06.000Z 5 | --- 6 | 7 | ## npm 8 | 9 | To run an npm script from another directory, use --prefix: 10 | 11 | ```sh 12 | npm --prefix run 13 | ``` 14 | 15 | ## yarn 16 | 17 | To run a yarn script from another directory, use --cwd: 18 | 19 | ```sh 20 | yarn --cwd 21 | ``` 22 | 23 | ## Example 24 | 25 | If you have a `package.json` in the `example` directory: 26 | 27 | ```json 28 | { 29 | "scripts": { 30 | "start": "echo hello world" 31 | } 32 | } 33 | ``` 34 | 35 | In the following directory: 36 | 37 | ``` 38 | . 39 | ├─ example 40 | │ ├─ package.json 41 | ├─ package.json 42 | ``` 43 | 44 | 1 directory, 1 file 45 | 46 | Then to run script `start` from your working directory: 47 | 48 | ```sh 49 | npm --prefix ./example run start 50 | 51 | yarn --cwd ./example start 52 | ``` 53 | 54 | ```json 55 | { 56 | "scripts": { 57 | "start": "npm --prefix ./example run start" 58 | } 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /notes/oembed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: oEmbed 3 | tags: 4 | - javascript 5 | - html 6 | link: 'https://oembed.com/' 7 | created: 2020-03-03T09:46:27.000Z 8 | modified: 2020-06-30T21:18:03.000Z 9 | --- 10 | 11 | Default providers list: https://oembed.com/providers.json 12 | 13 | Example provider scheme: 14 | 15 | ```json 16 | { 17 | "provider_name": "Twitter", 18 | "provider_url": "http:\/\/www.twitter.com\/", 19 | "endpoints": [ 20 | { 21 | "schemes": [ 22 | "https:\/\/twitter.com\/*\/status\/*", 23 | "https:\/\/*.twitter.com\/*\/status\/*" 24 | ], 25 | "url": "https:\/\/publish.twitter.com\/oembed" 26 | } 27 | ] 28 | }, 29 | ``` 30 | 31 | Example oEmbed link for Twitter 32 | 33 | ``` 34 | https://publish.twitter.com/oembed?url=https://twitter.com/Interior/status/463440424141459456 35 | ``` 36 | -------------------------------------------------------------------------------- /notes/opengraph-metacrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Opengraph metacrap 3 | tags: 4 | - html 5 | created: 2020-02-27T23:02:00.000Z 6 | modified: 2020-05-11T11:31:08.000Z 7 | --- 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | [Everything you ever wanted to know about unfurling but were afraid to ask /or/ How to make your site previews look amazing in Slack](https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254) 45 | 46 | Facebook: https://developers.facebook.com/docs/sharing/webmasters 47 | 48 | https://sproutsocial.com/insights/social-media-image-sizes-guide/ 49 | 50 | ## [Metatags.io](https://metatags.io/) 51 | 52 | Metatags.io is a very useful service to validate your metatags but also to generate the code needed for them too. 53 | -------------------------------------------------------------------------------- /notes/performance-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Performance tips 3 | emoji: 🏎 4 | created: 2021-01-15 5 | modified: 2021-01-15 6 | --- 7 | 8 | ## Reduce Cumulative Layout Shift 9 | 10 | Reduce Cumulative Layout Shift by always showing the scrollbar 11 | 12 | ```css 13 | html { 14 | overflow-y: scroll; 15 | } 16 | ``` 17 | 18 | https://twitter.com/TimVereecke/status/1239454788116533248 19 | 20 | ## Performance API 21 | 22 | ```js 23 | performance.mark('start') 24 | // some code we want to measure 25 | performance.mark('end') 26 | ``` 27 | 28 | ## Benchmarking code using node 29 | 30 | ```sh 31 | # assume there's an `add` function that is executed in benchmark.js 32 | 33 | node --trace-opt benchmark.js | grep add 34 | node --trace-opt --trace-deopt benchmark.js | grep add 35 | 36 | # add %NeverOptimizeFuntion(add) 37 | node --allow-natives-syntax benchmark.js 38 | ``` 39 | -------------------------------------------------------------------------------- /notes/picture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Picture 3 | tags: 4 | - html 5 | emoji: 🖼 6 | created: 2020-02-27T23:02:00.000Z 7 | modified: 2020-03-26T23:06:06.000Z 8 | --- 9 | 10 | ## Webp + png usage 11 | 12 | ```jsx 13 | 14 | 18 | 22 | Free Cash! 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /notes/postman.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Postman.io 3 | tags: 4 | - javascript 5 | emoji: 📨 6 | created: 2020-02-27T23:02:00.000Z 7 | modified: 2020-03-26T23:06:06.000Z 8 | --- 9 | 10 | ## Save an env var 11 | 12 | ```js 13 | var jsonData = JSON.parse(responseBody) 14 | pm.environment.set('ref', jsonData.refs[0].ref) 15 | ``` 16 | -------------------------------------------------------------------------------- /notes/quotation-marks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quotation marks in different countries 3 | emoji: 🗣 4 | created: 2020-10-08T16:37:57.000Z 5 | modified: 2021-11-11T12:06:43.046Z 6 | tags: 7 | - css 8 | --- 9 | 10 | - Albanian: `«…»` `‹…›` 11 | - English: `“…”` `‘…’` 12 | - Arab: `«…»` `‹…›` 13 | - African: `„…”` `‚…’` 14 | - Belorussian: `«…»` `„…“` 15 | - Bulgarian: `„…“` `‚…‘` 16 | - Hungarian: `„…”` 17 | - Greek: `«…»` `‹…›` 18 | - Danish: `»…«` `›…‹` 19 | - Hebrew: `«…»` / `'…'` `'…'` / `<<…>>` 20 | - Irish: `“…”` `‘…’` 21 | - Icelandic: `„…“` `‚…‘` 22 | - Spanish: `«…»` `“…”` 23 | - Italian: `«…»` 24 | - Chinese: `“…”` `‘…’` 25 | - Latvian: `„…“` `„…“` 26 | - Lithuanian: `„…“` `‚…‘` 27 | - Dutch: `„…”` `‚…’` 28 | - German: `„…“` `‚…‘` 29 | - Norwegian: `«…»` 30 | - Polish: `„…”` `«…»` 31 | - Portuguese: `“…”` `‘…’` 32 | - Romanian: `„…”` `«…»` 33 | - Russian: `«…»` `„…“` 34 | - Serbian: `„…“` `‚…‘` 35 | - Slovak: `„…“` `‚…‘` 36 | - Slovenian: `„…“` `‚…‘` 37 | - Turkish: `“…”` `‘…’` 38 | - Ukrainian: `«…»` `„…“` 39 | - Finnish: `”…”` `’…’` 40 | - French: `« …` `» ‹ … ›` 41 | - Croatian: `»…«` `›…‹` 42 | - Czech: `„…“` `‚…‘` 43 | - Swedish: `”…”` `’…’` 44 | - Estonian: `„…”` `„…”` 45 | - Japanese: `「…」` `『…』` 46 | 47 | ## Different kinds 48 | 49 | - `“ ”`: English double 50 | - `‘ ’`: English Single 51 | - `« »`: French «Christmas trees» 52 | - `„ “`: German „paws“ 53 | - `„ ”`: Polish 54 | - `» «`: Swedish reverse 55 | - `" "`: Double universal 56 | 57 | ## Example CSS 58 | 59 | ```css 60 | blockquote { 61 | background: #f9f9f9; 62 | border-left: 10px solid #ccc; 63 | margin: 1.5em 10px; 64 | padding: 0.5em 10px; 65 | quotes: '“' '”' '‘' '’'; 66 | } 67 | blockquote::before { 68 | color: #ccc; 69 | content: open-quote; 70 | font-size: 4em; 71 | line-height: 0.1em; 72 | margin-right: 0.25em; 73 | vertical-align: -0.4em; 74 | } 75 | blockquote p { 76 | display: inline; 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /notes/react-client-only.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Client-only React 3 | tags: 4 | - react 5 | emoji: 🎣 6 | link: 'https://joshwcomeau.com/react/the-perils-of-rehydration/' 7 | created: 2020-06-11T14:47:15.000Z 8 | modified: 2020-06-11T14:47:15.000Z 9 | --- 10 | 11 | ## A client-only wrapper component 12 | 13 | ```jsx 14 | export const ClientOnly = ({ children, ...delegated }) => { 15 | const [hasMounted, setHasMounted] = React.useState(false) 16 | React.useEffect(() => { 17 | setHasMounted(true) 18 | }, []) 19 | if (!hasMounted) { 20 | return null 21 | } 22 | return
{children}
23 | } 24 | ``` 25 | 26 | Use like so: 27 | 28 | ```jsx 29 | 30 | 31 | 32 | ``` 33 | 34 | or you could use a hook: 35 | 36 | ```jsx 37 | export const useHasMounted = () => { 38 | const [hasMounted, setHasMounted] = React.useState(false) 39 | React.useEffect(() => { 40 | setHasMounted(true) 41 | }, []) 42 | return hasMounted 43 | } 44 | ``` 45 | 46 | Which could be used like so: 47 | 48 | ```jsx 49 | function Navigation() { 50 | const hasMounted = useHasMounted() 51 | if (!hasMounted) { 52 | return null 53 | } 54 | 55 | const user = getUser() 56 | 57 | if (user) { 58 | return 59 | } 60 | 61 | return ( 62 | 65 | ) 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /notes/react-compound-components.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: React compound components 3 | tags: 4 | - react 5 | - js 6 | link: https://jjenzz.com/compound-components 7 | created: 2021-04-20T23:24:38.094Z 8 | modified: 2021-04-22T17:50:06.559Z 9 | --- 10 | 11 | ```tsx 12 | import React, { useContext } from 'react' 13 | 14 | const Context = React.createContext() 15 | 16 | const List = ({ isSmall = false, children, ...props }) => ( 17 |
    18 | {children} 19 |
20 | ) 21 | 22 | const ListItem = ({ children, ...props }) => { 23 | const isSmall = useContext(Context) 24 | 25 | return ( 26 |
  • 27 | {children} 28 |
  • 29 | ) 30 | } 31 | 32 | export { List, ListItem } 33 | ``` 34 | 35 | ```tsx 36 | 37 | Cat 38 | Dog 39 | 40 | ``` 41 | 42 | Another example: https://egghead.io/lessons/react-write-compound-components 43 | -------------------------------------------------------------------------------- /notes/react-conditionally-render-client-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Conditionally render on the client or server 3 | tags: 4 | - react 5 | emoji: ⚛ 6 | created: 2022-08-17T09:08:55.000Z 7 | modified: 2022-08-17T09:08:55.000Z 8 | --- 9 | 10 | ```tsx 11 | import React, { useEffect, useState, ReactNode } from 'react' 12 | 13 | export interface ConditionallyRenderProps { 14 | client?: boolean 15 | server?: boolean 16 | children?: ReactNode 17 | } 18 | 19 | const ConditionallyRender = ({ 20 | client, 21 | server, 22 | children, 23 | }: ConditionallyRenderProps) => { 24 | const [isMounted, setIsMounted] = useState(false) 25 | 26 | useEffect(() => setIsMounted(true), []) 27 | 28 | if (!isMounted && client) { 29 | return null 30 | } 31 | 32 | if (isMounted && server) { 33 | return null 34 | } 35 | 36 | return children as React.ReactElement 37 | } 38 | 39 | export default ConditionallyRender 40 | ``` 41 | 42 | ## Usage 43 | 44 | ```tsx 45 | 46 | 47 |

    This is rendered only on server.

    48 |
    49 | 50 |

    This is rendered only on client.

    51 |
    52 |
    53 | ``` 54 | -------------------------------------------------------------------------------- /notes/react-errorboundary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Error Boundary 3 | link: https://reactjs.org/docs/error-boundaries.html 4 | tags: 5 | - react 6 | emoji: ⚛ 7 | created: 2021-11-11T12:22:44.100Z 8 | modified: 2021-11-11T12:24:43.372Z 9 | --- 10 | 11 | From the React docs on [Error boundaries](https://reactjs.org/docs/error-boundaries.html): 12 | 13 | > Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. 14 | 15 | Use this component to wrap other components that might crash so errors can be caught. 16 | 17 | ## Basic example 18 | 19 | ```tsx 20 | 21 | }> 22 | {/* A component that fetches data */} 23 | 24 | 25 | ``` 26 | 27 | ## Custom fallback component 28 | 29 | A custom `fallback` component can be passed to the `ErrorBoundary` component, like so: 30 | 31 | ```tsx 32 | Error loading the cohort list}> 33 | }> 34 | {/* A component that fetches data */} 35 | 36 | 37 | ``` 38 | 39 | ### Source 40 | 41 | ```tsx 42 | import React, { ReactNode } from 'react' 43 | import { Alert } from 'theme-ui' 44 | 45 | export interface ErrorBoundaryProps { 46 | fallback?: ReactNode 47 | } 48 | 49 | interface ErrorBoundaryState { 50 | hasError: boolean 51 | error: Error | null 52 | } 53 | 54 | export class ErrorBoundary extends React.Component< 55 | ErrorBoundaryProps, 56 | ErrorBoundaryState 57 | > { 58 | state = { hasError: false, error: null } 59 | 60 | static getDerivedStateFromError(error: Error): ErrorBoundaryState { 61 | return { 62 | hasError: true, 63 | error, 64 | } 65 | } 66 | 67 | render(): ReactNode { 68 | if (this.state.hasError) { 69 | console.error(this.state.error) 70 | const fallback = this.props.fallback 71 | if (typeof fallback === 'string') { 72 | return {fallback} 73 | } else if (fallback) { 74 | return fallback 75 | } 76 | 77 | return ( 78 | 79 | Something went wrong. Please refresh the page and try again. 80 | 81 | ) 82 | } 83 | return this.props.children 84 | } 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /notes/react-query.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Query 3 | link: https://react-query.tanstack.com 4 | tags: 5 | - react 6 | emoji: ⚛ 7 | created: 2021-03-26T12:50:12.000Z 8 | modified: 2021-11-11T12:24:40.451Z 9 | --- 10 | 11 | ## useQuery 12 | 13 | Given a fetch function like this (that uses [axios](axios.html)): 14 | 15 | ```ts 16 | import axios from 'axios' 17 | import urlJoin from 'proper-url-join' 18 | 19 | export interface APIResponse { 20 | status?: number 21 | success: boolean 22 | data?: T 23 | error?: ErrorResponse 24 | } 25 | export interface ErrorResponse { 26 | message: string 27 | data?: { 28 | errors?: unknown 29 | error?: unknown 30 | } 31 | } 32 | 33 | interface RequestModel { 34 | path: string 35 | } 36 | 37 | export const getSomeData = async ({ 38 | path, 39 | }: RequestModel): Promise> => { 40 | try { 41 | const { data } = await axios({ 42 | method: 'GET', 43 | url: urlJoin('https://mysite.com', path), 44 | }) 45 | return data 46 | } catch (error) { 47 | throw new Error(error.message || 'error.unknown') 48 | } 49 | } 50 | ``` 51 | 52 | This is how you might use react-query 53 | 54 | ```ts 55 | import { useQuery, UseQueryResult } from 'react-query' 56 | 57 | interface ResponseModel { 58 | data: { 59 | key: string 60 | } 61 | } 62 | type ResponseUseQueryModel = UseQueryResult 63 | 64 | const someData = useQuery( 65 | ['traineeFeedback', traineeId], 66 | () => 67 | getSomeData({ 68 | path: TRAINEE_FEEDBACK, 69 | }), 70 | ) 71 | ``` 72 | 73 | ### Add options to the query 74 | 75 | ```ts 76 | const someData = useQuery( 77 | ['traineeFeedback', traineeId], 78 | () => 79 | getSomeData({ 80 | path: TRAINEE_FEEDBACK, 81 | }), 82 | // options are the 3rd param 83 | { 84 | // will wait for `idToken` to be truthy before running this query 85 | enabled: !!idToken, 86 | }, 87 | ) 88 | ``` 89 | 90 | ### As a custom hook 91 | 92 | ```ts 93 | import { useQuery, UseQueryResult } from 'react-query' 94 | 95 | export const useTraineeFeedback = ( 96 | traineeId: string, 97 | idToken: string, 98 | ): UseQueryResult => { 99 | return useQuery( 100 | ['traineeFeedback', traineeId], 101 | () => 102 | getSomeData({ 103 | path: TRAINEE_FEEDBACK, 104 | }), 105 | { 106 | enabled: !!idToken, 107 | }, 108 | ) 109 | } 110 | ``` 111 | 112 | ### Usage in a component 113 | 114 | From [here](https://github.com/tannerlinsley/react-query/discussions/2063) 115 | 116 | ```ts 117 | interface ViewUserPagePathParameters { 118 | userId: string 119 | } 120 | 121 | interface IUser { 122 | id: string 123 | displayName: string 124 | } 125 | 126 | export function assertNever(value: never): never { 127 | throw new Error(`Unhandled discriminated union member: ${value}`) 128 | } 129 | 130 | export const ViewUser: React.FC = (props) => { 131 | const { userId } = useParams() 132 | const { status, data, error } = useQuery( 133 | `getUser for ${userId}`, 134 | () => getUser({ id: userId }), 135 | ) 136 | 137 | if (status === 'success') { 138 | const user = data 139 | return
    Name: {user.displayName}
    140 | } else if (status === 'loading') { 141 | return
    loading...
    142 | } else if (status === 'idle') { 143 | return
    mutating...
    144 | } else if (status === 'error') { 145 | return 'An error has occurred: ' + error.message 146 | } else { 147 | return assertNever(queryResult) 148 | } 149 | } 150 | ``` 151 | 152 | ## useQueries 153 | 154 | ```ts 155 | import { useQueries } from 'react-query' 156 | 157 | const scorecardQueries = useQueries([ 158 | { 159 | queryKey: ['traineeInfo', traineeId], 160 | queryFn: () => 161 | getSomeData({ 162 | path: TRAINEE_FEEDBACK, 163 | }), 164 | // options in useQueries are not in a separate object 165 | enabled: !!idToken, 166 | }, 167 | { 168 | queryKey: ['traineeAchievements', traineeId], 169 | queryFn: () => 170 | getSomeData({ 171 | path: TRAINEE_FEEDBACK, 172 | }), 173 | enabled: !!idToken, 174 | }, 175 | ]) 176 | ``` 177 | -------------------------------------------------------------------------------- /notes/react-ssr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SSR 3 | tags: 4 | - react 5 | emoji: ⚛ 6 | created: 2020-09-06T07:57:42.000Z 7 | modified: 2020-09-06T07:57:42.000Z 8 | --- 9 | 10 | Use this to check if `window` is defined 11 | 12 | ```js 13 | const windowGlobal = typeof window !== 'undefined' && window 14 | ``` 15 | 16 | Useful libraries when working with SSR: 17 | 18 | - https://github.com/react-corekit/use-where 19 | -------------------------------------------------------------------------------- /notes/react-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React TypeScript 3 | tags: 4 | - react 5 | - typescript 6 | emoji: ⚛ 7 | created: 2021-03-09T12:42:11.000Z 8 | modified: 2021-11-11T11:54:45.763Z 9 | --- 10 | 11 | [React TypeScript cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet) is my bible for all React/TypeScipt things 12 | 13 | ## Functional components 14 | 15 | 1. Your own types 16 | 1. `VoidFunctionComponent` or `VFC` 17 | 1. `FunctionComponent` or `FC` 18 | 19 | ### 1. Your own type 20 | 21 | This is the preferred method, as described [here](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components/), for typing your React components. 22 | 23 | ```tsx 24 | interface AppProps { 25 | message: string 26 | } 27 | 28 | const App = ({ message }: AppProps): JSX.Element =>
    {message}
    29 | ``` 30 | 31 | If you need to type `children`, you can add the types like so: 32 | 33 | ```tsx 34 | interface AppProps { 35 | message: string 36 | children: ReactNode // or whatever type you want 37 | } 38 | 39 | const App = ({ message, children }: AppProps): JSX.Element => ( 40 |
    41 | {message} {children} 42 |
    43 | ) 44 | ``` 45 | 46 | ### 2. `FunctionComponent` or `FC` 47 | 48 | **Using `FunctionComponent` or `FC` is discouraged because it defines `children` as an optional prop.** In most cases you would want to be explicit about how `children` are used. It also defines `children` as `ReactNode` which is a very broad type. So you may want to define `children` as `ReactText` instead which is `number | string`. 49 | 50 | ```tsx 51 | interface AppProps { 52 | message: string 53 | } 54 | 55 | const App: FC = ({ message }) =>
    {message}
    56 | ``` 57 | 58 | ### 3. `VoidFunctionComponent` or `VFC` 59 | 60 | Using `VoidFunctionComponent` or `VFC` is slightly better than `FunctionComponent` because you have to explicitly define `children` type, but if you're doing that, you might as well use option 1 above instead. 61 | 62 | ```tsx 63 | interface AppProps { 64 | message: string 65 | } 66 | 67 | const App: FC = ({ message }) =>
    {message}
    68 | ``` 69 | 70 | ## Basic prop type examples 71 | 72 | ```ts 73 | type AppProps = { 74 | message: string 75 | count: number 76 | disabled: boolean 77 | /** array of a type! */ 78 | names: string[] 79 | /** string literals to specify exact string values, with a union type to join them together */ 80 | status: 'waiting' | 'success' 81 | /** any object as long as you dont use its properties (NOT COMMON but useful as placeholder) */ 82 | obj: object 83 | obj2: {} // almost the same as `object`, exactly the same as `Object` 84 | /** an object with any number of properties (PREFERRED) */ 85 | obj3: { 86 | id: string 87 | title: string 88 | } 89 | /** array of objects! (common) */ 90 | objArr: { 91 | id: string 92 | title: string 93 | }[] 94 | /** a dict object with any number of properties of the same type */ 95 | dict1: { 96 | [key: string]: MyTypeHere 97 | } 98 | dict2: Record // equivalent to dict1 99 | /** any function as long as you don't invoke it (not recommended) */ 100 | onSomething: Function 101 | /** function that doesn't take or return anything (VERY COMMON) */ 102 | onClick: () => void 103 | /** function with named prop (VERY COMMON) */ 104 | onChange: (id: number) => void 105 | /** alternative function type syntax that takes an event (VERY COMMON) */ 106 | onClick(event: React.MouseEvent): void 107 | /** an optional prop (VERY COMMON!) */ 108 | optional?: OptionalType 109 | } 110 | ``` 111 | 112 | ## Useful prop type examples 113 | 114 | ```ts 115 | export declare interface AppProps { 116 | children1: JSX.Element // bad, doesnt account for arrays 117 | children2: JSX.Element | JSX.Element[] // meh, doesn't accept strings 118 | children3: React.ReactChildren // despite the name, not at all an appropriate type; it is a utility 119 | children4: React.ReactChild[] // better, accepts array children 120 | children: React.ReactNode // best, accepts everything (see edge case below) 121 | functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type 122 | style?: React.CSSProperties // to pass through style props 123 | onChange?: React.FormEventHandler // form events! the generic parameter is the type of event.target 124 | // more info: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring 125 | props: Props & React.ComponentPropsWithoutRef<'button'> // to impersonate all the props of a button element and explicitly not forwarding its ref 126 | props2: Props & React.ComponentPropsWithRef // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref 127 | } 128 | ``` 129 | 130 | Read more [here](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/basic_type_example#useful-react-prop-type-examples) 131 | 132 | ## Event handling 133 | 134 | Some `class` based examples of TS event handling can be found at 135 | https://fettblog.eu/typescript-react/events/ 136 | -------------------------------------------------------------------------------- /notes/react-usecontext.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useContext 3 | tags: 4 | - react 5 | emoji: 🎣 6 | created: 2020-04-17T15:55:11.000Z 7 | modified: 2020-04-17T15:55:11.000Z 8 | --- 9 | 10 | - Docs: http://reactjs.org/docs/hooks-reference.html#usecontext 11 | - TS/React docs: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#context 12 | 13 | ## Setup 14 | 15 | This example uses the [`use-timer`](https://github.com/thibaultboursier/use-timer) hook to create a provider that passes a timer and it's methods to any consumers. 16 | 17 | ```tsx 18 | import React, { createContext } from 'react' 19 | import { useTimer } from 'use-timer' 20 | 21 | export interface TimerContextInterface { 22 | time: string 23 | start: () => void 24 | pause: () => void 25 | reset: () => void 26 | isRunning: boolean 27 | } 28 | 29 | export const TimerContext = createContext({} as TimerContextInterface) 30 | 31 | export const TimerProvider = ({ children }) => { 32 | const { time, start, pause, reset, isRunning } = useTimer({ 33 | initialTime: 5000, 34 | timerType: 'DECREMENTAL', 35 | step: 1000, 36 | }, 37 | }) 38 | 39 | return ( 40 | 41 | {children({ time, start, pause, reset, isRunning })} 42 | 43 | ) 44 | } 45 | 46 | export const useTimerContext = (): TimerContextInterface => 47 | useContext(TimerContext) 48 | ``` 49 | 50 | ## Usage 51 | 52 | ```tsx 53 | import React, { useContext } from 'react' 54 | 55 | const MyComponent = () => { 56 | const { time } = useContext(TimerContext) 57 | // or 58 | const { time } = useTimerContext() 59 | 60 | return
    Timer expires in {time}
    61 | } 62 | ``` 63 | 64 | ```tsx 65 | 66 | 67 | 68 | 69 | // or 70 | 71 | {({time})=> ( 72 |
    Timer expires in {time}
    73 | )} 74 |
    75 | ``` 76 | -------------------------------------------------------------------------------- /notes/react-usereducer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useReducer 3 | tags: 4 | - react 5 | emoji: 🎣 6 | created: 2020-04-17T15:55:11.000Z 7 | modified: 2020-04-17T15:55:11.000Z 8 | --- 9 | 10 | - Docs: http://reactjs.org/docs/hooks-reference.html#usereducer 11 | - TS/React docs: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/README.md#hooks 12 | 13 | ## Example 14 | 15 | ```tsx 16 | import React, { useReducer } from 'react' 17 | 18 | const initialState = { count: 0 } 19 | 20 | const reducer = (state, action) => { 21 | switch (action.type) { 22 | case 'increment': 23 | return { count: state.count + 1 } 24 | case 'decrement': 25 | return { count: state.count - 1 } 26 | default: 27 | throw new Error() 28 | } 29 | } 30 | 31 | export const Counter = () => { 32 | const [state, dispatch] = useReducer(reducer, initialState) 33 | return ( 34 | <> 35 | Count: {state.count} 36 | 37 | 38 | 39 | ) 40 | } 41 | ``` 42 | 43 | ## useImmer 44 | 45 | For complex stuff, [useImmer](https://github.com/immerjs/use-immer) is a great option 46 | -------------------------------------------------------------------------------- /notes/react-useref.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRef 3 | tags: 4 | - react 5 | emoji: 🎣 6 | created: 2020-04-17T15:55:11.000Z 7 | modified: 2020-04-17T15:55:11.000Z 8 | --- 9 | 10 | - Docs: http://reactjs.org/docs/hooks-reference.html#useref 11 | - TS/React docs: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/README.md#hooks 12 | 13 | ## TypeScript 14 | 15 | When using useRef, you have two options when creating a ref container that does not have an initial value: 16 | 17 | ```ts 18 | const ref1 = useRef(null!) 19 | const ref2 = useRef(null) 20 | ``` 21 | 22 | The first option will make `ref1.current` read-only, and is intended to be passed in to built-in ref attributes that React will manage (because React handles setting the current value for you). 23 | 24 | The second option will make `ref2.current` mutable, and is intended for "instance variables" that you manage yourself. 25 | 26 | ## Example 27 | 28 | ```tsx 29 | import React, { useRef } from 'react' 30 | 31 | export const TextInputWithFocusButton = () => { 32 | // initialise with null, but tell TypeScript we are looking for an HTMLInputElement 33 | const inputEl = React.useRef(null) 34 | const onButtonClick = () => { 35 | // strict null checks need us to check if inputEl and current exist. 36 | // but once current exists, it is of type HTMLInputElement, thus it 37 | // has the method focus! ✅ 38 | if (inputEl && inputEl.current) { 39 | inputEl.current.focus() 40 | } 41 | } 42 | return ( 43 | <> 44 | {/* in addition, inputEl only can be used with input elements. Yay! */} 45 | 46 | 47 | 48 | ) 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /notes/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: readme.md 3 | emoji: 📝 4 | created: 2020-02-27T23:51:44.000Z 5 | modified: 2021-07-10T07:14:48.229Z 6 | --- 7 | 8 | ### Header/intro 9 | 10 | ```html 11 |
    12 | 16 | 17 |

    18 | 21 | gatsby-theme-code-notes is released under the MIT license. 25 | 26 | 27 | Current npm package version. 31 | 32 | 35 | Downloads per month on npm. 39 | 40 | 43 | Total downloads on npm. 47 | 48 | PRs welcome! 52 | 53 | Follow @MrMartineau 57 | 58 | 59 | Open in Visual Studio Code 63 | 64 |

    65 | 66 |

    67 | Features • 68 | InstallationUsage • 69 | Demo 70 |

    71 |
    72 | ``` 73 | 74 | ### Footer 75 | 76 | ```md 77 | --- 78 | 79 | ## License 80 | 81 | [MIT](https://choosealicense.com/licenses/mit/) © [Zander Martineau](https://zander.wtf) 82 | 83 | > Made by Zander • [zander.wtf](https://zander.wtf) • [GitHub](https://github.com/mrmartineau/) • [Twitter](https://twitter.com/mrmartineau/) 84 | ``` 85 | -------------------------------------------------------------------------------- /notes/redux-saga.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Redux Saga 3 | tags: 4 | - react 5 | created: 2020-03-22T23:14:36.000Z 6 | modified: 2020-03-24T22:53:27.000Z 7 | --- 8 | 9 | > An alternative side effect model for Redux apps 10 | 11 | https://redux-saga.js.org 12 | https://github.com/redux-saga/redux-saga/ 13 | 14 | ## Routines 15 | 16 | > Routines for redux-saga 17 | 18 | https://github.com/afitiskin/redux-saga-routines 19 | 20 | ```js 21 | import { createRoutine } from 'redux-saga-routines' 22 | 23 | const fetchData = createRoutine('FETCH_DATA') 24 | ``` 25 | 26 | ### Access the action types 27 | 28 | ```js 29 | routine.TRIGGER === 'ACTION_TYPE_PREFIX/TRIGGER' 30 | routine.REQUEST === 'ACTION_TYPE_PREFIX/REQUEST' 31 | routine.SUCCESS === 'ACTION_TYPE_PREFIX/SUCCESS' 32 | routine.FAILURE === 'ACTION_TYPE_PREFIX/FAILURE' 33 | routine.FULFILL === 'ACTION_TYPE_PREFIX/FULFILL' 34 | ``` 35 | 36 | You also have 5 action creators: trigger, request, success, failure, fulfill: 37 | 38 | ```js 39 | routine.trigger(payload) === { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload } 40 | routine.request(payload) === { type: 'ACTION_TYPE_PREFIX/REQUEST', payload } 41 | routine.success(payload) === { type: 'ACTION_TYPE_PREFIX/SUCCESS', payload } 42 | routine.failure(payload) === { type: 'ACTION_TYPE_PREFIX/FAILURE', payload } 43 | routine.fulfill(payload) === { type: 'ACTION_TYPE_PREFIX/FULFILL', payload } 44 | ``` 45 | 46 | ### Reducer 47 | 48 | ```js 49 | import produce from 'immer' 50 | import { fetchData } from './routines' 51 | 52 | const initialState = { 53 | data: null, 54 | loading: false, 55 | error: null 56 | } 57 | 58 | export const exampleReducer = ( 59 | state = initialState, 60 | action: ExampleActionInterface 61 | ) => 62 | produce(state, draft => { 63 | switch (action.type) { 64 | case fetchData.REQUEST: 65 | draft.loading = true 66 | break 67 | case fetchData.SUCCESS: 68 | draft.loading = false 69 | draft.data = action.payload 70 | break 71 | case fetchData.FAILURE: 72 | draft.loading = false 73 | draft.error = true 74 | break 75 | } 76 | }) 77 | ``` 78 | 79 | ### Sagas 80 | 81 | ```js 82 | export function* fetchDataRequest( 83 | { payload }, 84 | ) { 85 | const { fromDate, toDate } = payload; 86 | try { 87 | const walletId = yield select(getWalletId); 88 | const { data } = yield call([walletAdapter, 'requestPendingTransactions'], { 89 | walletId, 90 | fromDate, 91 | toDate, 92 | }); 93 | 94 | yield put(getPendingTransactions.success(transformTransactionValues(data))); 95 | 96 | } catch (error) { 97 | yield put(getPendingTransactions.failure(error)); 98 | } 99 | } 100 | 101 | export function* walletSagas() { 102 | yield takeEvery( 103 | getPendingTransactions.REQUEST, 104 | getPendingTransactionsRequest as any 105 | ); 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /notes/redux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Redux 3 | tags: 4 | - react 5 | created: 2020-02-27T23:02:00.000Z 6 | modified: 2020-03-24T22:53:27.000Z 7 | --- 8 | 9 | ## Basic example 10 | 11 | ```jsx 12 | // index.ts 13 | import { connect } from 'react-redux' 14 | 15 | import { Component, mapStateToProps, mapDispatchToProps } from './Component' 16 | 17 | export const ConnectedComponent = connect( 18 | mapStateToProps, 19 | mapDispatchToProps 20 | )(UnconnectedComponent) 21 | ``` 22 | 23 | ```jsx 24 | // Component.tsx 25 | import React from 'react' 26 | import { doClearQuery } from './some/file/of/action/creators' 27 | 28 | const Component = ({ query, results, clearQuery }) => ( 29 |
    30 | query: {query} 31 | 32 |
      33 | {results.map(result => ( 34 |
    • 35 | {result.title} 36 | {result.title} 37 |
    • 38 | ))} 39 |
    40 |
    41 | ) 42 | 43 | // mapStateToProps 44 | // For our select function we're returning an object using 45 | // the implicit return and wrapping it in `()` 46 | const mapStateToProps = appState => ({ 47 | results: appState.results, 48 | query: appState.query 49 | }) 50 | 51 | // mapDispatchToProps 52 | // We could also use object shorthand here to avoid 53 | // building an object, as long as we're OK with the 54 | // props being named the same thing 55 | const mapDispatchToProps = { clearQuery: doClearQuery } 56 | ``` 57 | 58 | ## Selectors 59 | 60 | ```js 61 | export const getSignedInStatus = user => user.isSignedIn 62 | export const getFullName = user => `${user.firstName} ${user.lastName}` 63 | export const getUsername = user => user.username 64 | ``` 65 | -------------------------------------------------------------------------------- /notes/remove-array-duplicates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remove array duplicates 3 | tags: 4 | - javascript 5 | emoji: ❌ 6 | created: 2020-03-22T14:53:49.000Z 7 | modified: 2020-04-29T15:13:20.000Z 8 | --- 9 | 10 | ## Using `Set` 11 | 12 | ```js 13 | const array = ['🐑', 1, 2, '🐑', '🐑', 3] 14 | 15 | // Step 1 16 | const uniqueSet = new Set(array) 17 | // Set { '🐑', 1, 2, 3 } 18 | 19 | // Step 2 20 | const backToArray = [...uniqueSet] 21 | // ['🐑', 1, 2, 3] 22 | 23 | // or Step 1 24 | const uniqueSet = [...new Set(array)] 25 | // ['🐑', 1, 2, 3] 26 | ``` 27 | 28 | Source: https://www.samanthaming.com/tidbits/43-3-ways-to-remove-array-duplicates 29 | -------------------------------------------------------------------------------- /notes/remove-items-from-arrays.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Removing items from arrays 3 | tags: 4 | - javascript 5 | created: 2021-01-06T11:22:00.000Z 6 | modified: 2021-01-06T11:22:00.000Z 7 | emoji: ❌ 8 | --- 9 | 10 | ### Remove single item from array 11 | 12 | ```js 13 | const removeItemFromArray = (arr, value) => { 14 | var index = arr.indexOf(value) 15 | if (index > -1) { 16 | arr.splice(index, 1) 17 | } 18 | return arr 19 | } 20 | 21 | // Usage 22 | console.log(removeItemFromArray([2, 5, 9, 1, 5, 8, 5], 5)) 23 | ``` 24 | 25 | ### Remove multiple items from array 26 | 27 | ```js 28 | const removeAllItemsFromArray = (arr, value) => { 29 | var i = 0 30 | while (i < arr.length) { 31 | if (arr[i] === value) { 32 | arr.splice(i, 1) 33 | } else { 34 | ++i 35 | } 36 | } 37 | return arr 38 | } 39 | 40 | // Usage 41 | console.log(removeAllItemsFromArray([2, 5, 9, 1, 5, 8, 5], 5)) 42 | ``` 43 | -------------------------------------------------------------------------------- /notes/render-if-role.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RenderIfRole 3 | tags: 4 | - react 5 | emoji: ⚛ 6 | created: 2021-07-12T06:09:33.887Z 7 | modified: 2021-11-11T12:29:12.563Z 8 | --- 9 | 10 | ```tsx 11 | import { ReactNode } from 'react' 12 | import { useUserContext } from '../../providers' 13 | 14 | export enum Roles { 15 | none = 'none', 16 | admin = 'admin', 17 | superadmin = 'superadmin', 18 | user = 'user', 19 | readonly = 'readonly', 20 | } 21 | 22 | export interface RenderIfRoleProps { 23 | roles: Roles[] 24 | children: ReactNode 25 | } 26 | 27 | export const RenderIfRole = ({ 28 | roles, 29 | children, 30 | }: RenderIfRoleProps): ReactNode | null => { 31 | const { role } = useUserContext() // get user's current role, could also be passed as a prop 32 | const providedRolesMatchUserRole = role && roles.includes(Roles[role]) 33 | 34 | if (providedRolesMatchUserRole) { 35 | return children 36 | } 37 | return null 38 | } 39 | ``` 40 | 41 | ## Usage 42 | 43 | This component is used to conditionally show content based on a user's role. 44 | 45 | This link will only be shown if the current user's role matches `Roles.admin` 46 | 47 | ```tsx 48 | 49 | Invite 50 | 51 | ``` 52 | 53 | Multiple roles can be passed in to the `roles` prop, like so: 54 | 55 | ```tsx 56 | Some content 57 | ``` 58 | -------------------------------------------------------------------------------- /notes/save-scroll-position.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Save sidebar scroll position 3 | tags: 4 | - javascript 5 | link: 'https://twitter.com/hakimel/status/1262337514741706752' 6 | created: 2020-05-19T08:25:55.000Z 7 | modified: 2020-05-19T08:25:55.000Z 8 | --- 9 | 10 | ```ts 11 | let sidebar = document.querySelector('.sidebar') 12 | 13 | let top = sessionStorage.getItem('sidebar-scroll') 14 | 15 | if (top !== null) { 16 | sidebar.scrollTop = parseInt(top, 10) 17 | } 18 | 19 | window.addEventListener('beforeunload', () => { 20 | sessionStorage.setItem('sidebar-scroll', sidebar.scrollTop) 21 | }) 22 | ``` 23 | -------------------------------------------------------------------------------- /notes/security.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Security 3 | tags: 4 | - node 5 | emoji: 👮‍♂️ 6 | created: 2020-03-10T14:58:40.000Z 7 | modified: 2020-03-26T23:06:06.000Z 8 | --- 9 | 10 | [OWASP Top Ten](https://owasp.org/www-project-top-ten/) 11 | 12 | ## Node 13 | 14 | - [Node Security best practices](https://github.com/goldbergyoni/nodebestpractices#6-security-best-practices) 15 | - [eslint-plugin-security](https://github.com/nodesecurity/eslint-plugin-security) 16 | 17 | ### Express middlewares 18 | 19 | - [helmet](https://github.com/helmetjs/helmet) 20 | - [express-defend](https://github.com/akos-sereg/express-defend) 21 | - [guestlist](https://github.com/i-like-robots/guestlist) 22 | 23 | ## Threat modelling 24 | 25 | ### [STRIDE](https://en.wikipedia.org/wiki/STRIDE_%28security%29) 26 | 27 | | Threat | Desired property | 28 | | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | 29 | | [**S**poofing](https://en.wikipedia.org/wiki/Spoofing_attack) | Authenticity | 30 | | [**T**ampering]() | Integrity | 31 | | [**R**epudiation](https://en.wikipedia.org/wiki/Non-repudiation) | Non-repudiability | 32 | | **I**nformation disclosure ([privacy breach](https://en.wikipedia.org/wiki/Data_privacy) or [data leak](https://en.wikipedia.org/wiki/Data_leak)) | Confidentiality | 33 | | [**D**enial of Service](https://en.wikipedia.org/wiki/Denial-of-service_attack) | Availability | 34 | | [**E**levation of Privilege](https://en.wikipedia.org/wiki/Privilege_escalation) | Authorisation | 35 | 36 | ### [Attack tree](https://en.wikipedia.org/wiki/Attack_tree) 37 | 38 | Attack trees are conceptual diagrams showing how an asset, or target, might be attacked. Attack trees have been used in a variety of applications. In the field of information technology, they have been used to describe threats on computer systems and possible attacks to realize those threats. However, their use is not restricted to the analysis of conventional information systems. They are widely used in the fields of defense and aerospace for the analysis of threats against tamper resistant electronics systems (e.g., avionics on military aircraft).[1] Attack trees are increasingly being applied to computer control systems (especially relating to the electric power grid ).[2] Attack trees have also been used to understand threats to physical systems. 39 | 40 | ### [DREAD]() 41 | 42 | DREAD is part of a system for risk-assessing computer security threats previously used at Microsoft and although currently used by OpenStack and other corporations[citation needed] it was abandoned by its creators [1]. It provides a mnemonic for risk rating security threats using five categories. 43 | 44 | The categories are: 45 | 46 | - **Damage** – how bad would an attack be? 47 | - **Reproducibility** – how easy is it to reproduce the attack? 48 | - **Exploitability** – how much work is it to launch the attack? 49 | - **Affected** users – how many people will be impacted? 50 | - **Discoverability** – how easy is it to discover the threat? 51 | -------------------------------------------------------------------------------- /notes/showing-hiding-content.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Showing/hiding content 3 | emoji: ♿ 4 | created: 2020-12-29T10:31:32.000Z 5 | modified: 2020-12-29T17:17:21.000Z 6 | tags: 7 | - html 8 | - css 9 | --- 10 | 11 | ## Hide from all users 12 | 13 | ```css 14 | a { 15 | display: none; 16 | /* or */ 17 | visibility: hidden; 18 | } 19 | ``` 20 | 21 | or by using the `hidden` attribute 22 | 23 | ```html 24 |