[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"url-settings":86,"blog-\u002Fblog\u002Fcreating-custom-hooks-reactjs":671,"blog-author-\u002Fblog\u002Fcreating-custom-hooks-reactjs":1191,"i-material-symbols:arrow-back-rounded":1203},{"id":4,"extension":5,"footer":6,"header":73,"meta":83,"stem":84,"__hash__":85},"navigation\u002Fdata\u002Fshared\u002Fnavigation.yml","yml",{"brand":7,"columns":13,"legal":63},{"name":8,"tagline":9,"downloadCta":10},"Pieces","The memory layer for modern work.",{"label":11,"href":12},"All downloads","\u002Fdownloads",[14,26,39,51],{"title":15,"links":16},"Product",[17,20,23],{"label":18,"href":19},"Pieces Desktop","\u002F",{"label":21,"href":22},"Pieces Enterprise","\u002Fenterprise",{"label":24,"href":25},"Pieces MCP","\u002Fmcp",{"title":27,"links":28},"Resources",[29,33,36],{"label":30,"href":31,"external":32},"Documentation","url:docs.home",true,{"label":34,"href":35},"Blog","\u002Fblog",{"label":37,"href":38,"external":32},"GitHub","url:github.org",{"title":40,"links":41},"Community",[42,45,48],{"label":43,"href":44,"external":32},"Discord","url:social.discord",{"label":46,"href":47,"external":32},"X \u002F Twitter","url:social.x",{"label":49,"href":50,"external":32},"LinkedIn","url:social.linkedin",{"title":52,"links":53},"Company",[54,57,60],{"label":55,"href":56},"About","\u002Fabout",{"label":58,"href":59},"Updates","\u002Fupdates",{"label":61,"href":62},"Contact","\u002Fcontact",[64,67,70],{"label":65,"href":66},"Privacy Policy","\u002Flegal\u002Fprivacy",{"label":68,"href":69},"Refund Policy","\u002Flegal\u002Frefund",{"label":71,"href":72},"Terms of Service","\u002Flegal\u002Fterms",{"links":74,"signIn":75,"contact":78,"cta":80},[],{"label":76,"href":77},"Manage account","url:portal.home",{"label":79,"href":62},"Get in touch",{"label":81,"href":82},"Download","url:routes.downloads",{},"data\u002Fshared\u002Fnavigation","XlSALC7vYCXHcmcXnjJViwIZKsqnQWF4bn4o9RrcSBE",{"id":87,"extension":5,"links":88,"meta":668,"stem":669,"__hash__":670},"urlSettings\u002Fdata\u002Fshared\u002Furls.yml",[89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,303,307,311,314,318,322,326,330,334,338,342,346,350,354,358,362,366,370,374,378,382,386,390,394,398,402,406,410,414,418,422,426,430,434,438,442,446,450,454,458,461,465,469,473,477,481,485,488,491,494,497,501,505,508,512,516,520,524,528,532,536,540,544,548,552,556,559,563,567,571,575,579,582,585,588,592,595,599,603,607,611,614,618,622,625,628,631,635,639,643,646,649,652,656,660,664],{"key":90,"label":91,"href":92},"downloads.desktop","Desktop download page","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdownload",{"key":94,"label":95,"href":96},"downloads.macOS.dmgArm64","macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg-arm64\u002Fdownload",{"key":98,"label":99,"href":100},"downloads.macOS.dmgIntel","macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg\u002Fdownload",{"key":102,"label":103,"href":104},"downloads.macOS.pkg","macOS PKG","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg\u002Fdownload",{"key":106,"label":107,"href":108},"downloads.macOS.universal","Desktop macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fmacos-universal\u002Fdownload",{"key":110,"label":111,"href":112},"downloads.macOS.pkgArm64","Desktop macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd-arm64\u002Fdownload",{"key":114,"label":115,"href":116},"downloads.macOS.pkgIntel","Desktop macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd\u002Fdownload",{"key":118,"label":119,"href":120},"downloads.windows.appinstaller","Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fpieces_for_x.appinstaller",{"key":122,"label":123,"href":124},"downloads.windows.exe","Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-exe\u002Fdownload",{"key":126,"label":127,"href":128},"downloads.windows.msix","Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-msix\u002Fdownload",{"key":130,"label":131,"href":132},"downloads.windows.suiteManager","Windows Suite Manager","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_suite_windows\u002Fappinstaller\u002Fdownload",{"key":134,"label":135,"href":136},"downloads.linux.flatpakRepo","Linux Flatpak repository","https:\u002F\u002Fbuilds.pieces.app\u002Fpieces-flatpak-repo\u002Fpieces-flatpak.flatpakrepo",{"key":138,"label":139,"href":140},"downloads.linux.snapDesktop","Linux Snap Desktop","https:\u002F\u002Fsnapcraft.io\u002Fpieces-for-developers",{"key":142,"label":143,"href":144},"downloads.linux.snapPiecesOS","Linux Snap PiecesOS","https:\u002F\u002Fsnapcraft.io\u002Fpieces-os",{"key":146,"label":147,"href":148},"downloads.linux.snap","Desktop Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fsnap\u002Fdownload",{"key":150,"label":151,"href":152},"downloads.piecesOS.macOS.dmgArm64","PiecesOS macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg-arm64\u002Fdownload",{"key":154,"label":155,"href":156},"downloads.piecesOS.macOS.dmgIntel","PiecesOS macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg\u002Fdownload",{"key":158,"label":159,"href":160},"downloads.piecesOS.macOS.universal","PiecesOS macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fmacos-universal\u002Fdownload",{"key":162,"label":163,"href":164},"downloads.piecesOS.macOS.pkgArm64","PiecesOS macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only-arm64\u002Fdownload",{"key":166,"label":167,"href":168},"downloads.piecesOS.macOS.pkgIntel","PiecesOS macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only\u002Fdownload",{"key":170,"label":171,"href":172},"downloads.piecesOS.windows.appinstaller","PiecesOS Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fos_server.appinstaller",{"key":174,"label":175,"href":176},"downloads.piecesOS.windows.exe","PiecesOS Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-exe\u002Fdownload",{"key":178,"label":179,"href":180},"downloads.piecesOS.windows.msix","PiecesOS Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-msix\u002Fdownload",{"key":182,"label":183,"href":184},"downloads.piecesOS.linux.snap","PiecesOS Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fsnap\u002Fdownload",{"key":186,"label":187,"href":188},"downloads.combined.macOS.appleSilicon","Combined macOS installer Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-arm64\u002Fdownload",{"key":190,"label":191,"href":104},"downloads.combined.macOS.intel","Combined macOS installer Intel",{"key":193,"label":194,"href":195},"downloads.guides.macOS","macOS installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fmacos-installation-guide",{"key":197,"label":198,"href":199},"downloads.guides.windows","Windows installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fwindows-installation-guide",{"key":201,"label":202,"href":203},"downloads.guides.linux","Linux installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide",{"key":205,"label":206,"href":207},"downloads.guides.linuxFlatpak","Linux Flatpak installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fflatpak",{"key":209,"label":210,"href":211},"downloads.guides.linuxSnap","Linux Snap installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fsnap",{"key":213,"label":214,"href":215},"downloads.guides.piecesOS","PiecesOS manual installation","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fmanual-installation",{"key":217,"label":218,"href":219},"extensions.chrome","Chrome extension","https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Fpieces-save-code-snippets\u002Figbgibhbfonhmjlechmeefimncpekepm",{"key":221,"label":222,"href":223},"extensions.firefox","Firefox add-on","https:\u002F\u002Faddons.mozilla.org\u002Fen-US\u002Ffirefox\u002Faddon\u002Fpieces-save-code-from-the-web\u002F",{"key":225,"label":226,"href":227},"extensions.edge","Edge add-on","https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fpieces-save-code-snippet\u002Fhglfimcdgonaeeobjckfdabcldfidmim",{"key":229,"label":230,"href":231},"extensions.vscode","VS Code extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.pieces-vscode",{"key":233,"label":234,"href":235},"extensions.visualStudio","Visual Studio extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.PiecesVisualStudio",{"key":237,"label":238,"href":239},"extensions.jetbrains","JetBrains plugin","https:\u002F\u002Fplugins.jetbrains.com\u002Fplugin\u002F17328-pieces--save-search-share--reuse-code-snippets",{"key":241,"label":242,"href":243},"extensions.obsidian","Obsidian plugin","https:\u002F\u002Fobsidian.md\u002Fplugins?id=pieces-for-developers",{"key":245,"label":246,"href":247},"extensions.sublime","Sublime package","https:\u002F\u002Fpackagecontrol.io\u002Fpackages\u002FPieces",{"key":249,"label":250,"href":251},"extensions.neovim","Neovim plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neo_vim",{"key":253,"label":254,"href":255},"extensions.jupyterlab","JupyterLab plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fjupyterlab-pieces",{"key":257,"label":258,"href":259},"extensions.cli","Pieces CLI","https:\u002F\u002Fpypi.org\u002Fproject\u002Fpieces-cli\u002F",{"key":261,"label":262,"href":263},"docs.home","Documentation home","https:\u002F\u002Fdocs.pieces.app",{"key":265,"label":266,"href":267},"docs.getStarted","Get started docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces",{"key":269,"label":270,"href":271},"docs.api","API docs","https:\u002F\u002Fdocs.pieces.app\u002Fapi",{"key":273,"label":274,"href":275},"docs.desktop.overview","Desktop overview","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop",{"key":277,"label":278,"href":279},"docs.desktop.onboarding","Desktop onboarding","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fonboarding",{"key":281,"label":282,"href":283},"docs.desktop.timeline","Desktop timeline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Ftimeline",{"key":285,"label":286,"href":287},"docs.desktop.summaries","Desktop summaries docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fsingle-click-summaries",{"key":289,"label":290,"href":291},"docs.desktop.search","Desktop conversational search docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconversational-search",{"key":293,"label":294,"href":295},"docs.desktop.drive","Desktop drive docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdrive",{"key":297,"label":298,"href":299},"docs.desktop.account","Desktop account settings docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconfiguration\u002Faccount",{"key":301,"label":302,"href":92},"docs.desktop.download","Desktop download docs",{"key":304,"label":305,"href":306},"docs.piecesOS.overview","PiecesOS overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies",{"key":308,"label":309,"href":310},"docs.piecesOS.details","PiecesOS details docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os",{"key":312,"label":313,"href":215},"docs.piecesOS.install","PiecesOS install docs",{"key":315,"label":316,"href":317},"docs.piecesOS.quickMenu","PiecesOS quick menu docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fquick-menu",{"key":319,"label":320,"href":321},"docs.piecesOS.storage","On-device storage docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fon-device-storage",{"key":323,"label":324,"href":325},"docs.piecesOS.troubleshooting","PiecesOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Ftroubleshooting",{"key":327,"label":328,"href":329},"docs.mcp.overview","MCP overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp",{"key":331,"label":332,"href":333},"docs.mcp.cursor","MCP Cursor docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcursor",{"key":335,"label":336,"href":337},"docs.mcp.vscode","MCP VS Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fvs-code",{"key":339,"label":340,"href":341},"docs.mcp.claudeDesktop","MCP Claude Desktop docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-desktop",{"key":343,"label":344,"href":345},"docs.mcp.claudeCode","MCP Claude Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-code",{"key":347,"label":348,"href":349},"docs.mcp.claudeCowork","MCP Claude Cowork docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-cowork",{"key":351,"label":352,"href":353},"docs.mcp.githubCopilot","MCP GitHub Copilot docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgithub-copilot",{"key":355,"label":356,"href":357},"docs.mcp.goose","MCP Goose docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoose",{"key":359,"label":360,"href":361},"docs.mcp.windsurf","MCP Windsurf docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fwindsurf",{"key":363,"label":364,"href":365},"docs.mcp.zed","MCP Zed docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fzed",{"key":367,"label":368,"href":369},"docs.mcp.jetbrains","MCP JetBrains docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fjetbrains-ides",{"key":371,"label":372,"href":373},"docs.mcp.continueDev","MCP Continue docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcontinue-dev",{"key":375,"label":376,"href":377},"docs.mcp.cline","MCP Cline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcline",{"key":379,"label":380,"href":381},"docs.mcp.raycast","MCP Raycast docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fraycast",{"key":383,"label":384,"href":385},"docs.mcp.rovoDevCli","MCP Rovo Dev CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Frovo-dev-cli",{"key":387,"label":388,"href":389},"docs.mcp.openaiCodexCli","MCP OpenAI Codex CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenai-codex-cli",{"key":391,"label":392,"href":393},"docs.mcp.googleGeminiCli","MCP Google Gemini CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoogle-gemini-cli",{"key":395,"label":396,"href":397},"docs.mcp.amazonQ","MCP Amazon Q docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Famazon-q-developer",{"key":399,"label":400,"href":401},"docs.mcp.chatgptDev","MCP ChatGPT Developer Mode docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fchatgpt-developer-mode",{"key":403,"label":404,"href":405},"docs.mcp.openclaw","MCP OpenClaw docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenclaw",{"key":407,"label":408,"href":409},"docs.mcp.mcpRemote","MCP Remote docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fmcp-remote",{"key":411,"label":412,"href":413},"docs.mcp.ngrok","MCP ngrok docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fngrok-setup",{"key":415,"label":416,"href":417},"docs.troubleshooting.macOS","macOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fmacos",{"key":419,"label":420,"href":421},"docs.troubleshooting.windows","Windows troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fwindows",{"key":423,"label":424,"href":425},"docs.troubleshooting.linux","Linux troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Flinux",{"key":427,"label":428,"href":429},"docs.privacy","Privacy and security docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fprivacy-security-your-data",{"key":431,"label":432,"href":433},"docs.support","Support docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fsupport",{"key":435,"label":436,"href":437},"portal.home","Pieces portal","https:\u002F\u002Fportal.pieces.app",{"key":439,"label":440,"href":441},"site.home","Website home","https:\u002F\u002Fpieces.app",{"key":443,"label":444,"href":445},"site.about","About page","https:\u002F\u002Fpieces.app\u002Fabout",{"key":447,"label":448,"href":449},"site.features","Features page","https:\u002F\u002Fpieces.app\u002Ffeatures",{"key":451,"label":452,"href":453},"site.plugins","Plugins page","https:\u002F\u002Fpieces.app\u002Fplugins",{"key":455,"label":456,"href":457},"site.contact","Contact page","https:\u002F\u002Fpieces.app\u002Fcontact",{"key":459,"label":58,"href":460},"site.updates","https:\u002F\u002Fpieces.app\u002Fupdates",{"key":462,"label":463,"href":464},"site.news","News","https:\u002F\u002Fpieces.app\u002Fnews",{"key":466,"label":467,"href":468},"site.events","Community events","https:\u002F\u002Fpieces.app\u002Fcommunity\u002Fevents",{"key":470,"label":471,"href":472},"site.userStories","User stories","https:\u002F\u002Fpieces.app\u002Fuser-stories",{"key":474,"label":475,"href":476},"site.academy","Academy","https:\u002F\u002Fpieces.app\u002Flearn\u002Facademy",{"key":478,"label":479,"href":480},"site.support","Website support","https:\u002F\u002Fpieces.app\u002Fsupport",{"key":482,"label":483,"href":484},"site.standup","Standup","https:\u002F\u002Fpieces.app\u002Fstandup",{"key":486,"label":34,"href":487},"site.blog","https:\u002F\u002Fcode.pieces.app\u002Fblog",{"key":489,"label":43,"href":490},"social.discord","https:\u002F\u002Fdiscord.gg\u002Fgetpieces",{"key":492,"label":46,"href":493},"social.x","https:\u002F\u002Fx.com\u002Fgetpieces",{"key":495,"label":496,"href":493},"social.twitter","Twitter",{"key":498,"label":499,"href":500},"social.instagram","Instagram","https:\u002F\u002Fwww.instagram.com\u002Fgetpieces\u002F",{"key":502,"label":503,"href":504},"social.tiktok","TikTok","https:\u002F\u002Fwww.tiktok.com\u002F@getpieces",{"key":506,"label":49,"href":507},"social.linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fgetpieces\u002F",{"key":509,"label":510,"href":511},"social.youtube","YouTube","https:\u002F\u002Fyoutube.com\u002F@getpieces",{"key":513,"label":514,"href":515},"github.org","GitHub organization","https:\u002F\u002Fgithub.com\u002Fpieces-app",{"key":517,"label":518,"href":519},"github.support","GitHub support","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport",{"key":521,"label":522,"href":523},"github.issues","GitHub issues","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fissues",{"key":525,"label":526,"href":527},"github.discussions","GitHub discussions","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fdiscussions",{"key":529,"label":530,"href":531},"github.documentation","GitHub documentation","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fdocumentation",{"key":533,"label":534,"href":535},"github.opensource","GitHub open source","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fopensource",{"key":537,"label":538,"href":539},"github.sdks.python","Python SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-python",{"key":541,"label":542,"href":543},"github.sdks.typescript","TypeScript SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-typescript",{"key":545,"label":546,"href":547},"github.sdks.dart","Dart SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-dart",{"key":549,"label":550,"href":551},"github.sdks.kotlin","Kotlin SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-kotlin",{"key":553,"label":554,"href":555},"github.plugins.obsidian","Obsidian plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fobsidian-pieces",{"key":557,"label":558,"href":255},"github.plugins.jupyterlab","JupyterLab plugin repository",{"key":560,"label":561,"href":562},"github.plugins.sublime","Sublime plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_sublime",{"key":564,"label":565,"href":566},"github.plugins.neovim","Neovim plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neovim",{"key":568,"label":569,"href":570},"github.cliAgent","CLI agent repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fcli-agent",{"key":572,"label":573,"href":574},"github.mcpDart","MCP Dart repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fmcp_dart",{"key":576,"label":577,"href":578},"github.awesomePieces","Awesome Pieces repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fawesome-pieces",{"key":580,"label":581,"href":66},"legal.privacyPolicy","Privacy policy",{"key":583,"label":584,"href":69},"legal.refundPolicy","Refund policy",{"key":586,"label":587,"href":72},"legal.terms","Terms",{"key":589,"label":590,"href":591},"legal.security","Legal security","https:\u002F\u002Fpieces.app\u002Flegal\u002Fsecurity",{"key":593,"label":594,"href":511},"videos.youtubeChannel","YouTube channel",{"key":596,"label":597,"href":598},"videos.gettingStartedDesktop","Getting started desktop video","https:\u002F\u002Fyoutu.be\u002FdUr1lRM_TYk",{"key":600,"label":601,"href":602},"videos.snippetDiscoveryxR","Snippet discovery video","https:\u002F\u002Fyoutu.be\u002FG6vb1USw-30",{"key":604,"label":605,"href":606},"sales.bookACall","Book a sales call","https:\u002F\u002Fcalendar.app.google\u002FWVUDtUfNy5Vst3sH7",{"key":608,"label":609,"href":610},"sales.enterprise","Enterprise form","https:\u002F\u002Fgetpieces.typeform.com\u002Fto\u002FaVQFTvpE",{"key":612,"label":613,"href":527},"sales.feedback","Feedback discussions",{"key":615,"label":616,"href":617},"sales.earlyAccess","Early access form","https:\u002F\u002Fgetpieces.typeform.com\u002Fearlyaccess",{"key":619,"label":620,"href":621},"sales.supportEmail","Support email","mailto:support@pieces.app",{"key":623,"label":624,"href":19},"routes.home","Home route",{"key":626,"label":627,"href":56},"routes.about","About route",{"key":629,"label":630,"href":12},"routes.downloads","Downloads route",{"key":632,"label":633,"href":634},"routes.thanks","Post-install thanks route","\u002Finstall\u002Fthanks",{"key":636,"label":637,"href":638},"routes.pricing","Pricing route","\u002Fpricing",{"key":640,"label":641,"href":642},"cta.freeTrial","Start free trial CTA (Pieces Pro) — used by \u002Fcampaigns\u002F* landing pages","https:\u002F\u002Fcampaigns.pieces.app\u002F",{"key":644,"label":645,"href":22},"routes.enterprise","Enterprise route",{"key":647,"label":648,"href":62},"routes.contact","Contact route",{"key":650,"label":651,"href":59},"routes.updates","Updates route",{"key":653,"label":654,"href":655},"routes.migrationError","Migration error route","\u002Fmigration\u002Ferror",{"key":657,"label":658,"href":659},"routes.authenticated","Post-login authenticated route","\u002Fauth\u002Fsigned-in",{"key":661,"label":662,"href":663},"routes.signed-out","Signed out route","\u002Fauth\u002Fsigned-out",{"key":665,"label":666,"href":667},"routes.authentication-error","Authentication error route","\u002Fauth\u002Ferror",{},"data\u002Fshared\u002Furls","2AvfSWI6bZACH5Kr1ZS3txMLjteRjCjbDR_ul1uRZ9Q",{"id":672,"title":673,"author":674,"authorPhoto":675,"authorPhotoAlt":676,"authorSlug":677,"body":678,"buttonText":1176,"buttonUrl":1177,"category":1178,"date":1179,"description":1180,"draft":1181,"editorsPick":1181,"extension":1182,"featured":1181,"image":1183,"imageAlt":1184,"meta":1185,"navigation":32,"ogImage":1186,"ogImageAlt":1186,"path":1187,"seo":1188,"stem":1189,"tags":1186,"__hash__":1190},"blog\u002Fblog\u002Fcreating-custom-hooks-reactjs.md","Creating React custom hooks with Pieces","Raman Hundal","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fcreating-custom-hooks-reactjs\u002Fauthor.jpeg","Raman Hundal.","raman-hundal",{"type":679,"value":680,"toc":1170},"minimark",[681,685,688,693,696,712,716,719,750,760,763,773,776,779,782,788,791,800,807,810,816,819,822,828,831,837,840,843,846,849,852,856,859,870,873,884,899,906,912,923,929,932,935,949,952,958,963,968,971,977,982,985,991,1002,1008,1011,1024,1031,1037,1040,1046,1053,1061,1068,1074,1077,1080,1083,1086,1092,1095,1098,1104,1106,1109,1115,1118,1124,1127,1133,1136,1139,1142,1148,1151,1154,1157,1160,1166],[682,683,684],"p",{},"My React codebase used to be cluttered with repetitive state management logic. I often found myself duplicating the same lines of code across multiple components. That changed when I discovered React custom hooks.",[682,686,687],{},"They’re incredible for encapsulating common logic, like data fetching in a clean and maintainable way. Understanding why we use custom hooks in React is key. That’s why in this article, I'll share what I've learned about React hooks and show you how to create your own custom hooks.",[689,690,692],"h2",{"id":691},"hooks-at-a-glance","Hooks at a glance",[682,694,695],{},"React Hooks are reusable functions that let you manage state and lifecycle events in functional components. Think of them as special JavaScript functions with built-in reactivity. They were first introduced in February 2019 as a modern solution to class components and Higher-Order Components (HOCs).",[682,697,698,699,703,704,707,708,711],{},"React provides several built-in hooks like ",[700,701,702],"code",{},"useState",", ",[700,705,706],{},"useReducer",", and ",[700,709,710],{},"useEffect",", but it is also possible to create custom hooks to solve your particular use case.",[689,713,715],{"id":714},"how-to-create-your-first-custom-hook","How to create your first custom hook",[682,717,718],{},"When I created my first custom hooks in React, I learned the hard way that there are some critical rules to follow. Here’s what I discovered:",[720,721,722,738,744],"ol",{},[723,724,725,729,730,733,734,737],"li",{},[726,727,728],"strong",{},"Hook Namimg",": Hook names have to start with ",[700,731,732],{},"use"," (like ",[700,735,736],{},"useCounter",")",[723,739,740,743],{},[726,741,742],{},"Top-level Calls",": Hooks can only be called at the top level of a functional component, meaning no conditional statements or loops.",[723,745,746,749],{},[726,747,748],{},"Functional Components Only",": Hooks can only be called in a React functional components or other hooks — so not in regular JavaScript functions.",[682,751,752,753],{},"These are commonly referred to as the ",[754,755,759],"a",{"href":756,"rel":757},"https:\u002F\u002Freact.dev\u002Freference\u002Frules\u002Frules-of-hooks",[758],"nofollow","Rules of Hooks.",[682,761,762],{},"With these basic rules in mind, let me share a simple example from a project I was working on. Take a look at the following code snippet; I’m sure it’s something many of us have encountered in our own projects before:",[764,765,770],"pre",{"className":766,"code":768,"language":769},[767],"language-text","const [query, setQuery] = useState(\"\");\nconst [data, setData] = useState\u003Cunknown>(null);\n\nuseEffect(() => {\n  const timeout = setTimeout(async () => {\n    await fetch(`https:\u002F\u002Fexample.com?q=${query}`)\n    .then((res) => res.json())\n    .then((data) => setData(data));\n  }, 300);\n\n  return () => clearTimeout(timeout);\n}, [query]);\n","text",[700,771,768],{"__ignoreMap":772},"",[682,774,775],{},"This code fetches data from an API when the query changes, with a 300ms delay. It’s a commonly used pattern in React called debouncing, which helps prevent overloading your servers from rapid user input.",[682,777,778],{},"Since I often use this pattern in various places in my applications, it became clear that extracting this logic into a custom hook would be beneficial.",[682,780,781],{},"Here’s my first attempt at creating a custom hook to encapsulate this logic:",[764,783,786],{"className":784,"code":785,"language":769},[767],"function useDebounceValue\u003CT>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState\u003CT>(value);\n\n  useEffect(() => {\n    const handler = setTimeout(() => {\n    setDebouncedValue(value);\n    }, delay);\n    return () => {\n    clearTimeout(handler);\n    };\n  }, [value, delay]);\n\n  return debouncedValue;\n}\n",[700,787,785],{"__ignoreMap":772},[682,789,790],{},"In this hook, I accept a value and a delay as input.",[682,792,793,794,799],{},"The value is a ",[754,795,798],{"href":796,"rel":797},"https:\u002F\u002Fpieces.app\u002Fblog\u002Funderstanding-typescript-generics",[758],"generic type",", which means TypeScript will automatically infer its type based on what is passed in.",[682,801,802,803,806],{},"Meanwhile, the optional delay parameter determines how long the ",[700,804,805],{},"setTimeout"," should wait before updating the value.",[682,808,809],{},"To utilize my newly created custom hook, I simply import it and call it like any regular JavaScript function:",[764,811,814],{"className":812,"code":813,"language":769},[767],"const [query, setQuery] = useState(\"\");\nconst [data, setData] = useState\u003Cunknown>(null);\nconst debouncedQuery = useDebounceValue(query, 300);\n\nuseEffect(() => {\n  void fetch(`https:\u002F\u002Fexample.com?q=${query}`)\n    .then((res) => res.json())\n    .then((data) => setData(data));\n}, [debouncedQuery]);\n",[700,815,813],{"__ignoreMap":772},[682,817,818],{},"Now, my components are no longer cluttered with repetitive debounce logic. I also realized I could take this a step further by creating a custom hook for the fetch request itself.",[682,820,821],{},"This allows me to reuse the fetching logic across multiple components:",[764,823,826],{"className":824,"code":825,"language":769},[767],"const useFetchData = (query: string) => {\n  const [data, setData] = useState\u003Cunknown>(null);\n\n  useEffect(() => {\n    void fetch(`https:\u002F\u002Fexample.com?q=${query}`)\n    .then((res) => res.json())\n    .then((data) => setData(data));\n  }, [query]);\n\n  return data;\n};\n",[700,827,825],{"__ignoreMap":772},[682,829,830],{},"Just look at how clean the component code appears now:",[764,832,835],{"className":833,"code":834,"language":769},[767],"const [query, setQuery] = useState(\"\");\nconst debouncedQuery = useDebounceValue(query, 300);\nconst data = useFetchData(debouncedQuery);\n",[700,836,834],{"__ignoreMap":772},[682,838,839],{},"More importantly, the code inside my component is now streamlined, allowing me to focus on what I want to achieve without getting lost in implementation details.",[682,841,842],{},"I also discovered another hidden perk: it’s much easier to make migrations from a single location.",[682,844,845],{},"This approach aligns with React's best practices and emphasizes the value of utilizing custom hooks.",[682,847,848],{},"It's also good to note if you're building mobile applications, custom hooks work seamlessly in React Native, just as they do for React on the web.",[682,850,851],{},"Now that I’ve shared my approach to creating custom React hooks, let's dive deeper into common examples widely used in real-world applications.",[689,853,855],{"id":854},"creating-a-custom-data-fetching-hook","Creating a custom data fetching hook",[682,857,858],{},"Data fetching is one of the most common operations in front-end development. You should fetch data from an API on page load, on a button click, or when a user scrolls to the bottom of the page. Despite the varied triggers, I've found that the underlying logic often follows a structured pattern:",[720,860,861,864,867],{},[723,862,863],{},"Accept the API endpoint (URL) as an input parameter.",[723,865,866],{},"Manage and return the fetched data.",[723,868,869],{},"Track the status of the request, including loading, success, and error state",[682,871,872],{},"Typically, I would write this fetching logic directly inside our components, like so:",[764,874,877],{"className":875,"code":772,"language":876,"meta":772,"style":772},"language-typescript shiki shiki-themes github-light github-dark","typescript",[700,878,879],{"__ignoreMap":772},[880,881],"span",{"class":882,"line":883},"line",1,[682,885,886,887,890,891,894,895,898],{},"const ",[880,888,889],{},"data, setData"," = useState\u003Cunknown | null>(null);\nconst ",[880,892,893],{},"loading, setLoading"," = useState(false);\nconst ",[880,896,897],{},"error, setError"," = useState\u003CError | null>(null);",[682,900,901,902,905],{},"useEffect(() => {\n(async () => {\ntry {\nsetLoading(true);\nconst res = await fetch(url);\nconst data = await res.json();\nsetData(data);\n} catch (err) {\nsetError(err as any);\n} finally {\nsetLoading(false);\n}\n})();\n}, ",[880,903,904],{},"url",");",[764,907,910],{"className":908,"code":909,"language":769},[767],"\nI often found it tedious to repeat this logic for every endpoint I needed to fetch. Each new API call meant yet another `useEffect` block with multiple `useState` calls!\n\nNot to mention that I hadn't even implemented any features for aborting ongoing fetch calls or handling refetching.\n\nIn light of this, I decided it was best to abstract this stateful logic into a custom hook. This way, I could get the data with just one line of code by invoking the hook with the appropriate URL.\n\nBefore diving into implementation, I first defined what I wanted the custom hook’s interface to look like:\n\n",[700,911,909],{"__ignoreMap":772},[682,913,914,915],{},"function useFetch",[916,917,918,919],"t",{},"(\nurl: string,\n{ autoInvoke, ...options }?: RequestInit & { autoInvoke: boolean },\n): {\ndata: T | null;\nloading: boolean;\nerror: Error | null;\nrefetch: () => Promise",[920,921,922],"any",{}," | undefined;\nabort: () => void;\n};",[764,924,927],{"className":925,"code":926,"language":769},[767],"\nDon't worry if this looks a bit overwhelming. I'll break it down for you:\n\n- The `useFetch` function accepts a URL and an optional configuration object.\n- The configuration object primarily contains default options for the in-built `fetch`. It is of type `RequestInit` from the Fetch API.\n- The configuration object also includes a custom `autoInvoke` property that I added to determine whether the fetch request should be made automatically.\n- The function returns an object with the fetched `data, loading` state, `error` state, and two functions: `refetch` and `abort.`\n\n1. `refetch` allows you to manually trigger a fetch request.\n2. `abort` lets you cancel an ongoing fetch request.\n\nWith that groundwork established, I implemented the custom hook as follows:\n\n",[700,928,926],{"__ignoreMap":772},[682,930,931],{},"import { useCallback, useEffect, useRef, useState } from \"react\";",[682,933,934],{},"export interface UseFetchOptions extends RequestInit {\nautoInvoke?: boolean;\n}",[682,936,937,938],{},"export function useFetch",[916,939,940,941,943,944,894,946,948],{},"(\nurl: string,\n{ autoInvoke = true, ...options }: UseFetchOptions = {},\n) {\nconst ",[880,942,889],{}," = useState\u003CT | null>(null);\nconst ",[880,945,893],{},[880,947,897],{}," = useState\u003CError | null>(null);\nconst controller = useRef\u003CAbortController | null>(null);",[682,950,951],{},"const refetch = useCallback(() => {\nif (!url) {\nreturn;\n}",[764,953,956],{"className":954,"code":955,"language":769},[767],"if (controller.current) {\ncontroller.current.abort();\n}\n\ncontroller.current = new AbortController();\n\nsetLoading(true);\n\nreturn fetch(url, { signal: controller.current.signal, ...options })\n.then((res) => res.json())\n.then((res) => {\n    setData(res);\n    return res as T;\n})\n.catch((err) => {\n    if (err.name !== \"AbortError\") {\n    setError(err);\n    }\n\n    return err;\n})\n.finally(() => {\n    setLoading(false);\n});\n",[700,957,955],{"__ignoreMap":772},[682,959,960,961,905],{},"}, ",[880,962,904],{},[682,964,965,966,905],{},"const abort = useCallback(() => {\nif (controller.current) {\ncontroller.current.abort(\"\");\n}\n}, ",[880,967],{},[682,969,970],{},"useEffect(() => {\nif (autoInvoke) {\nrefetch();\n}",[764,972,975],{"className":973,"code":974,"language":769},[767],"return () => {\nif (controller.current) {\n    controller.current.abort(\"\");\n}\n};\n",[700,976,974],{"__ignoreMap":772},[682,978,960,979,905],{},[880,980,981],{},"refetch, autoInvoke",[682,983,984],{},"return { data, loading, error, refetch, abort };\n}",[764,986,989],{"className":987,"code":988,"language":769},[767],"\nIn the component where I needed to fetch data, the code can now be simplified to just one line.\n\n",[700,990,988],{"__ignoreMap":772},[682,992,993,994,996,997,1001],{},"const { data, loading, error, refetch, abort } = useFetch\u003CItem",[880,995],{},">(\n\"",[754,998,999],{"href":999,"rel":1000},"https:\u002F\u002Fexample.com\u002Fitems\u002F",[758],"\",\n);",[764,1003,1006],{"className":1004,"code":1005,"language":769},[767],"\nNow, I can fetch data from as many API endpoints as I want without cluttering my UI components. This is the power of custom hooks in React.\n\nI personally use this useFetch hook in many of my projects, and it has become a staple in my codebase. To effectively store and document precious reusable code, I [save it as code snippets](https:\u002F\u002Fpieces.app\u002Ffeatures\u002Fdrive\u002Fsave-code-snippets) using Pieces.\n\n[Long-term memory](https:\u002F\u002Fpieces.app\u002Ffeatures\u002Fcopilot\u002Flong-term-memory) helps me save, search, and [enrich my code snippets](https:\u002F\u002Fpieces.app\u002Ffeatures\u002Fdrive\u002Fenrich-code-snippets) across all my projects.\n\nWhenever I save a code snippet, Pieces automatically adds meta tags, a title, and a descriptive summary explaining what the code does for easy findability. It even scans for any potential sensitive information or Personally Identifiable Information (PII).\n\nWith my code snippets organized and easily accessible, I can quickly retrieve and use them whenever I need. You can check out the custom fetch hook we just created as a snippet [here](https:\u002F\u002F1cb3ec25-9428-464f-85f1-d2e34661bab4.pieces.cloud\u002F?p=32234ab6e0).\n\n## Creating a custom hook to track the mouse position\n\nI can recall multiple scenarios where I needed to track the mouse position inside a specific [DOM element](https:\u002F\u002Fpieces.app\u002Fblog\u002Fdom-virtual-dom-react). This could be for a drag-and-drop feature, custom cursor, or other interactive use cases.\n\nIn any case, a custom hook that could return the x and y coordinates, allowing me to track the mouse movement effectively,  would have been a godsend.\n\nSo, here we go—I’m going to create that hook!\n\n**Note: This is a super-simplified version just to get started.**\n\n",[700,1007,1005],{"__ignoreMap":772},[682,1009,1010],{},"import { MouseEvent, useEffect, useRef, useState } from 'react';",[682,1012,1013,1014],{},"export function useMouse",[916,1015,1016,1017,1020,1021],{"extends":772,"htmlelement":920},"() {\nconst ",[880,1018,1019],{},"position, setPosition"," = useState({ x: 0, y: 0 });\nconst ref = useRef",[916,1022,1023],{},"(null);",[682,1025,1026,1027],{},"const setMousePosition = (event: MouseEvent",[1028,1029,1030],"html-element",{},") => {\nif (ref.current) {\nconst rect = event.currentTarget.getBoundingClientRect();\nconst x = Math.max(\n0,\nMath.round(event.pageX - rect.left - window.scrollX)\n);\nconst y = Math.max(\n0,\nMath.round(event.pageY - rect.top - window.scrollY)\n);\nsetPosition({ x, y });\n} else {\nsetPosition({ x: event.clientX, y: event.clientY });\n}\n};",[682,1032,1033,1034,905],{},"useEffect(() => {\nconst element = ref?.current ? ref.current : document;\nelement.addEventListener('mousemove', setMousePosition as any);\nreturn () => {\nelement.removeEventListener('mousemove', setMousePosition as any);\n};\n}, ",[880,1035,1036],{},"ref.current",[682,1038,1039],{},"return { ref, ...position };\n}",[764,1041,1044],{"className":1042,"code":1043,"language":769},[767],"\nHere is a brief explanation for the above code:\n\n- First, I create a `useState` called `position` to hold the x and y coordinates of the mouse, initializing them to 0.\n- I then create a `ref` using `useRef` to allow me to attach the mouse tracking to a specific DOM element.\n- I define the `setMousePosition` function, which serves as the event handler for mouse move events. If the `ref`is attached to an element, I calculate the mouse position relative to that element using `getBoundingClientRect.`The calculations ensure that the coordinates are always non-negative and take into account window scrolling.\n- I use `useEffect` to add an event listener for mouse movements when the component mounts. This listener calls `setMousePosition`. If the `ref` is not attached to any element, I fall back to tracking the mouse position relative to the viewport.\n- When the component unmounts or the `ref` changes, I clean up by removing the event listener.\n\nHere is how I consume this hook while targeting mouse tracking on a specific `\u003Cdiv>` element:\n\n",[700,1045,1043],{"__ignoreMap":772},[682,1047,1048,1049],{},"const { ref, x, y } = useMouse",[1050,1051,1052],"html-div-element",{},"();",[682,1054,1055,1056,1060],{},"return (\n\u003Cdiv\nref={ref}\nstyle=",[1057,1058],"binding",{"value":1059},"width: '100%', height: '400px', border: '1px dashed black', position: 'relative'","\n>\n",[682,1062,1063,1064,1067],{},"Mouse Position: {",[700,1065,1066],{},"X: ${x}, Y: ${y}","}",[764,1069,1072],{"className":1070,"code":1071,"language":769},[767],"\nHere, I just call the `useMouse` hook and attach the ref to the `\u003Cdiv>` element to track the mouse position inside. The coordinates are displayed on the screen as the mouse moves within the element.\n\nIf I didn’t attach the provided ref to any DOM element, the tracked mouse position would default to being relative to the root document.\n\n## Testing custom hooks in React\n\nIt's crucial to test the code written in an application to ensure it behaves as expected across different scenarios. When it comes to custom hooks, I test them just like I would test any regular React component.\n\nIn fact, they're often easier to test because they are isolated and functionally pure (meaning they do not produce side effects).\n\nIn this section, I'll share the techniques and different tools available I use daily for writing effective tests.\n\nTo give a quick refresher, there are three main types of tests: **Unit, Integration, and End-to-End (E2E) testing**.\n\nAs you move from Unit to Integration to E2E, the time and resources required to write and maintain these tests increase, but so does your confidence in the application’s behavior.\n\nHere, I'll primarily focus on unit testing.\n\nUnit testing involves testing small, isolated pieces of code to verify that each unit functions correctly. To assert these behaviors, I typically use a testing framework like [Vitest](https:\u002F\u002Fvitest.dev\u002F).\n\nMeanwhile, to render custom hooks in my tests, I use [React Testing Library](https:\u002F\u002Ftesting-library.com\u002Fdocs\u002Freact-testing-library\u002Fintro\u002F).\n\nIf you like to learn more about different types of testing, check out [9 Types of API Testing to Ensure Performance and Security](https:\u002F\u002Fpieces.app\u002Fblog\u002F9-types-of-api-testing-to-ensure-performance-and-security) and keep an eye on the new one about [unit testing llms](https:\u002F\u002Fpieces.app\u002Fblog\u002Funit-testing-llms).\n\nTo illustrate a real-world scenario for testing a custom hook, consider my application where I have a hook designed to retrieve an access token for a user on an authenticated route. For this example, let’s call it `useAuthState.`\n\nThe token is stored in local storage, and if it doesn’t exist when the hook is called, a new one is fetched.\n\nThinking through its use case, there are only three possible states:\n\n1. The token is present in local storage; it should immediately return the token.\n2. The token is absent; it should fetch a new one.\n3. An error occurs during the fetch; it should return the error.\n\nWith this outline in mind, I can begin implementing the test cases. To speed up this process, I'll turn to [Pieces Copilot](https:\u002F\u002Fpieces.app\u002Ffeatures\u002Fcopilot) to scaffold the initial test suite.\n\nPieces Copilot is an extension of Pieces bringing the power of AI directly into my IDE. It saves me time and effort by providing [context-aware suggestions](https:\u002F\u002Fpieces.app\u002Ffeatures\u002Fcopilot\u002Fcontext-awareness) tailored to my codebase.\n\nHere's how Iuse Pieces Copilot directly in the editor to generate test cases for the `useAuthState` hook:\n\n\nIn this case, I asked Pieces Copilot using my current selection (the entire file) to generate unit tests for the `useAuthState` hook, specifying Vitest as the testing framework and React Testing Library (RTL) for rendering. Pieces provided a series of test cases that validated the hook's behavior.\n\nHere's the code it generated:\n\n",[700,1073,1071],{"__ignoreMap":772},[682,1075,1076],{},"import { renderHook, waitFor } from \"@testing-library\u002Freact\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { useAuthState } from \".\u002FuseAuthState\";",[682,1078,1079],{},"const LOCAL_STORAGE_KEY = \"accessToken\";\nconst LOCAL_STORAGE_VALUE = \"test-token\";",[682,1081,1082],{},"describe(\"useAuthState\", () => {\nafterEach(() => {\nwindow.localStorage.clear();\n});",[682,1084,1085],{},"it(\"should load accessToken from localStorage if present\", () => {\nwindow.localStorage.setItem(LOCAL_STORAGE_KEY, LOCAL_STORAGE_VALUE);\nconst { result } = renderHook(() => useAuthState());",[764,1087,1090],{"className":1088,"code":1089,"language":769},[767],"expect(result.current.accessToken).toBe(LOCAL_STORAGE_VALUE);\nexpect(result.current.isError).toBe(false);\nexpect(result.current.error).toBeNull();\n",[700,1091,1089],{"__ignoreMap":772},[682,1093,1094],{},"});",[682,1096,1097],{},"it(\"should fetch and set accessToken if not in localStorage\", async () => {\nconst { result } = renderHook(() => useAuthState());",[764,1099,1102],{"className":1100,"code":1101,"language":769},[767],"await waitFor(() => {\nexpect(result.current.accessToken).toBe(LOCAL_STORAGE_VALUE);\nexpect(result.current.isError).toBe(false);\nexpect(result.current.error).toBeNull();\n});\n",[700,1103,1101],{"__ignoreMap":772},[682,1105,1094],{},[682,1107,1108],{},"it(\"should handle fetch error and set isError to true\", async () => {\nconst { result } = renderHook(() => useAuthState());",[764,1110,1113],{"className":1111,"code":1112,"language":769},[767],"await waitFor(() => {\nexpect(result.current.isError).toBe(true);\nexpect(result.current.error).toBe(\"Fetch error\");\n});\n",[700,1114,1112],{"__ignoreMap":772},[682,1116,1117],{},"});\n});",[764,1119,1122],{"className":1120,"code":1121,"language":769},[767],"\nThese tests thoroughly validate the hook's behavior across all happy paths, including negative scenarios. To run the test I use:\n\n",[700,1123,1121],{"__ignoreMap":772},[682,1125,1126],{},"pnpm vitest run",[764,1128,1131],{"className":1129,"code":1130,"language":769},[767],"\nUpon running the test suite, all tests pass successfully.\n\n![Terminal displaying successful test results after running the test suite](https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fcreating-custom-hooks-reactjs\u002Fimg-002.png)\n\nHowever, I’m not done yet — my tests currently depend on an external dependency: the API fetch request.\n\nWhen testing custom hooks, it's common to encounter situations where you might need to mock external dependencies as you want to control their behavior.\n\nIn this case, I’ll need to mock the fetch API to simulate both success and failure scenarios. I can use the `vi.spyOn` API provided by Vitest to intercept global fetch calls and return mock values.\n\nThanks to Pieces long-term memory, I can easily ask it to modify my tests to mock the fetch API.\n\nThis time, I’ll use the [Pieces for Developers Desktop App](https:\u002F\u002Fdocs.pieces.app\u002Finstallation-getting-started\u002Fwhat-am-i-installing), where my chat history is fully preserved. This highlights how seamlessly Pieces integrates with any development workflow.\n\n![Pieces for Developers Desktop App interface showing a chat history and code modifications to mock the fetch API in test cases](https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fcreating-custom-hooks-reactjs\u002Fimg-003.png)\n\nHere's the result after updating the tests:\n\n",[700,1132,1130],{"__ignoreMap":772},[682,1134,1135],{},"\u002F\u002F ...\nimport { vi } from \"vitest\";",[682,1137,1138],{},"const mockFetchSuccess = (data: string) =>\nvi.spyOn(global, \"fetch\").mockResolvedValue({\njson: vi.fn().mockResolvedValue(data),\n} as unknown as Response);",[682,1140,1141],{},"const mockFetchFailure = () =>\nvi.spyOn(global, \"fetch\").mockRejectedValue(\"Fetch error\");",[764,1143,1146],{"className":1144,"code":1145,"language":769},[767],"\nI’ve added two helper functions: `mockFetchSuccess` and `mockFetchFailure`. The former resolves with the provided data, while the latter rejects with an error message.\n\nNext, I called the mocked functions in their respective test cases and restored the original fetch function after each test to avoid any possible side effects.\n\n",[700,1147,1145],{"__ignoreMap":772},[682,1149,1150],{},"describe(\"useAuthState\", () => {\nafterEach(() => {\n\u002F\u002F ...\nvi.restoreAllMocks();\n});",[682,1152,1153],{},"it(\"should load accessToken from localStorage if present\", () => {\n\u002F\u002F ...\n});",[682,1155,1156],{},"it(\"should fetch and set accessToken if not in localStorage\", async () => {\nmockFetchSuccess(ACCESS_TOKEN_LOCAL_STORAGE_VALUE);\n\u002F\u002F ...\n});",[682,1158,1159],{},"it(\"should handle fetch error and set isError to true\", async () => {\nmockFetchFailure();\n\u002F\u002F ...\n});\n});",[764,1161,1164],{"className":1162,"code":1163,"language":769},[767],"\nWith these mocks in place, our tests become deterministic. I can run them as many times as I want in any environment without worrying about external API failures. If you're looking to learn more about Unit testing in React, check out [this modern guide](https:\u002F\u002Fpieces.app\u002Fblog\u002Fa-modern-guide-to-react-unit-testing).\n\n## Conclusion\n\nIn this article, we explored React hooks, the rules that govern them, and how they serve as a powerful tool for abstracting complex logic. Additionally, we covered how to create our own custom hooks and effectively test them using Pieces Copilot as an assistant.\n\nPieces can be your development companion – from saving code snippets for future use and generating test cases to chatting with the copilot to help you modify existing code, Pieces can help at every step of the way.\n\nI hope I was able to demonstrate how custom react hooks are a game-changer. If you're looking to learn more, the [React official guide](https:\u002F\u002Freact.dev\u002Flearn\u002Freusing-logic-with-custom-hooks) is a helpful resource.\n\nThanks for reading!\n\nFind some time to read these articles too:\n\n1. [Collaboration tools that you need to know for productive remote work](https:\u002F\u002Fpieces.app\u002Fblog\u002Ftop-5-code-collaboration-tools-for-remote-work)\n2. [How to build a server in Dart: CTO insights](https:\u002F\u002Fpieces.app\u002Fblog\u002Fhow-to-build-a-server-in-dart)\n3. [Practical API methods](https:\u002F\u002Fpieces.app\u002Fblog\u002Fpractical-guide-api-methods) & [guide to API caching](https:\u002F\u002Fpieces.app\u002Fblog\u002Fapi-caching-techniques-for-better-performance)\n\n*This article was first published on August, 5th, 2022, and was improved by Emmanuel Isenah as of February 14th, 2025, to improve your experience and share the latest information.*\n",[700,1165,1163],{"__ignoreMap":772},[1167,1168,1169],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":772,"searchDepth":1171,"depth":1171,"links":1172},2,[1173,1174,1175],{"id":691,"depth":1171,"text":692},{"id":714,"depth":1171,"text":715},{"id":854,"depth":1171,"text":855},"Try AI companion for free","https:\u002F\u002Fbeta.docs.pieces.app\u002Fproducts\u002Fmeet-pieces","Software Development","2025-02-15T00:00:00.000Z","Discover what custom React hooks are, how to create one, and why they are essential for writing reusable components.",false,"md","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fcreating-custom-hooks-reactjs\u002Fhero.png","A large hook on a platform next to a body of water.",{},null,"\u002Fblog\u002Fcreating-custom-hooks-reactjs",{"title":673,"description":1180},"blog\u002Fcreating-custom-hooks-reactjs","74JOfZ8ccxAdTz0lloUGdfMExcnw6NLi_O7bs3YKcX0",{"id":1192,"title":674,"body":1193,"description":772,"draft":1181,"extension":1182,"meta":1197,"navigation":32,"path":1198,"photo":1199,"photoAlt":1186,"seo":1200,"stem":1201,"__hash__":1202},"authors\u002Fauthors\u002Framan-hundal.md",{"type":679,"value":1194,"toc":1195},[],{"title":772,"searchDepth":1171,"depth":1171,"links":1196},[],{},"\u002Fauthors\u002Framan-hundal","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fauthors\u002Framan-hundal.jpeg",{"title":674,"description":772},"authors\u002Framan-hundal","lPCfuQIsZeQkzqLHzL9n9IXlR9buEK9et5U6TWBUULg",{"left":1204,"top":1204,"width":1205,"height":1205,"rotate":1204,"vFlip":1181,"hFlip":1181,"body":1206},0,24,"\u003Cpath fill=\"currentColor\" d=\"m7.825 13l4.9 4.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-6.6-6.6q-.15-.15-.213-.325T4.426 12t.063-.375t.212-.325l6.6-6.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L7.825 11H19q.425 0 .713.288T20 12t-.288.713T19 13z\"\u002F>"]