next.jsgraphqlblogsheadless-cmshygraph

How can I implement the Hyperlink on this code?


Theres a lot of information behind this problem. I'll try to show everything here.

I'm using Hygraph and GraphQL to create a Blog with Headless CMS and on my API call I have the getPostDetails. This is working and my problem is related to content { raw }.

    export const getPostDetails = async (slug) => {
      const query = gql`
        query GetPostDetails($slug: String!) {
          post(where: {slug: $slug}) {
            title
            excerpt
            featuredImage {
              url
            }
            author{
              name
              bio
              photo {
                url
              }
            }
            createdAt
            slug
            content {
              raw 
            }
            categories {
              name
              slug
            }
          }
        }
      `;
    
      const result = await request(graphqlAPI, query, { slug });
    
      return result.post;
    };

I needed to create a way to render all the nodes the hygraph provides when you create a text. So used as example the content I saw on this video (here) at 2:01:36, article details.

'use client'

import React, { useEffect, useState } from 'react';
import { getPostDetails } from '@/app/api';
import { Flex, Grid, GridItem, Heading, Image, Link, Text } from '@chakra-ui/react';
import AdAside from '../../UI/Atoms/AdAside';

export default function PostPage({ params: { slug }}) {
  const getContentFragment = (index, text, obj, type) => {
    let modifiedText = text;

    if (obj) {
      if (obj.bold) {
        modifiedText = (<b key={index}>{text}</b>);
      }

      if (obj.italic) {
        modifiedText = (<em key={index}>{text}</em>);
      }

      if (obj.underline) {
        modifiedText = (<u key={index}>{text}</u>);
      }

      if (obj.link) {
        modifiedText = (<a key={index} href={obj.nodeId}>{text}</a>);
      }
      
    }

    switch (type) {
      case 'heading-three':
        return <Heading as='h3' key={index}>{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Heading>;
      case 'paragraph':
        return <Text key={index} maxWidth="1200px" mb={8}>{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Text>;
      case 'heading-four':
        return <Heading as='h4' key={index}>{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Heading>;
      case 'image':
        return (
          <Image
            key={index}
            alt={obj.title}
            height={obj.height}
            width={obj.width}
            src={obj.src}
          />
        );
      case 'link':
      return <Link key={index} href={obj.nodeId}>{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Link>

      default:
        return modifiedText;
    }
  };

  const [pagePost, setPagePost] = useState(null);

  useEffect(() => {
    const fetchPost = async () => {
      try {
        if (slug) {
          const result = await getPostDetails(slug);
          setPagePost(result);
        }
      } catch (error) {
        console.error(error);
      }
    };

    fetchPost();
    console.log(pagePost)
  }, [slug]);

  if (!pagePost) {
    return <div>Loading...</div>;
  }
  
  return (
    <>
    <Image
      w="100vw" 
      maxHeight='600px' 
      objectFit='cover' 
      alt='banner content' 
      src={pagePost.featuredImage.url}
      />
    <Grid 
    templateColumns="repeat(12, 1fr)" 
    gap={1} 
    px={6} 
    py={10}
    >
      <GridItem 
      gridColumn={{ base: 'span 12', lg: 'span 8' }}
      >
      <Flex 
      maxWidth="1200px" 
      flexDirection="column"
       >
        <Heading textAlign='left' mb={16}>
          {pagePost.title}
          </Heading>
        <Flex 
        textAlign="left" 
        flexDirection="column"
        >
        {pagePost.content.raw.children.map((typeObj, index) => {
          const children = typeObj.children.map((item, itemindex) => getContentFragment(itemindex, item.text, item));
          
          return getContentFragment(index, children, typeObj, typeObj.type);
        })}
        </Flex>

      </Flex>
      </GridItem>
      <GridItem gridColumn={{ base: 'span 12', lg: 'span 4' }}>
        <AdAside />
      </GridItem>
    </Grid>
      
    </>
  );
}

If you see the video you'll see that

      if (obj.link) {
        modifiedText = (<a key={index} href={obj.nodeId}>{text}</a>);
      }

and

case 'link':
      return <Link key={index} href={obj.nodeId}>{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Link>

don't exist there. I included trying to make a way to use hyperlinks. Futhermore, I toke a look in the Hygraph Playground to see what is related to 'link' and there are too properties: nodeId and Href.

enter image description here

The content with hyperlink aren't being rendering and I don't know what I'm doing wrong. I want to use nodeId to pass from a blogpost to another and to use hyperlink to send the user to a external website.

Can you help me?


Solution

  • The answer is simple. As you can see, you are checking for the link the same way you are checking for bold or italic, but if you pay close attention your link identifier is at a top level and/or included in type key.

    So the first thing you should expect is to match the type as link so instead of obj.link you should check for:

    if(obj.type === 'link') {
      modifiedText = (<a key={index} href={obj.href}>
        {getContentFragment(0, obj.children[0].text, obj.children, obj.children.props)}
      </a>)
    }
    

    You see the reference inside happens because when you have a type === "link" your text should be inside children and not as a sibling of obj.type