Use this guide to show a Storyteller view in a React or Next.js app. Initialize
the SDK once for the page, create the view after the component mounts, and
destroy the view when the component unmounts.
The first call starts initialize. Later calls return the same promise. If
initialize fails, the helper clears the cached promise, so the next call tries
again.
Each initialize call resets the global theme
(Storyteller.sharedInstance.theme) to the defaults. If you use a global
theme, set it after initialize resolves, for example in a .then() inside
startStoryteller(). A theme set in a view's configuration is kept. See
Customize themes.
When the signed-in user changes, call initialize again as described in
Identify and personalize users, then set the
global theme again.
Create the view after startStoryteller() resolves, and destroy it in the
effect cleanup:
exportfunctionStoryRow(){useEffect(()=>{letisMounted=true;letstoryRow;startStoryteller().then(()=>{if(isMounted){storyRow=newStoryteller.StorytellerStoriesRowView('storyteller-stories-row');}}).catch((error)=>console.error('Storyteller could not start.',error));return()=>{isMounted=false;storyRow?.destroy();};},[]);return<divid="storyteller-stories-row"style={{height:200}}/>;}
In development, React Strict Mode runs each effect, its cleanup, and the effect
again. The cleanup sets isMounted to false, so only the second effect
creates a view. Both effects get the same promise from startStoryteller(), so
the SDK initializes once.
Each view needs a container ID that is unique on the page. If you render the
component more than once at the same time, pass a different ID to each
instance. The ID must
follow the container ID rules.
Keep the view in a Client Component so the SDK runs in the browser. Add
'use client' at the top of the component file, then use the same StoryRow
component as in the React section:
{"slug": "getting-started-react-nextjs", "page_title": "Use React or Next.js", "page_url": "getting-started/react-nextjs/", "canonical_url": "/web/getting-started/react-nextjs/", "markdown": "# Use React or Next.js\n\nUse this guide to show a Storyteller view in a React or Next.js app. Initialize\nthe SDK once for the page, create the view after the component mounts, and\ndestroy the view when the component unmounts.\n\nInstall the SDK with [npm](npm.md) first.\n\n## Initialize the SDK once {#initialize-the-sdk-once}\n\nAdd a module that calls `initialize` once and shares its promise with every\ncomponent:\n\n```javascript\n// storyteller.js\nimport * as Storyteller from '@getstoryteller/storyteller-sdk-javascript';\n\nlet initialization;\n\nexport function startStoryteller() {\n if (!initialization) {\n initialization = Storyteller.sharedInstance\n .initialize('demo-api-key')\n .catch((error) => {\n initialization = undefined;\n throw error;\n });\n }\n\n return initialization;\n}\n```\n\nThe first call starts `initialize`. Later calls return the same promise. If\n`initialize` fails, the helper clears the cached promise, so the next call tries\nagain.\n\nEach `initialize` call resets the global theme\n(`Storyteller.sharedInstance.theme`) to the defaults. If you use a global\ntheme, set it after `initialize` resolves, for example in a `.then()` inside\n`startStoryteller()`. A theme set in a view's `configuration` is kept. See\n[Customize themes](../Themes.md).\n\nWhen the signed-in user changes, call `initialize` again as described in\n[Identify and personalize users](../Users.md#changing-users), then set the\nglobal theme again.\n\n## React\n\nImport the SDK, its stylesheet, and the helper:\n\n```jsx\nimport { useEffect } from 'react';\nimport * as Storyteller from '@getstoryteller/storyteller-sdk-javascript';\nimport '@getstoryteller/storyteller-sdk-javascript/dist/storyteller.min.css';\nimport { startStoryteller } from './storyteller';\n```\n\nCreate the view after `startStoryteller()` resolves, and destroy it in the\neffect cleanup:\n\n```jsx\nexport function StoryRow() {\n useEffect(() => {\n let isMounted = true;\n let storyRow;\n\n startStoryteller()\n .then(() => {\n if (isMounted) {\n storyRow = new Storyteller.StorytellerStoriesRowView(\n 'storyteller-stories-row'\n );\n }\n })\n .catch((error) => console.error('Storyteller could not start.', error));\n\n return () => {\n isMounted = false;\n storyRow?.destroy();\n };\n }, []);\n\n return <div id=\"storyteller-stories-row\" style={{ height: 200 }} />;\n}\n```\n\nIn development, React Strict Mode runs each effect, its cleanup, and the effect\nagain. The cleanup sets `isMounted` to `false`, so only the second effect\ncreates a view. Both effects get the same promise from `startStoryteller()`, so\nthe SDK initializes once.\n\nEach view needs a container ID that is unique on the page. If you render the\ncomponent more than once at the same time, pass a different ID to each\ninstance. The ID must\n[follow the container ID rules](../Quickstart.md#add-a-container).\n\n## Next.js App Router\n\nImport the stylesheet in `app/layout.js`.\n\n```javascript\nimport '@getstoryteller/storyteller-sdk-javascript/dist/storyteller.min.css';\n```\n\nKeep the view in a Client Component so the SDK runs in the browser. Add\n`'use client'` at the top of the component file, then use the same `StoryRow`\ncomponent as in the [React](#react) section:\n\n```jsx\n'use client';\n\nimport { useEffect } from 'react';\nimport * as Storyteller from '@getstoryteller/storyteller-sdk-javascript';\nimport { startStoryteller } from './storyteller';\n```\n\nThe Storyteller Web Showcase uses the same lifecycle. Its\n[`isStorytellerInitialized` guard](https://github.com/getstoryteller/storyteller-showcase-web/blob/11.0.0/nextjs/src/components/atoms/StorytellerRenderers/useStorytellerView.ts#L68)\nruns before view creation. Cleanup calls\n[`view.destroy()`](https://github.com/getstoryteller/storyteller-showcase-web/blob/11.0.0/nextjs/src/components/atoms/StorytellerRenderers/useStorytellerView.ts#L98).\n\n## Next steps\n\n- [Add a Story or Clips row](../StorytellerRowView.md)\n- [Identify and personalize users](../Users.md)\n- [Handle delegates and callbacks](../delegates/index.md)\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}