Search code examples
javascriptreactjsobjectcontent-management-systemternary

How can I render out a piece of content dynamically only once from mapped through data, despite potentially multiple matches?


I have an object of data I am pulling through from a CMS to create a blog and am mapping through the content object in order to render out the main body of the blog.

I have created a ternary operator to essentially say, that if the line of text has a type of 'strong', make the font bold, otherwise if it has a type of 'hyperlink', make the piece of text a link within an tag.

The issue I'm having is that some pieces of text are both 'bold' and 'hyperlinks', and so it is rendering out the sentence twice, how can I ensure that when the type is both 'bold' & 'hyperlink', it only renders once?

This is what my data looks like:

enter image description here

and here is my file where I return this data on the frontend:

 <div className='text-left mt-12 text-white'>
          <h1 className='text-5xl mb-4 font-header text-blue-500'>
            {RichText.asText(data.title)}
          </h1>
          {data.content.map((t, i) => {
            return (
              <>
                {!t.spans[0] && (
                  <p className={`text-lg mt-6 opacity-80 ${t.type}`}>
                    {t.text}
                  </p>
                )}
                {t.spans.map((item) =>
                  item.type === 'strong' ? (
                    <p className={`text-xl mt-6 font-bold ${t.type}`}>
                      {t.text}
                    </p>
                  ) : item.type === 'hyperlink' ? (
                    <a
                      href={item.data.url}
                      target='_blank'
                      className={`text-lg opacity-80 mt-6 text-blue-500 ${t.type}`}>
                      {t.text}
                    </a>
                  ) : item.type === 'list-item' ? (
                    <ul className='list-disc'>
                      <li className='text-lg mt-4 text-white'>{t.text}</li>
                    </ul>
                  ) : (
                    <p
                      className={`text-lg opacity-80 mt-6 font-bold ${t.type}`}>
                      {t.text}
                    </p>
                  )
                )}
              </>
            );
          })}
        </div>

I'm sure this is simple but I wondered the best way to handle something like this?

Thanks in advance!


Solution

  • Don't make your logic switching cases on type.

    Try something like this:

    const styleProps = spans.reduce((acc, span) => {
       if (span === 'strong'){
          acc['font-weight'] = 'bold';
       }
       if (span === 'something-else'){
          acc['some-css-prop'] = 'some-value';
       }
       ...
       return acc;
    }, {})
    

    and then you will have an accumulator (styleProps) with all the necessary props needed to style your component.

    <YourComponent className="my-constant-class-name" style={{...styleProps}} />
    

    You can generalize this idea:

    ex.

    const MyComponentProps = spans.reduce((acc, span) => {
       if (span === 'strong'){
          acc.style['font-weight'] = 'bold';
       }
       if (span === 'hyperlink'){
          acc['href'] = 'www.example.com';
       }
       ...
       return acc;
    }, {});
    

    and also

    let MyComponent = null;
    switch(type){
      case 'hyperlink':
         MyComponent = (props) => <a {...props} />;
         break;
      case 'text':
         MyComponent = (props) => <p {...props} />;
         break;
      case 'bullet':
         MyComponent = (props) => <li {...props} />;
         break;
    

    and then

    <MyComponent {...MyComponentProps} />