# Story Block (React Native)

{% hint style="info" %}
For android:

StoryBlock is a heavy object containing multiple instances of the player, Heavy-lifting UI elements, and intensive background tasks, Beware that the recommended number of the StoryBlock being used in a single screen is 1. However, in a wide range of new Android devices, 2 instances might work alright. Any number of StoryBlock above this limitation is not recommended by the Firework team and is not supported.
{% endhint %}

There are four source types of the story block:

* Discover
* Channel
* Playlist
* Dynamic Content
* Hashtag Playlist
* Sku
* Single Content

## Integration

```tsx
import {
  StoryBlock,
} from 'react-native-firework-sdk';

// discover
<StoryBlock
  style={{ height: 400 }} 
  source="discover" 
/>

// channel
<StoryBlock 
  style={{ height: 400 }} 
  source="channel" 
  channel="your encoded channel id"
/>

// playlist
<StoryBlock 
  style={{ height: 400 }} 
  source="playlist" 
  playlist="your encoded playlist id"
  channel="your encoded channel id"
/>

// dynamic content
<StoryBlock 
  style={{ height: 400 }} 
  source="dynamicContent" 
  channel="your encoded channel id"
  dynamicContentParameters={{
    '<cohort key>': ['<cohort value1>', '<cohort value2>'],
  }}
/>

// hashtag playlist
<StoryBlock 
  style={{ height: 400 }} 
  source="hashtagPlaylist"
  channel="your encoded channel id"
  hashtagFilterExpression="<hashtag filter expression>"
/>

<StoryBlock 
  style={{ height: 400 }} 
  source="sku"
  channel="your encoded channel id"
  productIds={['prodct_id_1', 'product_id_2']}
/>

<StoryBlock 
  style={{ height: 400 }} 
  source="singleContent"
  channel="your encoded vide or live stream id"
  productIds={['prodct_id_1', 'product_id_2']}
/>
```

{% hint style="info" %}
Please refer to the [Encoded IDs](/firework-for-developers/additional-resources/encoded-ids.md) help article to learn about how to find your encoded channel ID, playlist ID.
{% endhint %}

### Story block loading result callback

`StoryBlock` component provides `onStoryBlockLoadFinished` prop for setting video feed loading result callback.

```tsx
<StoryBlock
  style={{ height: 400 }}
  source="discover"
  onStoryBlockLoadFinished={(error) => {
  /**
    * if error is undefined, it means that story block loaded successfully. 
    * Otherwise, it means that story block failed to load.
    */
  console.log('onStoryBlockLoadFinished error', error);
  }}
/>
```

### Story block configuration

Please refer to [Player configurations (React Native)](/firework-for-developers/react-native-sdk/integration-guide-v2/customization-react-native/player-configurations-react-native.md).

### Enable PiP(Picture in Picture)

