# Builder editor requires manual refresh to see the updated inputs

**URL:** <https://forum.builder.io/t/builder-editor-requires-manual-refresh-to-see-the-updated-inputs/15960>\
**Category:** Technical Questions\
**Tags:** visual-editor, api, nextjs\
**Created:** [November 18, 2025, 5:18pm UTC](https://forum.builder.io/t/builder-editor-requires-manual-refresh-to-see-the-updated-inputs/15960 "2025-11-18T17:18:49Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![tlee-pb](https://avatars.discourse-cdn.com/v4/letter/t/7ba0ec/32.png) [@tlee-pb](https://forum.builder.io/u/tlee-pb)\
**Post date:** [November 18, 2025, 5:18pm UTC](https://forum.builder.io/t/builder-editor-requires-manual-refresh-to-see-the-updated-inputs/15960/1 "2025-11-18T17:18:49Z")

</div>

_I have the following code:_

```auto
  {

    name: 'enableManualSelection',

friendlyName: 'Enable manual selection',

helperText:

'Enable manual selection of content. By default, the content is automatically selected based on the model type, and fetched from the latest published content.',

type: 'boolean',

defaultValue: false,

  },

  {

name: 'postCount',

friendlyName: 'Post count',

helperText: 'Please refresh the page after changing the post count to see the change in the preview.',

type: 'enum',

enum: RELATED_CONTENT_POST_COUNTS,

defaultValue: '3',

showIf: "options.get('enableManualSelection') === false",

  },

  {

name: 'modelType',

friendlyName: 'Post model type',

helperText: 'Please refresh the page after changing the model type to see the change in the preview.',

type: 'string',

enum: Object.entries(RELATED_CONTENT_MODEL_TYPES).map(([key, value]) => ({ label: value, value: key })),

defaultValue: 'blog-detail',

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'blog-detail' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

type: 'reference',

friendlyName: 'Blog Card',

model: 'blog-detail',

meta: {

title: 'Blog Detail',

        },

      },

],

  },…

```

where changing the modeType should switch what content would get fetch.  
In the Builder editor, I have to manually refresh the page to see the change.  
Is there a way to detect the change, or state without the need to manually refreshing the page.

**Builder content link**  
[Here is the builder link.](https://builder.io/content/75e3b171df21440eab89f554880c1b7b/edit?activeDesignerTab=0&activeLocale=Default)

**Builder public api key**  
1d8ecee591ac4358befb8fe998100548

**What are you trying to accomplish**  
_e.g. I want to integrate builder on a landing page_

**Screenshots or video link**  
[Here is the loom link](https://www.loom.com/share/b9551bcfeb1148acad6344e9bb2e0e49)

**Code stack you are integrating Builder with**  
_NextJs_

---

<div class="post-metadata">

**Author:** ![manish-sharma](https://avatars.discourse-cdn.com/v4/letter/m/8797f3/32.png) [@manish-sharma](https://forum.builder.io/u/manish-sharma)\
**Post date:** [November 25, 2025, 5:48pm UTC](https://forum.builder.io/t/builder-editor-requires-manual-refresh-to-see-the-updated-inputs/15960/2 "2025-11-25T17:48:37Z")

</div>

Hello @tlee-pb,

We suspect the issue may be related to the integration. Could you share the complete code for your **Related Content** custom component so we can take a closer look?

Additionally, please confirm which Builder SDK and SDK version you’re currently using.

Thanks,

---

<div class="post-metadata">

**Author:** ![tlee-pb](https://avatars.discourse-cdn.com/v4/letter/t/7ba0ec/32.png) [@tlee-pb](https://forum.builder.io/u/tlee-pb)\
**Post date:** [December 4, 2025, 10:15pm UTC](https://forum.builder.io/t/builder-editor-requires-manual-refresh-to-see-the-updated-inputs/15960/3 "2025-12-04T22:15:50Z")

</div>

Hello @manish-sharma

Here are our relatedContent code:

**Presenter (index):**

```auto
import React, { useMemo } from 'react';

import RelatedContent from '@/components/RelatedContent';

import type { ArticleCardProps } from '@/components/ArticleCard/consts';

import type {

RelatedContentModelType,

RelatedContentPostCount,

RelatedContentCardItem,

} from '@/builder-presenters/RelatedContent/consts';

import { type BasePresenterProps, builderPresenter } from '@/utils/builder/builderPresenter';

import type { HeadingWithEyebrowProps } from '@/molecules/HeadingWithEyebrow/consts';

import SectionNew from '@/molecules/SectionNew';

import type { SectionNewGap, SectionNewPosition, SectionNewProps } from '@/molecules/SectionNew/consts';

import type { SectionPresenterProps } from '@/builder-presenters/Section';

import { getRelatedContentAsyncProps, transformToArticleCard } from '@/builder-presenters/RelatedContent/utils';

// Raw section props from Builder.io (derived from SectionPresenterProps, omitting BasePresenterProps, and required fields)

// Position and gap are made optional for this use case

// Align is filtered out from the Builder inputs

type RawSectionProps = Omit<SectionPresenterProps, keyof BasePresenterProps | 'position' | 'gap' | 'align'> & {

position?: SectionNewPosition;

gap?: SectionNewGap;

};

// Transform raw Builder.io section props to SectionNew props

// This is needed because Builder uses 'image' but SectionNew expects 'backgroundImage'

const transformSectionProps = (section?: RawSectionProps): SectionNewProps | undefined => {

if (!section) return undefined;

const { image, useImageBackground, useVideoBackground, video, useGradient, ...rest } = section;

return {

...rest,

// Map Builder's 'image' to SectionNew's 'backgroundImage'

...(useImageBackground && image ? { backgroundImage: image } : {}),

// Map Builder's 'video' to SectionNew's 'videoSources'

...(useVideoBackground && video

? {

videoSources: {

small: video.srcSmall || '',

medium: video.srcMedium || '',

large: video.srcLarge || '',

          },

        }

: {}),

// Only include gradient if useGradient is true

...(useGradient && rest.gradient ? { gradient: rest.gradient } : {}),

  } as SectionNewProps;

};

export type RelatedContentAsyncProps = {

modelType: RelatedContentModelType;

postCount: RelatedContentPostCount;

data: ArticleCardProps[];

heading?: HeadingWithEyebrowProps;

section?: RawSectionProps;

};

export type RelatedContentPresenterProps = BasePresenterProps &

RelatedContentAsyncProps & {

enableManualSelection: boolean;

cards?: RelatedContentCardItem[];

  };

type TransformedProps =

| {

enableManualSelection: false;

data: ArticleCardProps[];

heading?: HeadingWithEyebrowProps;

section?: SectionNewProps;

    }

| {

enableManualSelection: true;

heading?: HeadingWithEyebrowProps;

section?: SectionNewProps;

cards: RelatedContentCardItem[];

    };

export const transformRelatedContentProps = ({

enableManualSelection,

data,

builderBlock,

heading,

section,

...props

}: RelatedContentPresenterProps): TransformedProps => {

// Transform section props from Builder.io format to SectionNew format

const transformedSection = transformSectionProps(section);

if (!enableManualSelection) {

return {

enableManualSelection: false,

data,

heading,

section: transformedSection,

    };

  }

// Extract cards directly from builderBlock to avoid async props interference

// This ensures we always get the latest cards from Builder, even when async props cause re-renders

const cards = (builderBlock?.component?.options?.cards as RelatedContentCardItem[] | undefined) || props.cards || [];

return {

enableManualSelection: true,

heading,

section: transformedSection,

cards,

  };

};

export default builderPresenter(

props => {

const { enableManualSelection, section, heading, ...rest } = props;

// Remove data and cards from rest to prevent them from being spread onto SectionNew

// eslint-disable-next-line @typescript-eslint/no-unused-vars

const { data, cards, ...sectionProps } = rest as typeof rest & { data?: unknown; cards?: unknown };

if (!enableManualSelection) {

return (

<SectionNew {...section} {...sectionProps}>

<RelatedContent data={props.data} heading={heading} />

</SectionNew>

      );

    }

// Use useMemo to stabilize the manualCards array and prevent unnecessary re-renders

// Use a ref to preserve cards across renders to prevent them from disappearing

const previousCardsRef = React.useRef<ArticleCardProps[]>([]);

// Create a stable dependency string based on card IDs instead of array reference

// This ensures memoization only recalculates when card content actually changes

// We compute the string inline in the dependency array since React compares values, not references

const cardIdsString =

props.cards

        ?.map(card => card?.card?.id)

        .filter((id): id is string => Boolean(id))

        .join(',') ?? '';

const manualCards: ArticleCardProps[] = useMemo(() => {

if (!props.cards || props.cards.length === 0) {

// If we have previous cards, keep them to prevent disappearing

// This handles the case where props.cards temporarily becomes empty during re-renders

if (previousCardsRef.current.length > 0) {

return previousCardsRef.current;

        }

return [];

      }

// Check if cards have valid content - if not, use previous cards

const cardsWithValidContent = props.cards.filter(card => card?.card?.value);

const validCardsCount = cardsWithValidContent.length;

// If we have previous cards but current cards are invalid, keep previous cards

if (validCardsCount === 0 && previousCardsRef.current.length > 0) {

return previousCardsRef.current;

      }

const transformed = props.cards

        .map(card => {

const content = card?.card?.value;

if (!content) {

return null;

          }

// Extract model type from BuilderReference if available

const modelType = card?.card?.model;

return transformToArticleCard(content, modelType);

        })

        .filter((item): item is ArticleCardProps => item !== null);

// If transformation resulted in fewer cards than we had before, and we have previous cards, use previous

if (transformed.length === 0 && previousCardsRef.current.length > 0) {

return previousCardsRef.current;

      }

// Update the ref with the new cards only if we got valid cards

if (transformed.length > 0) {

previousCardsRef.current = transformed;

      }

return transformed.length > 0 ? transformed : previousCardsRef.current;

// eslint-disable-next-line react-hooks/exhaustive-deps

    }, [cardIdsString]);

return (

<SectionNew {...section} {...sectionProps}>

<RelatedContent data={manualCards} heading={heading} />

</SectionNew>

    );

  },

transformRelatedContentProps,

  { includeSection: false, fetchAsyncProps: getRelatedContentAsyncProps },

);

return (

<RelatedContentWrapper ref={ref} {...rest}>

<HeadingWithEyebrow type="heading2" align="center" {...headingProps}>

{headingChildren}

</HeadingWithEyebrow>

<CardWrapper>

{data ? (

data.map(blog => {

return <StyledArticleCard key={`RelatedContent_${blog.title}`} variant="boxed" {...blog} />;

          })

        ) : (

<Spinner size="xl" />

        )}

</CardWrapper>

</RelatedContentWrapper>

  );

};

export default memo(forwardRef(RelatedContent));

```

**Present (Builder config)**

```auto
import type { Component, Input } from '@builder.io/sdk';

import { HeadingInputs } from '@/builder-presenters/Heading/heading.builder';

import { RELATED_CONTENT_MODEL_TYPES, RELATED_CONTENT_POST_COUNTS } from '@/builder-presenters/RelatedContent/consts';

import { SectionInputs } from '@/builder-presenters/Section/section.builder';

const relatedContentInputs: Input[] = [

  {

name: 'heading',

friendlyName: 'Heading',

type: 'object',

subFields: HeadingInputs.filter(field => !['align'].includes(field.name) && field.name !== 'eyebrow'),

defaultValue: {

type: 'heading2',

children: 'You might also like',

    },

  },

  {

name: 'enableManualSelection',

friendlyName: 'Enable manual selection',

helperText:

'Enable manual selection of content. By default, the content is automatically selected based on the model type, and fetched from the latest published content.',

type: 'boolean',

defaultValue: false,

  },

  {

name: 'postCount',

friendlyName: 'Post count',

helperText: 'Please refresh the page after changing the post count to see the change in the preview.',

type: 'enum',

enum: RELATED_CONTENT_POST_COUNTS.map(count => ({ label: count.toString(), value: count })),

defaultValue: RELATED_CONTENT_POST_COUNTS[0],

showIf: "options.get('enableManualSelection') === false",

  },

  {

name: 'modelType',

friendlyName: 'Post model type',

helperText: 'Please refresh the page after changing the model type to see the change in the preview.',

type: 'string',

enum: Object.entries(RELATED_CONTENT_MODEL_TYPES).map(([key, value]) => ({ label: value, value: key })),

defaultValue: 'blog-detail',

  },

// Builder doesn't seem to allow dynamic fields based on the model type, so we have to use a list of cards for each model type.

// I submitted an inquiry to Builder.io to see if this is possible, but no response yet.

// 

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'blog-detail' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

type: 'reference',

friendlyName: 'Blog Card',

model: 'blog-detail',

meta: {

title: 'Blog Detail',

        },

      },

],

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'event-detail' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Event Card',

type: 'reference',

model: 'event-detail',

meta: {

title: 'Event Detail',

        },

      },

],

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'customer-detail' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Customer Card',

type: 'reference',

model: 'customer-detail',

meta: {

title: 'Customer Detail',

        },

      },

],

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'ebook-detail' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Ebook Card',

type: 'reference',

model: 'ebook-detail',

meta: {

title: 'Ebook Detail',

        },

      },

],

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'resources-infographic' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Infographic Card',

type: 'reference',

model: 'resources-infographic',

meta: {

title: 'Infographic Detail',

        },

      },

],

  },

  {

name: 'cards',

type: 'list',

showIf: "options.get('modelType') === 'resources-product-demo' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Product Demo Card',

type: 'reference',

model: 'resources-product-demo',

meta: {

title: 'Product Demo Detail',

        },

      },

],

  },

  {

name: 'cards',

friendlyName: 'List of Cards',

type: 'list',

showIf: "options.get('modelType') === 'resources-webinar' && options.get('enableManualSelection') === true",

helperText: 'You can manually select the cards to display in the Related Content.',

subFields: [

      {

name: 'card',

friendlyName: 'Webinar Card',

type: 'reference',

model: 'resources-webinar',

meta: {

title: 'Webinar Detail',

        },

      },

],

  },

  {

name: 'section',

type: 'object',

subFields: SectionInputs.filter(field => field.name !== 'align'),

advanced: true,

  },

];

export const RelatedContentComponent: Component = {

name: 'Related Content',

noWrap: true,

image:

'https://cdn.builder.io/api/v1/image/assets%2F1d8ecee591ac4358befb8fe998100548%2F82b8f6b8aa224e1aade0684a398a3133',

inputs: relatedContentInputs,

canHaveChildren: true,

};

```

**Component:**

```auto
import type { ForwardRefRenderFunction } from 'react';

import { forwardRef, memo } from 'react';

import HeadingWithEyebrow from '@/molecules/HeadingWithEyebrow';

import type { RelatedContentProps } from '@/components/RelatedContent/consts';

import { CardWrapper, RelatedContentWrapper, StyledArticleCard } from '@/components/RelatedContent/styled';

import { Spinner } from '@/molecules/Spinner';

const RelatedContent: ForwardRefRenderFunction<HTMLDivElement, RelatedContentProps> = (

  { heading, data, ...rest }: RelatedContentProps,

ref,

) => {

const { children: headingChildren = 'You might also like', ...headingProps } = heading || {};

return (

<RelatedContentWrapper ref={ref} {...rest}>

<HeadingWithEyebrow type="heading2" align="center" {...headingProps}>

{headingChildren}

</HeadingWithEyebrow>

<CardWrapper>

{data ? (

data.map(blog => {

return <StyledArticleCard key={`RelatedContent_${blog.title}`} variant="boxed" {...blog} />;

          })

        ) : (

<Spinner size="xl" />

        )}

</CardWrapper>

</RelatedContentWrapper>

  );

};

export default memo(forwardRef(RelatedContent));

```
