Dynamic blog rendering for regular react/typescript app

Hello, I am attempting to recreate our app, but in react/typescript. I’m trying to have a dynamic blog app and recreating the following blog post but in react - Create a drag-and-drop editable blog with Next.js. I can get the section model to render, but not render specific blogs when I search it. When checking the console network tab, I can see if querying the data model, and retrieving the data, but it is the wrong slug. The environment is a separate one than this to start fresh but here is the link: Builder.io: Visual Development Platform

Here is the code as well

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { BuilderComponent, builder, useIsPreviewing } from '@builder.io/react';
import axios from 'axios';
import styles from './blog.module.css';

builder.init(*removed for privacy*);

interface ArticleData {
  data: any; // Replace with the specific type based on your data structure
}

interface ArticleTemplate {
  data: any; // Replace with the specific type based on your template data structure
}

export function Blog() {
  const { slug } = useParams<{ slug: string }>();
  const isPreviewingInBuilder = useIsPreviewing();
  const [notFound, setNotFound] = useState(false);
  const [articleData, setArticleData] = useState<ArticleData | null>(null);
  const [articleTemplate, setArticleTemplate] = useState<ArticleTemplate | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchContent() {
      try {
        const articleResponse = await builder
          .get('article', {
            options: { includeRefs: true },
            query: {
              'data.slug': slug,
            },
          })
          .toPromise();
          console.log(articleResponse)
          

        const templateResponse = await builder
          .get('blog-template', {
            options: { enrich: true },
          })
          .toPromise();

        if (articleResponse && templateResponse) {
          setArticleData(articleResponse);
          setArticleTemplate(templateResponse);
        } else {
          setNotFound(true);
        }
      } catch (error) {
        console.error('Error fetching data:', error);
        setNotFound(true);
      } finally {
        setLoading(false);
      }
    }

    fetchContent();
  }, [slug]);

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (notFound && !isPreviewingInBuilder) {
    return <div>Not Found 404 Error</div>;
  }

  return (
    <div className={styles.container}>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      {!articleData && <meta name="robots" content="noindex" />}
      {articleData && articleTemplate && (
        <BuilderComponent
          model="blog-template"
          content={articleTemplate}
          data={{ article: articleData.data }}
        />
      )}
    </div>
  );
};

The app routes are as follows:

 <Routes>
        <Route path="/" element={<HomePage />}></Route>
        <Route path="/builder" element={<CatchAllRoute />}></Route>
        <Route path="/builder/*" element={<CatchAllRoute />}></Route>
        <Route path="/blog/" element={<Blog />}></Route>
        <Route path="/blog/*" element={<Blog />}></Route>
      </Routes>

I do have dynamic rendering for the initial builder path, but not the blog path

Hi @cedson, Hope you are doing well!

Let’s go through your code to understand the issue and ensure it is configured correctly for fetching and rendering specific blog posts dynamically with React and TypeScript in a Builder.io environment.

Step-by-Step Debugging and Fixing:

  1. Initialization and Configuration:

    • Ensure Builder.io SDK is initialized with the correct public API key.
    • Double-check that the correct content models are being queried (article and blog-template).
  2. Slug Matching:

    • Ensure the slug used in your URL matches the slug used in the Builder.io data model.
  3. API Query and Response:

    • Verify that the response data contains the expected fields and is correctly parsed.

Example Improvements and Validation:

Complete and Improved Example:

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { BuilderComponent, builder, useIsPreviewing } from '@builder.io/react';
import styles from './blog.module.css';

builder.init('YOUR_PUBLIC_API_KEY');

interface ArticleData {
  data: {
    slug: string;
    title: string;
    // Add any other fields your article content model contains
    content: any;
  };
}

interface ArticleTemplate {
  data: any; // Replace with the specific type based on your template's data structure
}