Please refer to [Enable PiP(Picture in Picture)](https://docs.firework.com/firework-for-developers/react-native-sdk/integration-guide-v2/customization-react-native/player-configurations-react-native#enable-pip-picture-in-picture).

### Manually manage story block appearance

#### Why manual appearance management is needed

By default the SDK tries to detect viewport changes automatically through the underlying native view's lifecycle. In a React Native app this auto-detection is limited and is **not** reliable in the following common cases:

* **JS-based screen navigation.** Only `@react-navigation/native-stack` (which is backed by a real `UINavigationController` / `FragmentManager`) reliably triggers the native view controller / fragment appearance callbacks the SDK depends on. JS-based navigators such as `@react-navigation/stack`, `bottom-tabs`, and `drawer` keep the same root native view controller / activity and only swap the React tree, so the native SDK does not receive an "appeared" / "disappeared" signal when the user navigates between screens.
* **Scrolling inside a list.** When a story block is embedded in a `ScrollView`, `FlatList`, `SectionList`, or any other custom scroll container, the native view stays attached to the same window and parent view as the user scrolls it on/off screen. The native SDK has no portable way to observe that the host RN list has scrolled the story block out of (or back into) the visible area, so it cannot pause/resume on its own.

In these cases set `storyBlockConfiguration.handleAppearanceManually` to `true` and drive the viewport state from the host app.

#### Driving viewport state from the host app

When `storyBlockConfiguration.handleAppearanceManually` is set to `true`, the SDK will not perform any automatic viewport detection. The host app is responsible for notifying the story block of viewport changes by calling `onViewportEntered` and `onViewportLeft` on the `StoryBlock` ref:

* When the story block leaves the viewport, call `onViewportLeft` and the story block will pause playback.
* When the story block re-enters the viewport, call `onViewportEntered` and the story block will resume the state it was in before leaving.

```tsx
import { useRef } from 'react';
import { StoryBlock, type IStoryBlockMethods } from 'react-native-firework-sdk';

const storyBlockRef = useRef<IStoryBlockMethods>(null);

<StoryBlock
  ref={storyBlockRef}
  style={{ height: 400 }}
  source="discover"
  storyBlockConfiguration={{
    handleAppearanceManually: true,
  }}
/>
```

Below are two common scenarios for driving these callbacks.

#### Example 1: The story block fills the whole screen

If the story block occupies the entire screen, you can use React Navigation's `useFocusEffect` to detect when the screen becomes focused or unfocused, and forward those events to the story block:

```tsx
import { useCallback, useRef } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { StoryBlock, type IStoryBlockMethods } from 'react-native-firework-sdk';

const StoryBlockScreen = () => {
  const storyBlockRef = useRef<IStoryBlockMethods>(null);

  useFocusEffect(
    useCallback(() => {
      storyBlockRef.current?.onViewportEntered();

      return () => {
        storyBlockRef.current?.onViewportLeft();
      };
    }, [])
  );

  return (
    <StoryBlock
      ref={storyBlockRef}
      style={{ flex: 1 }}
      source="discover"
      storyBlockConfiguration={{
        handleAppearanceManually: true,
      }}
    />
  );
};
```

#### Example 2: The story block is embedded in a FlatList

If the story block is rendered as one of many items inside a `FlatList`, you need to consider two factors together:

1. Whether the story block's row is currently visible inside the list (tracked via `onViewableItemsChanged`).
2. Whether the screen that hosts the list is currently focused (tracked via `useFocusEffect`). The list item may still be "viewable" within the list while the user has navigated to another screen, in which case the story block should still be paused.

Track each condition and derive the combined viewport state. A `useEffect` keyed on the derived value will fire `onViewportEntered` / `onViewportLeft` only when the combined state changes:

```tsx
import { useCallback, useEffect, useRef, useState } from 'react';
import { FlatList, type ViewToken } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
import { StoryBlock, type IStoryBlockMethods } from 'react-native-firework-sdk';

const STORY_BLOCK_ITEM_KEY = 'story-block';

const viewabilityConfig = { itemVisiblePercentThreshold: 50 };

const FeedScreen = () => {
  const storyBlockRef = useRef<IStoryBlockMethods>(null);
  const isScreenFocused = useIsFocused();
  const [isItemVisible, setIsItemVisible] = useState(false);

  const onViewableItemsChanged = useCallback(
    ({ viewableItems }: { viewableItems: ViewToken[] }) => {
      setIsItemVisible(
        viewableItems.some((item) => item.key === STORY_BLOCK_ITEM_KEY)
      );
    },
    []
  );

  const isInViewport = isScreenFocused && isItemVisible;

  useEffect(() => {
    if (isInViewport) {
      storyBlockRef.current?.onViewportEntered();
    } else {
      storyBlockRef.current?.onViewportLeft();
    }
  }, [isInViewport]);

  return (
    <FlatList
      data={data}
      viewabilityConfig={viewabilityConfig}
      onViewableItemsChanged={onViewableItemsChanged}
      renderItem={({ item }) => {
        if (item.key === STORY_BLOCK_ITEM_KEY) {
          return (
            <StoryBlock
              ref={storyBlockRef}
              style={{ height: 400 }}
              source="discover"
              storyBlockConfiguration={{
                handleAppearanceManually: true,
              }}
            />
          );
        }
        return renderOtherItem(item);
      }}
    />
  );
};
```

## Reference

[StoryBlock](https://eng.firework.com/react-native-firework-sdk/v2/functions/StoryBlock.html)

[IStoryBlockProps](https://eng.firework.com/react-native-firework-sdk/v2/interfaces/IStoryBlockProps.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.firework.com/firework-for-developers/react-native-sdk/integration-guide-v2/story-block.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
