Update page/section in preview on page field changes

Hello,

We have a section where the section fields influence the rendering of the page.
Those fields are two dropdowns and when they change, the React components that wrap around the BuilderComponent and the position where the BuilderComponent is put is changed.

Because we are getting the section in advance, before we render react, the editor preview is not updated anymore when those fields change.
We are currently solving this issue with dynamic URLs and passing down the fields as URL parameters, which feels like a hack.
Is it possible to listen to page/section field changes through the SDK, so we can poll the section again when there was a change of the field? I looked through the API of the SDK but I couldn’t spot something like that.
The dynamic URL approach is not really performant as it always needs to load and initiate the whole page including React again, every time a field is updated (typing in would cause multiple reloads).

Hello @tchock,

To improve the performance and efficiency of updating fields within a section model without requiring full page reloads, you can leverage the onStateChange prop in the BuilderComponent. This allows you to listen for changes in the Builder state and react accordingly, such as re-fetching section data or manipulating the component tree based on the updated fields.

Here’s a step-by-step solution:

  1. Listen to Field Changes Using onStateChange:
    Use the onStateChange prop to listen for changes in the Builder state. This can include field updates within your section.

  2. Re-fetch or Update Components as Needed:
    When a field update is detected, you can then trigger a re-fetch of the necessary data or update the component tree directly.

  3. Example Code in React:
    Here’s an example illustrating how you might set this up:

import { useEffect, useState } from "react";
import { BuilderComponent, builder, useIsPreviewing } from "@builder.io/react";

// Initialize Builder with your API key
builder.init('YOUR_API_KEY');

export default function SectionWithDynamicFields() {
  const [sectionData, setSectionData] = useState(null);
  const isPreviewing = useIsPreviewing();

  // Fetch initial section data
  useEffect(() => {
    async function fetchInitialData() {
      const initialData = await builder.get('section-model', {
        userAttributes: {
          urlPath: window.location.pathname
        }
      }).toPromise();
      setSectionData(initialData);
    }
    fetchInitialData();
  }, []);

  // Handler for state changes
  const handleStateChange = (newState) => {
    if (newState?.someFieldThatChanged) {
      // Re-fetch the section data or update based on newState
      async function refetchData() {
        const updatedData = await builder.get('section-model', {
          userAttributes: {
            urlPath: window.location.pathname
          }
        }).toPromise();
        setSectionData(updatedData);
      }
      refetchData();
    }
  };

  return (
    <div>
      {sectionData && (
        <BuilderComponent
          model="section-model"
          content={sectionData}
          onStateChange={handleStateChange}
        />
      )}
    </div>
  );
}
  1. Leverage useIsPreviewing:
    Using the useIsPreviewing hook, you can determine if the changes are being made in the Builder editor. This helps you optimize how and when to re-fetch or update your component data.

  2. Customization Tips:

    • Dynamic URLs and Performance: Avoid full page reloads by dynamic URL parameters. Use the onStateChange callback for granular updates based on specific field changes.
    • Optimize Data Fetching: Ensure you’re not over-fetching data by caching responses or using conditional fetching strategies.

Hope this helps!

Thanks,

Hello,

thank you for the detailed answer. Unfortunately we are prefetching everything outside of a react component and caching it. So this happens outside of react and before react has been mounted anything.

we are doing something like this

export async function preloadContent() {
  const result = await builderPackage.builder.get('ecom-section', {
    enrich: true,
    cachebust: true,
    query: {
      id: params.id,
    },
  });

  // add it to the cache that react uses later (we are actually fetching multiple sections that are referenced in a field in the result section
    contentCache.set(result.id, result);

  // return some properties from the result object
}

Later we are just getting the content through the contentCache Map inside react.
Is there a non react way, like a listener function or something like this?

Hello @tchock,

In your scenario, since you are preloading content and caching it outside of React components, using a non-React way to listen for changes or updates involves a different approach. Here’s one way you can achieve this:

Use Event Listeners or a Pub-Sub System

You can create a custom event listener or a simple publish-subscribe system to notify when the content is updated. This allows you to trigger updates in your cached data without relying on React-specific hooks or lifecycle methods.

Example Implementation

  1. Custom Event Listener

You can create a simple event listener mechanism to listen for updates to your content:

// EventManager.js
class EventManager {
  constructor() {
    this.events = {};
  }
  
  subscribe(eventName, fn) {
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(fn);
  }
  
  unsubscribe(eventName, fn) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(f => f !== fn);
    }
  }
  
  publish(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(fn => fn(data));
    }
  }
}

export const eventManager = new EventManager();
  1. Integrate the Event System with Your Cache

Use this event manager to notify when the content cache is updated:

import { eventManager } from './EventManager';

// Your existing preload function
export async function preloadContent() {
  const result = await builderPackage.builder.get('ecom-section', {
    enrich: true,
    cachebust: true,
    query: {
      id: params.id,
    },
  });

  // Store in cache
  contentCache.set(result.id, result);

  // Publish an event that the content is updated
  eventManager.publish('contentUpdated', result);
}
  1. Subscribe to Events in Your React Component

React components can then subscribe to these events without needing any state-specific hooks:

import { useEffect } from 'react';
import { eventManager } from './EventManager';

function MyComponent() {
  useEffect(() => {
    const handleContentUpdate = (newContent) => {
      console.log('Content updated:', newContent);
      // Update your component or perform any actions you need
    };

    eventManager.subscribe('contentUpdated', handleContentUpdate);

    return () => {
      eventManager.unsubscribe('contentUpdated', handleContentUpdate);
    };
  }, []);

  return (
    <div>
      {/* ... */}
    </div>
  );
}

This method decouples your state management from React while still allowing you to leverage React components if needed to respond to updates. It provides a flexible way to handle updates and can be adapted to fit either server-side or client-side rendering strategies.