Skip to content

Commit dda1790

Browse files
committed
chore: changes after self review
1 parent 6d3d30b commit dda1790

2 files changed

Lines changed: 132 additions & 132 deletions

File tree

docs/docs/guides/building-chat-app.mdx

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,74 @@ const onKeyboardPress = () => {
197197

198198
When `freeze` is `true`, all keyboard-driven layout changes (padding, content offset, scroll position) are paused.
199199

200+
### Handling a growing multiline input
201+
202+
If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
203+
204+
Pass a `SharedValue<number>` via the `extraContentPadding` prop. Update it in the `TextInput`'s `onLayout` callback whenever the height changes:
205+
206+
```tsx
207+
import { useCallback } from "react";
208+
import { TextInput, type LayoutChangeEvent } from "react-native";
209+
import { useSharedValue, withTiming } from "react-native-reanimated";
210+
211+
const MIN_INPUT_HEIGHT = 36;
212+
213+
function ChatScreen() {
214+
const extraContentPadding = useSharedValue(0);
215+
216+
const onInputLayout = useCallback(
217+
(e: LayoutChangeEvent) => {
218+
const height = e.nativeEvent.layout.height;
219+
220+
extraContentPadding.value = withTiming(
221+
Math.max(height - MIN_INPUT_HEIGHT, 0),
222+
{ duration: 250 },
223+
);
224+
},
225+
[extraContentPadding],
226+
);
227+
228+
return (
229+
<>
230+
<KeyboardChatScrollView extraContentPadding={extraContentPadding}>
231+
{/* ...messages... */}
232+
</KeyboardChatScrollView>
233+
<KeyboardStickyView>
234+
<TextInput multiline onLayout={onInputLayout} />
235+
</KeyboardStickyView>
236+
</>
237+
);
238+
}
239+
```
240+
241+
The value represents the **delta** above the baseline input height. When the input is at its minimum height the value is `0`; each line of growth adds to it. The component uses this value to:
242+
243+
1. Extend the scrollable range so the last messages aren't hidden under the taller input.
244+
2. Scroll the list down (based on `keyboardLiftBehavior`) so the bottom messages stay visible.
245+
246+
:::note Using with virtualized lists
247+
When using `extraContentPadding` with `FlatList`, `FlashList`, or `LegendList`, pass the shared value through your wrapper component — both to `KeyboardChatScrollView` and to `useCallback` dependencies so the reference is stable:
248+
249+
```tsx
250+
const renderScrollComponent = useCallback(
251+
(props: ScrollViewProps) => (
252+
<VirtualizedListScrollView
253+
{...props}
254+
extraContentPadding={extraContentPadding}
255+
/>
256+
),
257+
[extraContentPadding],
258+
);
259+
```
260+
261+
:::
262+
200263
### Reserving blank space for AI streaming responses
201264

202-
In AI chat apps (like ChatGPT or Claude), after the user sends a message, the AI response streams in token by token. Without special handling, the user's sent message stays at the bottom of the viewport and the streaming response grows underneath the keyboard — invisible until the user scrolls.
265+
In AI chat apps (like ChatGPT or Claude), after the user sends a message, the AI response streams in token by token. Without special handling, the user's sent message stays at the bottom of the viewport and the streaming response grows.
203266

204-
The `blankSpace` prop solves this. It sets a **minimum bottom inset** on the scroll view, creating empty space below the last message. This pushes the user's sent message toward the top of the screen, leaving room for the AI response to stream in visibly. The total bottom padding is computed as:
267+
The `blankSpace` prop solves this. It sets a **minimum bottom inset** on the scroll view, creating empty space below the last message. This allows the user's sent message being pushed toward the top of the screen, leaving room for the AI response to stream in visibly. The total bottom padding is computed as:
205268

206269
```
207270
max(blankSpace, keyboardPadding + extraContentPadding)
@@ -241,7 +304,7 @@ function ChatScreen() {
241304

242305
#### Calculating the right value
243306

244-
The value of `blankSpace` should be the amount of empty space needed below your content. In practice, this is the difference between the scrollable area and the content that sits below the "anchor" message (the last user message). A typical pattern with an inverted virtualized list looks like:
307+
The value of `blankSpace` should be the amount of empty space needed below your content. In practice, this is the difference between the scrollable area and the content that sits below the "anchor" message (the last user message). A typical pattern with a virtualized list may looks like:
245308

246309
```tsx
247310
const blankSpace = useSharedValue(0);
@@ -266,69 +329,6 @@ As the AI response streams in and grows taller, `contentBelowAnchor` increases a
266329
On **iOS with React Native 0.81+**, the `contentInset` area created by `blankSpace` may not respond to touch/scroll gestures. To fix this, set `applyWorkaroundForContentInsetHitTestBug={true}` on `KeyboardChatScrollView`, or apply the upstream React Native patch. See the [API reference](../api/components/keyboard-chat-scroll-view#blankspace) for details.
267330
:::
268331

269-
### Handling a growing multiline input
270-
271-
If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
272-
273-
Pass a `SharedValue<number>` via the `extraContentPadding` prop. Update it in the `TextInput`'s `onLayout` callback whenever the height changes:
274-
275-
```tsx
276-
import { useCallback } from "react";
277-
import { TextInput, type LayoutChangeEvent } from "react-native";
278-
import { useSharedValue, withTiming } from "react-native-reanimated";
279-
280-
const MIN_INPUT_HEIGHT = 36;
281-
282-
function ChatScreen() {
283-
const extraContentPadding = useSharedValue(0);
284-
285-
const onInputLayout = useCallback(
286-
(e: LayoutChangeEvent) => {
287-
const height = e.nativeEvent.layout.height;
288-
289-
extraContentPadding.value = withTiming(
290-
Math.max(height - MIN_INPUT_HEIGHT, 0),
291-
{ duration: 250 },
292-
);
293-
},
294-
[extraContentPadding],
295-
);
296-
297-
return (
298-
<>
299-
<KeyboardChatScrollView extraContentPadding={extraContentPadding}>
300-
{/* ...messages... */}
301-
</KeyboardChatScrollView>
302-
<KeyboardStickyView>
303-
<TextInput multiline onLayout={onInputLayout} />
304-
</KeyboardStickyView>
305-
</>
306-
);
307-
}
308-
```
309-
310-
The value represents the **delta** above the baseline input height. When the input is at its minimum height the value is `0`; each line of growth adds to it. The component uses this value to:
311-
312-
1. Extend the scrollable range so the last messages aren't hidden under the taller input.
313-
2. Scroll the list down (based on `keyboardLiftBehavior`) so the bottom messages stay visible.
314-
315-
:::note Using with virtualized lists
316-
When using `extraContentPadding` with `FlatList`, `FlashList`, or `LegendList`, pass the shared value through your wrapper component — both to `KeyboardChatScrollView` and to `useCallback` dependencies so the reference is stable:
317-
318-
```tsx
319-
const renderScrollComponent = useCallback(
320-
(props: ScrollViewProps) => (
321-
<VirtualizedListScrollView
322-
{...props}
323-
extraContentPadding={extraContentPadding}
324-
/>
325-
),
326-
[extraContentPadding],
327-
);
328-
```
329-
330-
:::
331-
332332
## Using with virtualized lists
333333

334334
For production chat apps you'll likely use a virtualized list (`FlatList`, `FlashList`, or `LegendList`) instead of a plain `ScrollView`. All of these accept a custom scroll component, making integration straightforward.

docs/versioned_docs/version-1.21.0/guides/building-chat-app.mdx

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,74 @@ const onKeyboardPress = () => {
197197

198198
When `freeze` is `true`, all keyboard-driven layout changes (padding, content offset, scroll position) are paused.
199199

200+
### Handling a growing multiline input
201+
202+
If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
203+
204+
Pass a `SharedValue<number>` via the `extraContentPadding` prop. Update it in the `TextInput`'s `onLayout` callback whenever the height changes:
205+
206+
```tsx
207+
import { useCallback } from "react";
208+
import { TextInput, type LayoutChangeEvent } from "react-native";
209+
import { useSharedValue, withTiming } from "react-native-reanimated";
210+
211+
const MIN_INPUT_HEIGHT = 36;
212+
213+
function ChatScreen() {
214+
const extraContentPadding = useSharedValue(0);
215+
216+
const onInputLayout = useCallback(
217+
(e: LayoutChangeEvent) => {
218+
const height = e.nativeEvent.layout.height;
219+
220+
extraContentPadding.value = withTiming(
221+
Math.max(height - MIN_INPUT_HEIGHT, 0),
222+
{ duration: 250 },
223+
);
224+
},
225+
[extraContentPadding],
226+
);
227+
228+
return (
229+
<>
230+
<KeyboardChatScrollView extraContentPadding={extraContentPadding}>
231+
{/* ...messages... */}
232+
</KeyboardChatScrollView>
233+
<KeyboardStickyView>
234+
<TextInput multiline onLayout={onInputLayout} />
235+
</KeyboardStickyView>
236+
</>
237+
);
238+
}
239+
```
240+
241+
The value represents the **delta** above the baseline input height. When the input is at its minimum height the value is `0`; each line of growth adds to it. The component uses this value to:
242+
243+
1. Extend the scrollable range so the last messages aren't hidden under the taller input.
244+
2. Scroll the list down (based on `keyboardLiftBehavior`) so the bottom messages stay visible.
245+
246+
:::note Using with virtualized lists
247+
When using `extraContentPadding` with `FlatList`, `FlashList`, or `LegendList`, pass the shared value through your wrapper component — both to `KeyboardChatScrollView` and to `useCallback` dependencies so the reference is stable:
248+
249+
```tsx
250+
const renderScrollComponent = useCallback(
251+
(props: ScrollViewProps) => (
252+
<VirtualizedListScrollView
253+
{...props}
254+
extraContentPadding={extraContentPadding}
255+
/>
256+
),
257+
[extraContentPadding],
258+
);
259+
```
260+
261+
:::
262+
200263
### Reserving blank space for AI streaming responses
201264

202-
In AI chat apps (like ChatGPT or Claude), after the user sends a message, the AI response streams in token by token. Without special handling, the user's sent message stays at the bottom of the viewport and the streaming response grows underneath the keyboard — invisible until the user scrolls.
265+
In AI chat apps (like ChatGPT or Claude), after the user sends a message, the AI response streams in token by token. Without special handling, the user's sent message stays at the bottom of the viewport and the streaming response grows.
203266

204-
The `blankSpace` prop solves this. It sets a **minimum bottom inset** on the scroll view, creating empty space below the last message. This pushes the user's sent message toward the top of the screen, leaving room for the AI response to stream in visibly. The total bottom padding is computed as:
267+
The `blankSpace` prop solves this. It sets a **minimum bottom inset** on the scroll view, creating empty space below the last message. This allows the user's sent message being pushed toward the top of the screen, leaving room for the AI response to stream in visibly. The total bottom padding is computed as:
205268

206269
```
207270
max(blankSpace, keyboardPadding + extraContentPadding)
@@ -241,7 +304,7 @@ function ChatScreen() {
241304

242305
#### Calculating the right value
243306

244-
The value of `blankSpace` should be the amount of empty space needed below your content. In practice, this is the difference between the scrollable area and the content that sits below the "anchor" message (the last user message). A typical pattern with an inverted virtualized list looks like:
307+
The value of `blankSpace` should be the amount of empty space needed below your content. In practice, this is the difference between the scrollable area and the content that sits below the "anchor" message (the last user message). A typical pattern with a virtualized list may looks like:
245308

246309
```tsx
247310
const blankSpace = useSharedValue(0);
@@ -266,69 +329,6 @@ As the AI response streams in and grows taller, `contentBelowAnchor` increases a
266329
On **iOS with React Native 0.81+**, the `contentInset` area created by `blankSpace` may not respond to touch/scroll gestures. To fix this, set `applyWorkaroundForContentInsetHitTestBug={true}` on `KeyboardChatScrollView`, or apply the upstream React Native patch. See the [API reference](../api/components/keyboard-chat-scroll-view#blankspace) for details.
267330
:::
268331

269-
### Handling a growing multiline input
270-
271-
If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
272-
273-
Pass a `SharedValue<number>` via the `extraContentPadding` prop. Update it in the `TextInput`'s `onLayout` callback whenever the height changes:
274-
275-
```tsx
276-
import { useCallback } from "react";
277-
import { TextInput, type LayoutChangeEvent } from "react-native";
278-
import { useSharedValue, withTiming } from "react-native-reanimated";
279-
280-
const MIN_INPUT_HEIGHT = 36;
281-
282-
function ChatScreen() {
283-
const extraContentPadding = useSharedValue(0);
284-
285-
const onInputLayout = useCallback(
286-
(e: LayoutChangeEvent) => {
287-
const height = e.nativeEvent.layout.height;
288-
289-
extraContentPadding.value = withTiming(
290-
Math.max(height - MIN_INPUT_HEIGHT, 0),
291-
{ duration: 250 },
292-
);
293-
},
294-
[extraContentPadding],
295-
);
296-
297-
return (
298-
<>
299-
<KeyboardChatScrollView extraContentPadding={extraContentPadding}>
300-
{/* ...messages... */}
301-
</KeyboardChatScrollView>
302-
<KeyboardStickyView>
303-
<TextInput multiline onLayout={onInputLayout} />
304-
</KeyboardStickyView>
305-
</>
306-
);
307-
}
308-
```
309-
310-
The value represents the **delta** above the baseline input height. When the input is at its minimum height the value is `0`; each line of growth adds to it. The component uses this value to:
311-
312-
1. Extend the scrollable range so the last messages aren't hidden under the taller input.
313-
2. Scroll the list down (based on `keyboardLiftBehavior`) so the bottom messages stay visible.
314-
315-
:::note Using with virtualized lists
316-
When using `extraContentPadding` with `FlatList`, `FlashList`, or `LegendList`, pass the shared value through your wrapper component — both to `KeyboardChatScrollView` and to `useCallback` dependencies so the reference is stable:
317-
318-
```tsx
319-
const renderScrollComponent = useCallback(
320-
(props: ScrollViewProps) => (
321-
<VirtualizedListScrollView
322-
{...props}
323-
extraContentPadding={extraContentPadding}
324-
/>
325-
),
326-
[extraContentPadding],
327-
);
328-
```
329-
330-
:::
331-
332332
## Using with virtualized lists
333333

334334
For production chat apps you'll likely use a virtualized list (`FlatList`, `FlashList`, or `LegendList`) instead of a plain `ScrollView`. All of these accept a custom scroll component, making integration straightforward.

0 commit comments

Comments
 (0)