Warning: Text content did not match in React 18
Tags: react
Published: 2022-05-14 20:39:29

Hello, recently the project I am working on has been upgraded to React 18. By then, suddenly a lot of issues with hydration have started to appear as warnings/errors in the console. The one I'm struggling with is "Warning: Text content did not match":

[Error](https://i.stack.imgur.com/NPqqc.png)
Code of this component:

```
   <div className="O75-product-faq__questions is-active accordion--initialized">
                            {
                                dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
                            }
                            {
                                props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
                                    <div key={i} className="O75-product-faq__questions__item">
                                        {(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
                                        <AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
                                            <div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
                                        </AnimateHeight>
                                    </div>))
                            }
                        </div>
```

I know that this issue results from the difference between client and server side rendering, but I don't know how to fix it and nothing I have found on internet has worked in my case.

Rest of the file, in case if the issue is not with aforementioned part:

```
import React, { useMemo, useState } from 'react';
import type { ReactElement } from 'react';
import AnimateHeight from 'react-animate-height';
import { BaseSelect, SelectOption } from '../molecules/base-select';
import type { FrequentlyAskedCategory } from './frequentlyAskedQuestion';
import { fromBase64 } from '../shared-services/base64Service';

interface FaqPanelProps {
    mainTitle?: string;
    menuTitle?: string;
    showOnlyAnswer: boolean;
    currentPageUrl: string;
    questionsByCategories: Record<string, FrequentlyAskedCategory>,
    faqStructuredDataBase64: string;
}

const FAQPanel = (props: FaqPanelProps): ReactElement => {
    const categories = Object.keys(props.questionsByCategories);
    const dataForSelect: Array<SelectOption> = categories.map(key => ({ label: props.questionsByCategories[key].name, value: key }));
    const noOpenedElementIndex = -1;

    const [openedElement, setOpenedElement] = useState<number>(-1);
    const [selectedCategory, setSelectedCategory] = useState<SelectOption>(dataForSelect.length > 0 ? dataForSelect[0] : null);
    const [transitionDisabled, setTransitionDisabled] = useState<boolean>(false);
    const parsedStructuredData = useMemo(() => {
        if(props.faqStructuredDataBase64 != null && props.faqStructuredDataBase64 !== ""){
           return fromBase64(props.faqStructuredDataBase64);
        }

        return "";
    }, [props.faqStructuredDataBase64]);

    const selectNewCategory = (option: SelectOption): void => {
        setTransitionDisabled(true);
        setOpenedElement(noOpenedElementIndex);
        setSelectedCategory(option);
    }

    const openElement = (index: number): void => {
        if (transitionDisabled) {
            setTransitionDisabled(false);
        }
        setOpenedElement(index === openedElement ? noOpenedElementIndex : index);
    }

    const speakableJson = JSON.stringify({
        "@context": "https://schema.org/",
        "@type": "WebPage",
        "name": props.mainTitle,
        "speakable":
            [".O75-product-faq__headline",
                ".O75-product-faq__questions__item"],
        "url": props.currentPageUrl
    });

    const hasFAQStructuredData = parsedStructuredData != null && parsedStructuredData !== "";
    return (
        <div className="container">
            <section className="O75-product-faq" >
                {
                    props.mainTitle
                        ? <h2 className="O75-product-faq__headline">{props.mainTitle}</h2>
                        : <h4 className="O75-product-faq__categories-headline">{props.menuTitle}</h4>
                }
                <div className="flex">
                    {dataForSelect.length > 1 &&
                        <div className="O75-product-faq__categories">
                            <div className="filter__list is-hidden-sm filter">
                                {
                                    dataForSelect.map((element, i) => (
                                        <button className={`filter__btn js-filter__btn${element.value === selectedCategory?.value ? " is-active" : ""}`} key={i} onClick={() => selectNewCategory(element)}>
                                            {element.label}
                                        </button>))
                                }
                            </div>
                            <div className="filter__group is-hidden-md">
                                <BaseSelect selectedValue={selectedCategory}
                                    handleChange={selectNewCategory}
                                    options={dataForSelect} />
                            </div>
                        </div>
                    }
                    {categories.length > 0 &&
                        <div className="O75-product-faq__questions is-active accordion--initialized">
                            {
                                dataForSelect.length > 1 && <h4 className="O75-product-faq__questions__name js-category-name">{props.questionsByCategories[selectedCategory?.value].name}</h4>
                            }
                            {
                                props.questionsByCategories[selectedCategory?.value].questions.map((element, i) => (
                                    <div key={i} className="O75-product-faq__questions__item">
                                        {(element.question || props.showOnlyAnswer) && <div className={`O75-product-faq__questions__item__button${openedElement === i ? ' has-accordion-open' : ''}`} onClick={() => openElement(i)}>{element.question}</div>}
                                        <AnimateHeight height={openedElement === i ? 'auto' : 0} duration={transitionDisabled ? 0 : 400}>
                                            <div className="O75-product-faq__questions__item__content" dangerouslySetInnerHTML={{ __html: element.answer }} />
                                        </AnimateHeight>
                                    </div>))
                            }
                        </div>
                    }
                </div>
                {hasFAQStructuredData && <script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html:parsedStructuredData } }></script>}
                <script suppressHydrationWarning type="application/ld+json" dangerouslySetInnerHTML={{__html: speakableJson}} ></script>
            </section>
        </div>
    )
}

export { FAQPanel };
export type { FaqPanelProps }

```

Link to code on stackblitz for better readability: 

[File](https://stackblitz.com/edit/react-ts-xdbemj?file=index.tsx)

Does anyone knows how to fix this? I have totally ran out of ideas.