[{"data":1,"prerenderedAt":874},["ShallowReactive",2],{"blog:/blog/add-realtime-to-react":3},{"id":4,"title":5,"author":6,"body":7,"category":863,"date":864,"description":865,"excerpt":866,"extension":867,"meta":868,"navigation":96,"path":869,"readTime":870,"seo":871,"stem":872,"__hash__":873},"blog/blog/add-realtime-to-react.md","Add Realtime to a React App with NoLag","Henco Burger",{"type":8,"value":9,"toc":855},"minimark",[10,20,25,51,55,591,595,807,811,822,826,829,833,851],[11,12,13,14,19],"p",{},"React apps built with Vite, Create React App, or any other bundler can add realtime with a small custom hook. The hook owns the connection lifecycle so your components just render messages and call a publish function. This works in any React setup; if you are on Next.js, see the ",[15,16,18],"a",{"href":17},"/blog/add-realtime-to-nextjs","Next.js guide"," for the extra server-token step.",[21,22,24],"h2",{"id":23},"_1-install-the-sdk","1. Install the SDK",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-bash shiki shiki-themes github-light github-dark","npm install @nolag/js-sdk\n","bash","",[33,34,35],"code",{"__ignoreMap":31},[36,37,40,44,48],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sScJk","npm",[36,45,47],{"class":46},"sZZnC"," install",[36,49,50],{"class":46}," @nolag/js-sdk\n",[21,52,54],{"id":53},"_2-a-reusable-hook","2. A reusable hook",[26,56,60],{"className":57,"code":58,"language":59,"meta":31,"style":31},"language-tsx shiki shiki-themes github-light github-dark","import { useEffect, useRef, useState, useCallback } from 'react'\nimport { NoLag } from '@nolag/js-sdk'\n\nexport function useNoLagRoom(token: string, app: string, roomName: string, topic: string) {\n  const [messages, setMessages] = useState\u003Cany[]>([])\n  const roomRef = useRef\u003Cany>(null)\n\n  useEffect(() => {\n    let client: ReturnType\u003Ctypeof NoLag> | undefined\n    let active = true\n\n    ;(async () => {\n      client = NoLag(token)\n      await client.connect()\n      if (!active) return\n\n      const room = client.setApp(app).setRoom(roomName)\n      room.subscribe(topic)\n      room.on(topic, (data: any) => setMessages((m) => [...m, data]))\n      roomRef.current = room\n    })()\n\n    return () => {\n      active = false\n      roomRef.current = null\n      client?.disconnect?.()\n    }\n  }, [token, app, roomName, topic])\n\n  const publish = useCallback(\n    (data: unknown) => roomRef.current?.emit(topic, data),\n    [topic],\n  )\n\n  return { messages, publish }\n}\n","tsx",[33,61,62,78,91,98,155,190,217,222,237,265,278,283,299,313,328,346,351,376,388,433,444,450,455,467,478,488,500,506,512,517,533,559,565,571,576,585],{"__ignoreMap":31},[36,63,64,68,72,75],{"class":38,"line":39},[36,65,67],{"class":66},"szBVR","import",[36,69,71],{"class":70},"sVt8B"," { useEffect, useRef, useState, useCallback } ",[36,73,74],{"class":66},"from",[36,76,77],{"class":46}," 'react'\n",[36,79,81,83,86,88],{"class":38,"line":80},2,[36,82,67],{"class":66},[36,84,85],{"class":70}," { NoLag } ",[36,87,74],{"class":66},[36,89,90],{"class":46}," '@nolag/js-sdk'\n",[36,92,94],{"class":38,"line":93},3,[36,95,97],{"emptyLinePlaceholder":96},true,"\n",[36,99,101,104,107,110,113,117,120,124,127,130,132,134,136,139,141,143,145,148,150,152],{"class":38,"line":100},4,[36,102,103],{"class":66},"export",[36,105,106],{"class":66}," function",[36,108,109],{"class":42}," useNoLagRoom",[36,111,112],{"class":70},"(",[36,114,116],{"class":115},"s4XuR","token",[36,118,119],{"class":66},":",[36,121,123],{"class":122},"sj4cs"," string",[36,125,126],{"class":70},", ",[36,128,129],{"class":115},"app",[36,131,119],{"class":66},[36,133,123],{"class":122},[36,135,126],{"class":70},[36,137,138],{"class":115},"roomName",[36,140,119],{"class":66},[36,142,123],{"class":122},[36,144,126],{"class":70},[36,146,147],{"class":115},"topic",[36,149,119],{"class":66},[36,151,123],{"class":122},[36,153,154],{"class":70},") {\n",[36,156,158,161,164,167,169,172,175,178,181,184,187],{"class":38,"line":157},5,[36,159,160],{"class":66},"  const",[36,162,163],{"class":70}," [",[36,165,166],{"class":122},"messages",[36,168,126],{"class":70},[36,170,171],{"class":122},"setMessages",[36,173,174],{"class":70},"] ",[36,176,177],{"class":66},"=",[36,179,180],{"class":42}," useState",[36,182,183],{"class":70},"\u003C",[36,185,186],{"class":122},"any",[36,188,189],{"class":70},"[]>([])\n",[36,191,193,195,198,201,204,206,208,211,214],{"class":38,"line":192},6,[36,194,160],{"class":66},[36,196,197],{"class":122}," roomRef",[36,199,200],{"class":66}," =",[36,202,203],{"class":42}," useRef",[36,205,183],{"class":70},[36,207,186],{"class":122},[36,209,210],{"class":70},">(",[36,212,213],{"class":122},"null",[36,215,216],{"class":70},")\n",[36,218,220],{"class":38,"line":219},7,[36,221,97],{"emptyLinePlaceholder":96},[36,223,225,228,231,234],{"class":38,"line":224},8,[36,226,227],{"class":42},"  useEffect",[36,229,230],{"class":70},"(() ",[36,232,233],{"class":66},"=>",[36,235,236],{"class":70}," {\n",[36,238,240,243,246,248,251,253,256,259,262],{"class":38,"line":239},9,[36,241,242],{"class":66},"    let",[36,244,245],{"class":70}," client",[36,247,119],{"class":66},[36,249,250],{"class":42}," ReturnType",[36,252,183],{"class":70},[36,254,255],{"class":66},"typeof",[36,257,258],{"class":70}," NoLag> ",[36,260,261],{"class":66},"|",[36,263,264],{"class":122}," undefined\n",[36,266,268,270,273,275],{"class":38,"line":267},10,[36,269,242],{"class":66},[36,271,272],{"class":70}," active ",[36,274,177],{"class":66},[36,276,277],{"class":122}," true\n",[36,279,281],{"class":38,"line":280},11,[36,282,97],{"emptyLinePlaceholder":96},[36,284,286,289,292,295,297],{"class":38,"line":285},12,[36,287,288],{"class":70},"    ;(",[36,290,291],{"class":66},"async",[36,293,294],{"class":70}," () ",[36,296,233],{"class":66},[36,298,236],{"class":70},[36,300,302,305,307,310],{"class":38,"line":301},13,[36,303,304],{"class":70},"      client ",[36,306,177],{"class":66},[36,308,309],{"class":42}," NoLag",[36,311,312],{"class":70},"(token)\n",[36,314,316,319,322,325],{"class":38,"line":315},14,[36,317,318],{"class":66},"      await",[36,320,321],{"class":70}," client.",[36,323,324],{"class":42},"connect",[36,326,327],{"class":70},"()\n",[36,329,331,334,337,340,343],{"class":38,"line":330},15,[36,332,333],{"class":66},"      if",[36,335,336],{"class":70}," (",[36,338,339],{"class":66},"!",[36,341,342],{"class":70},"active) ",[36,344,345],{"class":66},"return\n",[36,347,349],{"class":38,"line":348},16,[36,350,97],{"emptyLinePlaceholder":96},[36,352,354,357,360,362,364,367,370,373],{"class":38,"line":353},17,[36,355,356],{"class":66},"      const",[36,358,359],{"class":122}," room",[36,361,200],{"class":66},[36,363,321],{"class":70},[36,365,366],{"class":42},"setApp",[36,368,369],{"class":70},"(app).",[36,371,372],{"class":42},"setRoom",[36,374,375],{"class":70},"(roomName)\n",[36,377,379,382,385],{"class":38,"line":378},18,[36,380,381],{"class":70},"      room.",[36,383,384],{"class":42},"subscribe",[36,386,387],{"class":70},"(topic)\n",[36,389,391,393,396,399,402,404,407,410,412,415,418,421,423,425,427,430],{"class":38,"line":390},19,[36,392,381],{"class":70},[36,394,395],{"class":42},"on",[36,397,398],{"class":70},"(topic, (",[36,400,401],{"class":115},"data",[36,403,119],{"class":66},[36,405,406],{"class":122}," any",[36,408,409],{"class":70},") ",[36,411,233],{"class":66},[36,413,414],{"class":42}," setMessages",[36,416,417],{"class":70},"((",[36,419,420],{"class":115},"m",[36,422,409],{"class":70},[36,424,233],{"class":66},[36,426,163],{"class":70},[36,428,429],{"class":66},"...",[36,431,432],{"class":70},"m, data]))\n",[36,434,436,439,441],{"class":38,"line":435},20,[36,437,438],{"class":70},"      roomRef.current ",[36,440,177],{"class":66},[36,442,443],{"class":70}," room\n",[36,445,447],{"class":38,"line":446},21,[36,448,449],{"class":70},"    })()\n",[36,451,453],{"class":38,"line":452},22,[36,454,97],{"emptyLinePlaceholder":96},[36,456,458,461,463,465],{"class":38,"line":457},23,[36,459,460],{"class":66},"    return",[36,462,294],{"class":70},[36,464,233],{"class":66},[36,466,236],{"class":70},[36,468,470,473,475],{"class":38,"line":469},24,[36,471,472],{"class":70},"      active ",[36,474,177],{"class":66},[36,476,477],{"class":122}," false\n",[36,479,481,483,485],{"class":38,"line":480},25,[36,482,438],{"class":70},[36,484,177],{"class":66},[36,486,487],{"class":122}," null\n",[36,489,491,494,497],{"class":38,"line":490},26,[36,492,493],{"class":70},"      client?.",[36,495,496],{"class":42},"disconnect",[36,498,499],{"class":70},"?.()\n",[36,501,503],{"class":38,"line":502},27,[36,504,505],{"class":70},"    }\n",[36,507,509],{"class":38,"line":508},28,[36,510,511],{"class":70},"  }, [token, app, roomName, topic])\n",[36,513,515],{"class":38,"line":514},29,[36,516,97],{"emptyLinePlaceholder":96},[36,518,520,522,525,527,530],{"class":38,"line":519},30,[36,521,160],{"class":66},[36,523,524],{"class":122}," publish",[36,526,200],{"class":66},[36,528,529],{"class":42}," useCallback",[36,531,532],{"class":70},"(\n",[36,534,536,539,541,543,546,548,550,553,556],{"class":38,"line":535},31,[36,537,538],{"class":70},"    (",[36,540,401],{"class":115},[36,542,119],{"class":66},[36,544,545],{"class":122}," unknown",[36,547,409],{"class":70},[36,549,233],{"class":66},[36,551,552],{"class":70}," roomRef.current?.",[36,554,555],{"class":42},"emit",[36,557,558],{"class":70},"(topic, data),\n",[36,560,562],{"class":38,"line":561},32,[36,563,564],{"class":70},"    [topic],\n",[36,566,568],{"class":38,"line":567},33,[36,569,570],{"class":70},"  )\n",[36,572,574],{"class":38,"line":573},34,[36,575,97],{"emptyLinePlaceholder":96},[36,577,579,582],{"class":38,"line":578},35,[36,580,581],{"class":66},"  return",[36,583,584],{"class":70}," { messages, publish }\n",[36,586,588],{"class":38,"line":587},36,[36,589,590],{"class":70},"}\n",[21,592,594],{"id":593},"_3-use-it-in-a-component","3. Use it in a component",[26,596,598],{"className":57,"code":597,"language":59,"meta":31,"style":31},"function Chat({ token }: { token: string }) {\n  const { messages, publish } = useNoLagRoom(token, 'chat', 'general', 'message')\n\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {messages.map((m, i) => (\n          \u003Cli key={i}>{m.text}\u003C/li>\n        ))}\n      \u003C/ul>\n      \u003Cbutton onClick={() => publish({ text: 'Hello!' })}>Send\u003C/button>\n    \u003C/div>\n  )\n}\n",[33,599,600,630,668,672,679,691,701,724,744,749,758,790,799,803],{"__ignoreMap":31},[36,601,602,605,608,611,613,616,618,621,623,625,627],{"class":38,"line":39},[36,603,604],{"class":66},"function",[36,606,607],{"class":42}," Chat",[36,609,610],{"class":70},"({ ",[36,612,116],{"class":115},[36,614,615],{"class":70}," }",[36,617,119],{"class":66},[36,619,620],{"class":70}," { ",[36,622,116],{"class":115},[36,624,119],{"class":66},[36,626,123],{"class":122},[36,628,629],{"class":70}," }) {\n",[36,631,632,634,636,638,640,643,646,648,650,653,656,658,661,663,666],{"class":38,"line":80},[36,633,160],{"class":66},[36,635,620],{"class":70},[36,637,166],{"class":122},[36,639,126],{"class":70},[36,641,642],{"class":122},"publish",[36,644,645],{"class":70}," } ",[36,647,177],{"class":66},[36,649,109],{"class":42},[36,651,652],{"class":70},"(token, ",[36,654,655],{"class":46},"'chat'",[36,657,126],{"class":70},[36,659,660],{"class":46},"'general'",[36,662,126],{"class":70},[36,664,665],{"class":46},"'message'",[36,667,216],{"class":70},[36,669,670],{"class":38,"line":93},[36,671,97],{"emptyLinePlaceholder":96},[36,673,674,676],{"class":38,"line":100},[36,675,581],{"class":66},[36,677,678],{"class":70}," (\n",[36,680,681,684,688],{"class":38,"line":157},[36,682,683],{"class":70},"    \u003C",[36,685,687],{"class":686},"s9eBZ","div",[36,689,690],{"class":70},">\n",[36,692,693,696,699],{"class":38,"line":192},[36,694,695],{"class":70},"      \u003C",[36,697,698],{"class":686},"ul",[36,700,690],{"class":70},[36,702,703,706,709,711,713,715,718,720,722],{"class":38,"line":219},[36,704,705],{"class":70},"        {messages.",[36,707,708],{"class":42},"map",[36,710,417],{"class":70},[36,712,420],{"class":115},[36,714,126],{"class":70},[36,716,717],{"class":115},"i",[36,719,409],{"class":70},[36,721,233],{"class":66},[36,723,678],{"class":70},[36,725,726,729,732,735,737,740,742],{"class":38,"line":224},[36,727,728],{"class":70},"          \u003C",[36,730,731],{"class":686},"li",[36,733,734],{"class":42}," key",[36,736,177],{"class":66},[36,738,739],{"class":70},"{i}>{m.text}\u003C/",[36,741,731],{"class":686},[36,743,690],{"class":70},[36,745,746],{"class":38,"line":239},[36,747,748],{"class":70},"        ))}\n",[36,750,751,754,756],{"class":38,"line":267},[36,752,753],{"class":70},"      \u003C/",[36,755,698],{"class":686},[36,757,690],{"class":70},[36,759,760,762,765,768,770,773,775,777,780,783,786,788],{"class":38,"line":280},[36,761,695],{"class":70},[36,763,764],{"class":686},"button",[36,766,767],{"class":42}," onClick",[36,769,177],{"class":66},[36,771,772],{"class":70},"{() ",[36,774,233],{"class":66},[36,776,524],{"class":42},[36,778,779],{"class":70},"({ text: ",[36,781,782],{"class":46},"'Hello!'",[36,784,785],{"class":70}," })}>Send\u003C/",[36,787,764],{"class":686},[36,789,690],{"class":70},[36,791,792,795,797],{"class":38,"line":285},[36,793,794],{"class":70},"    \u003C/",[36,796,687],{"class":686},[36,798,690],{"class":70},[36,800,801],{"class":38,"line":301},[36,802,570],{"class":70},[36,804,805],{"class":38,"line":315},[36,806,590],{"class":70},[21,808,810],{"id":809},"keep-the-token-off-the-client","Keep the token off the client",[11,812,813,814,816,817,821],{},"Fetch the ",[33,815,116],{}," from your own backend rather than hardcoding a project key in the bundle. Any server can mint one by calling the NoLag REST API, and for browser sessions the production pattern is a short-lived ",[15,818,820],{"href":819},"/docs/client-tokens","client token"," scoped to what that user may do.",[21,823,825],{"id":824},"why-the-cleanup-matters","Why the cleanup matters",[11,827,828],{},"The hook's cleanup function runs on unmount and when the dependencies change. Clearing the room ref and disconnecting prevents duplicate subscriptions and leaked WebSocket connections as users navigate around your app.",[21,830,832],{"id":831},"next-steps","Next steps",[698,834,835,843],{},[731,836,837,838,842],{},"Walk through the full flow in the ",[15,839,841],{"href":840},"/docs/getting-started","5-minute quick start",".",[731,844,845,846,850],{},"Use a ",[15,847,849],{"href":848},"/docs/high-level-sdks","blueprint SDK"," if you would rather not wire chat, notifications, or dashboards by hand.",[852,853,854],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":31,"searchDepth":80,"depth":80,"links":856},[857,858,859,860,861,862],{"id":23,"depth":80,"text":24},{"id":53,"depth":80,"text":54},{"id":593,"depth":80,"text":594},{"id":809,"depth":80,"text":810},{"id":824,"depth":80,"text":825},{"id":831,"depth":80,"text":832},"Integration","2026-07-28","How to add realtime messaging to any React app with NoLag using a small reusable hook that handles connect, subscribe, and cleanup.",null,"md",{},"/blog/add-realtime-to-react","6 min read",{"title":5,"description":865},"blog/add-realtime-to-react","36HeBOje0HLI8QV7oMYvtJ8ScahqfdCfVq8x8z2uIRU",1788160339717]