export function Blog() {
  const { slug } = useParams<{ slug: string }>();
  const isPreviewingInBuilder = useIsPreviewing();
  const [notFound, setNotFound] = useState(false);
  const [articleData, setArticleData] = useState<ArticleData | null>(null);
  const [articleTemplate, setArticleTemplate] = useState<ArticleTemplate | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchContent() {
      try {
        // Fetch the specific article by slug
        const articleResponse = await builder
          .get('article', {
            options: { includeRefs: true },
            query: {
              'data.slug': slug,
            },
          })
          .toPromise();
        console.log('Article Response:', articleResponse);

        // Fetch the blog template
        const templateResponse = await builder
          .get('blog-template', {
            options: { enrich: true },
          })
          .toPromise();
        console.log('Template Response:', templateResponse);

        if (articleResponse && articleResponse.length > 0 && templateResponse) {
          setArticleData(articleResponse[0]);
          setArticleTemplate(templateResponse);
        } else {
          setNotFound(true);
        }
      } catch (error) {
        console.error('Error fetching data:', error);
        setNotFound(true);
      } finally {
        setLoading(false);
      }
    }

    fetchContent();
  }, [slug]);

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (notFound && !isPreviewingInBuilder) {
    return <div>Not Found 404 Error</div>;
  }

  return (
    <div className={styles.container}>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      {!articleData && <meta name="robots" content="noindex" />}
      {articleData && articleTemplate && (
        <BuilderComponent
          model="blog-template"
          content={articleTemplate}
          data={{ article: articleData.data }}
        />
      )}
    </div>
  );
};

Key Points to Note:

  1. Initialization:

    • Ensure builder.init('YOUR_PUBLIC_API_KEY') uses the correct API key.
  2. Slug Matching and Query:

    • The slug used in the URL must match the slug field in your Builder.io model.
    • Ensure the data.slug key matches how your slug is stored in Builder.io.
  3. Check Network Requests:

    • Use the browser’s network tab to check the actual API requests being made.
    • Look at the query parameters and response to ensure they match your expectations.
  4. Handling Responses:

    • Ensure articleResponse and templateResponse are correctly checked.
    • Use appropriate checks to handle arrays or single objects based on your API response structure.
  5. Error Handling and Debugging:

    • Add console statements like console.log to check intermediate steps and responses.

Testing and Debugging:

  1. Network Tab:

    • Open Developer Tools (F12) and check the network tab.
    • Look at the requests to Builder.io and ensure the correct data is being fetched.
  2. Console Logging:

    • Use console.log extensively to debug and ensure the data fetching is working correctly:
      console.log('Article Response:', articleResponse);
      console.log('Template Response:', templateResponse);
      

By following these steps and ensuring the data fetched is correctly handled and matched to your expected structure, you should be able to get the blog posts to display correctly. If issues persist, dive deeper into the responses in the network tab and adjust the query or data parsing as necessary.

Hi Garima, I got this to work. The page is able to dynamically render the content based upon the slug.

My new question is, if I don’t have a slug and just want to do localhost:4200/blog, the blog home page, how do I render a separate page rather than the blog article content?

Right now, The same page renders if I have a slug or not, but I have a text box that is doing an element binding to the articleData property which is being queried from the slug. If I have no slug, obviously no data is showing, if I do have a slug, the data is showing. Which is great, but I want to render a completely different “home page” If I don’t have a second slug passed in. Do I need to implement a whole new page and then just render the “section” if there is a slug?

@cedson

To render a different “home page” for your blog when there is no slug provided (e.g., when visiting localhost:4200/blog), and render specific blog article content when a slug is provided (e.g., localhost:4200/blog/[slug]), you can set up routing in your React application to handle these cases differently.

Here’s a detailed explanation of how you can achieve this:

Setting Up Routes

You can use a routing library such as react-router-dom to manage your routes. You would set up routes to render different components based on whether a slug is provided.

Example Implementation with React Router

1. Setup Routes in App Component

App.js

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import BlogPage from './BlogPage';
import BlogArticle from './BlogArticle';

function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/blog" component={BlogPage} />
        <Route path="/blog/:slug" component={BlogArticle} />
      </Switch>
    </Router>
  );
}

export default App;

2. Implement BlogPage Component for the Blog Home Page

BlogPage.js

import React, { useEffect, useState } from 'react';
import { BuilderComponent, builder } from '@builder.io/react';

builder.init('YOUR_PUBLIC_API_KEY');

const BlogPage = () => {
  const [homeContent, setHomeContent] = useState(null);

  useEffect(() => {
    const fetchContent = async () => {
      const content = await builder.get('page', {
        query: {
          'data.slug': 'blog-home'
        }
      }).toPromise();
      setHomeContent(content);
    };

    fetchContent();
  }, []);

  return (
    <div>
      {homeContent ? (
        <BuilderComponent model="page" content={homeContent} />
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default BlogPage;

3. Implement BlogArticle Component for Individual Articles

BlogArticle.js

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { BuilderComponent, builder } from '@builder.io/react';

builder.init('YOUR_PUBLIC_API_KEY');

const BlogArticle = () => {
  const { slug } = useParams();
  const [articleData, setArticleData] = useState(null);

  useEffect(() => {
    const fetchContent = async () => {
      const content = await builder.get('article', {
        query: {
          'data.slug': slug
        }
      }).toPromise();
      setArticleData(content);
    };

    fetchContent();
  }, [slug]);

  return (
    <div>
      {articleData ? (
        <BuilderComponent model="article" content={articleData} />
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default BlogArticle;

Explanation

  1. Routing Setup

    • Define routes in the App component using react-router-dom.
    • Use the Switch component to switch between the routes for the blog home page and the blog article page.
  2. BlogPage Component

    • This component fetches and renders the content for the blog home page when the /blog path is accessed.
    • Fetches content with slug set to blog-home (or whatever identifier you use for your blog home content).
  3. BlogArticle Component

    • This component fetches and renders content for individual blog articles when a specific slug is provided in the URL.
    • Uses useParams from react-router-dom to get the slug from the URL and fetches the corresponding article.

Notes

  • Builder Content Models:

    • Ensure you have a content entry with the slug blog-home (or another identifier) to serve as your blog homepage.
    • Ensure your individual articles are correctly tagged and accessible using their respective slugs.
  • Error Handling:

    • Add error handling and loading states to improve user experience.

By following this approach, you can effectively render different content for the blog home page and individual articles based on the presence of a slug in the URL. This allows you to manage both views flexibly using Builder.io and React.

Let me know if you’ve further questions!

This is great, thank you!

1 Like

Following up here. Looking for help on registering custom components to builder. I followed the steps for react and it wasn’t working. Steps I took:

  1. I created a custom component in my components folder.

  2. I tried implementing and registering into my codebase, below:

import { BuilderComponent, builder, useIsPreviewing, Builder } from '@builder.io/react';
import {NavBar2} from '../../../navbar2/src/lib/navbar2'
import './builderio.module.css'
import HelloWorld from '../../../../src/app/components/customComponent1/customComponent1';
builder.init('insert private key');

Builder.registerComponent(HelloWorld, {
  name: 'HelloWorld',
  inputs: [
    {
      name: 'text',
      type: 'text',
      defaultValue: 'World',
    },
  ],
});

export function CatchAllRoute() {
  const isPreviewingInBuilder = useIsPreviewing();
  // Use `any` to handle dynamic content structure
  const [notFound, setNotFound] = useState<boolean>(false);
  const [content, setContent] = useState<any | null>(null);

  useEffect(() => {
    async function fetchContent() {
      const content = await builder.get('page', {
        url: window.location.pathname
      }).promise();
      console.log(content);
      
      // Dynamically renders tab title if the content has a title
      if (content && content.data && content.data.title) {
        document.title = content.data.title;
      }

      setContent(content);
      setNotFound(!content);
    }
    fetchContent();
  }, []);

  if (notFound && !isPreviewingInBuilder) {
    return <div>Not Found 404 Error</div>;
  }





  return (
    <>
      <NavBar2 />
      {content && <BuilderComponent model="page" content={content} />}
    </>
  );
}



// src/builder.js




Here is the custom component code, very simple but just trying to implement to start.

import React from 'react';

const HelloWorld = (props:any) => {
  return <div>Hello, {props.text}!</div>;
};

export default HelloWorld;

When I do this the page doesn’t load. Please let me know what I’m doing wrong.

I am using typescript & react for my tech specs.

Hey @cedson steps you followed seem correct. I opened the editor link you shared before but seems it running on localhost so won’t be able to debug the loading issue.

Please share the latest builder content link if it’s not on localhost else do you see any console error when the page not loading?