Условный рендеринг компонента Youtube на основе состояния redux (готовка ie принятие) - PullRequest
1 голос
/ 14 июля 2020

На моем сайте Gatsby я хочу, чтобы компонент Youtube появлялся только в том случае, если пользователь принял файлы cookie из-за GDPR. Если они отклонили файлы cookie, я хочу, чтобы он дал руководство по сбросу их настроек, и если состояние принятия cook ie неизвестно (скажем, они используют расширение, которое заблокировало уведомление cook ie), я хочу, чтобы он сказал, что он не знает, разрешены ли файлы cookie или нет, и если вы не видите баннер, проверьте свой блокировщик рекламы.

Я уже настроил глобальное состояние с помощью response-redux, который проверяет 'acceptCookies' cook ie и все logi c для отображения баннера et c (все это делается с помощью response-cook ie -consent) и устанавливает для state.acceptCookies значение true. Это работало на различных страницах, где, например, я хочу, чтобы контактная форма отображалась только в том случае, если принимаются файлы cookie.

Это мой компонент (который я включаю на любую страницу, которая должна иметь видео Youtube, передавая его код Youtube в качестве опоры):

import React from "react"
import { useSelector } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"

const YouTubeVideo = (props) => {
  const acceptCookies = useSelector(state => state.acceptCookies)
  return (
    <>
      {acceptCookies === '' &&
        <Alert variant="warning">
          <p>
            We cannot show this YouTube video as cookies  have not been accepted,
            but are required for YouTube to work.  To comply with GDPR law, the
            video is not displayed until you have accepted cookies.
        </p>
          <p>
            If you do not see the cookie banner, please try resetting your
          preferences from the <Link to="/privacy-policy">Privacy Policy</Link> or
          disabling any content blockers.
        </p>
        </Alert>
      }
      {acceptCookies === 'false' &&
        <Alert variant="warning">
          <p>
            We cannot show this YouTube video as cookies have been declined, but
            are required for YouTube to work.  To comply with GDPR law, the video
            is not displayed.
        </p>
          <p>
            You may reset your preferences from
          the <Link to="/privacy-policy">Privacy Policy</Link>
          </p>
        </Alert>
      }
      {acceptCookies === 'true' &&
        <div className="embed-container">
          <iframe src={`https://www.youtube.com/embed/${props.code}?rel=0`}
            title={`YouTube Video ${props.code}`}
            frameBorder="0"
            allowFullScreen
          >
          </iframe>
        </div>
      }
    </>
  )
}

export default YouTubeVideo

Я пробовал другой синтаксис, такой как использование операторов if и нескольких блоков рендеринга, без разницы

Я пробовал переписать его как класс и используя connect (), никакой разницы

Да хоть убей, я не могу заставить его работать должным образом. Он отлично работает в режиме разработки - в исходном состоянии отображается предупреждение «файлы cookie не приняты», при отклонении отображается предупреждение «файлы cookie отклонены», а при принятии отображается видео, и все в порядке. Я обновляю sh, и все продолжает работать.

Но как только я создаю и развертываю сайт, он ведет себя очень странно. Сначала это кажется нормальным - начальное состояние показывает предупреждение «куки не были приняты», отклонение показывает предупреждение «куки были отклонены», а принятие показывает видео. А затем вы обновляете sh страницу и .... внутри предупреждения отображается небольшая версия видео! (Без текста предупреждения). Это действительно странно. Это похоже на то, как если бы он следовал логике «куки не приняты» logi c и начинал рисовать предупреждение, а затем, прежде чем писать в абзацах, достигал какого-то состояния гонки и рендерил часть кода «куки приняты», то есть видео. Я вообще не понимаю, я бы ожидал, что он отобразит одну из трех вещей, а не как-то sh результаты вместе.

Что мне здесь не хватает? Я совершенно сбит с толку. Я думаю, что я, должно быть, споткнулся о что-то при предварительном рендеринге, но просто не могу понять это.

Edit: с более ясной головой я подумал хотя бы попробовать установить acceptCookies на stati c 'true', чтобы увидеть, связана ли проблема с моим рендерингом (в этом случае он все равно будет отображаться некорректно) или реализацией глобального состояния (в этом случае будет), и доказал, что это последнее. На самом деле проблема в том, что я не знаю, как правильно сделать ie разрешение или глобальное состояние в Gatsby («кэшировать» его как глобальное состояние, чтобы после принятия файлов cookie вывод обновлялся без необходимости обновлять sh page) и мог только go из ma sh руководств по vanilla React и Gatsby. Мне, вероятно, лучше всего его переписать - на данный момент я не могу вспомнить, какие учебники я использовал!

Однако, чтобы добавить, это то, что у меня есть

gatsby-ssr. js и gatsby-browser. js оба содержат:

import wrapWithProvider from "./wrap-with-provider"
export const wrapRootElement = wrapWithProvider

wrap-with-provider. js:

import React from "react"
import { Provider } from "react-redux"

import createStore from "./src/state/createStore"

// eslint-disable-next-line react/display-name,react/prop-types
export default ({ element }) => {
  // Instantiating store in `wrapRootElement` handler ensures:
  //  - there is fresh store for each SSR page
  //  - it will be called only once in browser, when React mounts
  const store = createStore()
  return <Provider store={store}>{element}</Provider>
}

src / state / createStore. js:

import { createStore as reduxCreateStore } from "redux"
import Cookies from 'js-cookie'

const reducer = (state, action) => {
  if (action.type === `ACCEPT_COOKIES`) {
    return Object.assign({}, state, {
      acceptCookies: 'true',
    })
  }
  if (action.type === `DECLINE_COOKIES`) {
    return Object.assign({}, state, {
      acceptCookies: 'false',
    })
  }
  if (action.type === `RESET_COOKIES`) {
    return Object.assign({}, state, {
      acceptCookies: '',
    })
  }
  return state
}

const initialState = { acceptCookies: Cookies.get('acceptCookies') || '' }

const createStore = () => reduxCreateStore(reducer, initialState)
export default createStore

Повар ie уведомление о согласии является частью макета. js так, чтобы оно было на каждой странице всякий раз, когда это необходимо:

import CookieConsent from "react-cookie-consent"

const OurCookieConsent = ({ acceptCookies, accept, decline }) => (
  <CookieConsent
    location="bottom"
    enableDeclineButton={true}
    cookieName="acceptCookies"
    onDecline={decline}
    onAccept={accept}
  >
    <p>This website uses cookies for:</p>
    <ul>
      <li>REQUIRED: Saving your cookie preferences</li>
      <li>OPTIONAL: The contact form on the "Contact Us" page - for spam
      control (reCAPTCHA).</li>
      <li>OPTIONAL: Embedded YouTube videos e.g. videos in our news pages
      </li>
    </ul>
    <p>Declining consent will disable the features marked as OPTIONAL.</p>
    <p>For more in-depth detail or to withdraw or re-establish your
    consent at any time, please see the <Link to="/privacy-policy">Privacy Policy</Link>.</p>
  </CookieConsent>
)

OurCookieConsent.propTypes = {
  acceptCookies: PropTypes.string.isRequired,
  accept: PropTypes.func.isRequired,
  decline: PropTypes.func.isRequired,
}

const mapStateToProps = ({ acceptCookies }) => {
  return { acceptCookies }
}

const mapDispatchToProps = dispatch => {
  return {
    accept: () => dispatch({ type: `ACCEPT_COOKIES` }),
    decline: () => dispatch({ type: `DECLINE_COOKIES` })
  }
}

const ConnectedCookieConsent = connect(mapStateToProps, mapDispatchToProps)(OurCookieConsent)

Тогда ConnectedCookieConsent включается внутрь макет.

Ответы [ 2 ]

1 голос
/ 16 июля 2020

Кажется, у меня все заработало. Все дело в эффективности того, как Gatsby работает для производственных сайтов - он предварительно отрисовывает большую часть HTML через Node.js, а затем, когда размещенный сайт загружается и гидратируется, он обновляет только то, что, по его мнению, необходимо. Он не понимает, что весь предварительно отрендеренный «внешний» div (который был отрисован в «неизвестном» состоянии, поскольку во время SSR нет файлов cookie) необходимо повторно отрисовывать и обновлять только там, где он уверен, что что-то изменилось: внутреннее содержимое.

Таким образом, компонент должен «знать» ПОЛНОСТЬЮ refre sh самого себя, что он будет делать только при изменении состояния или свойств. Первоначальная гидратация не считается изменением (поскольку, по мнению Гэтсби, приложение не должно заботиться о том, находится ли оно в SSR или в браузере), оно остается в такой «неопределенности». Вы должны инициировать изменение состояния, что, по-видимому, можно сделать с помощью хука useEffect (), но лично мне показалось проще всего преобразовать его в класс и использовать componentDidMount (). Затем это состояние нужно использовать в функции рендеринга, чтобы она знала, «если состояние изменится, мне нужно заново отрендерить себя». Если вы хотите, чтобы видео отображалось сразу после того, как уведомление повара ie принято (без refre sh), вам также понадобится componentDidUpdate () (и нужно обновлять только состояние, если оно отличается, иначе это приведет к бесконечному l oop, следовательно, оператор if в приведенном ниже коде).

Это очень сложно, и я все еще не понимаю его на 100%, но это мое приблизительное понимание ситуации, основанное на ответах на этот вопрос:

Можно ли принудительно выполнить повторную визуализацию компонента React без вызова setState?

и этот:

https://github.com/gatsbyjs/gatsby/issues/12413

Мой рабочий компонент выглядит следующим образом:

import React from "react"
import { connect } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"

function mapStateToProps(state) {
  return { acceptCookies: state.acceptCookies }
}

class YouTubeVideo extends React.Component {
  state = {
    acceptCookies: 'undecided',
  }

  componentDidMount() {
    this.setState({
      acceptCookies: this.props.acceptCookies,
    })
  }

  componentDidUpdate() {
    if (this.state.acceptCookies !== this.props.acceptCookies) {
      this.setState({
        acceptCookies: this.props.acceptCookies,
      })
    }
  }

  render () {
    const { acceptCookies } = this.state
    if (acceptCookies === 'undecided') {
      return (
          <Alert variant="warning">
            <p>
              We cannot show this YouTube video as cookies  have not been accepted,
              but are required for YouTube to work.  To comply with GDPR law, the
              video is not displayed until you have accepted cookies.
            </p>
            <p>
              If you do not see the cookie banner, please try resetting your
              preferences from the <Link to="/privacy-policy">Privacy Policy</Link> or
              disabling any content blockers.
            </p>
          </Alert>
      )
    }
    if (acceptCookies === 'false') {
      return(
          <Alert variant="warning">
            <p>
              We cannot show this YouTube video as cookies have been declined, but
              are required for YouTube to work.  To comply with GDPR law, the video
              is not displayed.
            </p>
            <p>
              You may reset your preferences from
              the <Link to="/privacy-policy">Privacy Policy</Link>
            </p>
          </Alert>
      )
    }

    if (acceptCookies === 'true') {
      return (
          <div className="embed-container">
            <iframe src={`https://www.youtube.com/embed/${this.props.code}?rel=0`}
              title={`YouTube Video ${this.props.code}`}
              frameBorder="0"
              allowFullScreen
            >
            </iframe>
          </div>
      )
    }
    return (
      <p><a href={`https://www.youtube.com/watch?v=${this.props.code}`}>YouTube Video</a></p>
    )
  }
}


export default connect(mapStateToProps)(YouTubeVideo)

Итак, напомним ....

Компонент имеет собственное локальное состояние с acceptCookies, изначально установленным на 'undecided', что и будет SSR генерировать (и то, что в конечном итоге увидят Google или люди с отключенным JS), и это также то, что вы видите в течение нескольких мс при загрузке, если у вас есть cook ie, пока не загрузятся рабочие JS и страница не гидратируется. Это вызывает краткую ошибку sh нежелательного контента или FOU C, но я не думаю, что с этим можно что-то сделать и при этом соответствовать требованиям GDPR. Ему требуется javascript для загрузки, чтобы проверить файлы cookie и узнать, что у него есть разрешение на рендеринг встраиваемых файлов YouTube (а также множество нежелательных файлов cookie, которые Google сбрасывает пользователю, что требует всего этого упражнения). Как только он гидратируется, вызывается componentDidMount () и обновляет локальное состояние до текущего свойства acceptCookies (к которому мы скоро вернемся). Если он другой (т.е. вы уже приняли файлы cookie), то компонент видит изменение состояния и полностью перерисовывает себя.

Внизу вы видите, что компонент экспортируется через соединение (mapStateToProps), который берет глобальное состояние (контекст) из redux, передает его через функцию mapStateToProps, которая выдает опору acceptCookies, которая затем привязывается обратно к компоненту. Это, к счастью, происходит до componentDidMount (), поэтому он знает, как правильно обновить состояние. ), мы уже знаем, что редуктор ACCEPT_COOKIES в createStore. js запускается, в результате чего состояние редукции обновляется до acceptCookies = 'true'. Затем функция подключения обновляет свойства компонента для соответствия (аналогично тому, как это работало раньше), но поскольку теперь мы используем состояние, а не свойства в функции рендеринга, этого недостаточно для запуска повторного рендеринга - нам также нужен componentDidUpdate (), чтобы уловить изменение реквизита и обновить состояние, чтобы оно соответствовало.

Поправьте меня, если я сделал что-то глупое, но, похоже, это помогло. Надеюсь, эта многословность поможет кому-то еще в моей ситуации.

Мне нравится запасной вариант, который я придумал, - просто предоставить ссылку (поскольку в соответствии с GDPR вы не несете ответственности за файлы cookie, которые размещает другой сайт, если это внешняя ссылка, это просто, если вы встраиваете их сайт как iframe или тому подобное, он считается вашей "частью"), и в конце, вероятно, обновит мой ложный / неопределенный вывод, чтобы сделать это (и предоставить небольшое объяснение), вместо того, чтобы отображать большое предупреждение и отказывать пользователю в возможность посмотреть видео.

0 голосов
/ 15 июля 2020

Я бы написал компонент YoutubeVideo вместо этого:

import React from "react"
import { useSelector } from "react-redux"
import { Alert } from "react-bootstrap"
import { Link } from "gatsby"

const YouTubeVideo = (props) => {
    const acceptCookies = useSelector((state) => state.acceptCookies)

    if (acceptCookies === "")
        return (
            <Alert variant="warning">
                <p>
                    We cannot show this YouTube video as cookies have not been
                    accepted, but are required for YouTube to work. To comply
                    with GDPR law, the video is not displayed until you have
                    accepted cookies.
                </p>
                <p>
                    If you do not see the cookie banner, please try resetting
                    your preferences from the{" "}
                    <Link to="/privacy-policy">Privacy Policy</Link> or
                    disabling any content blockers.
                </p>
            </Alert>
        )

    if (acceptCookies === "false")
        return (
            <div className="embed-container">
                <iframe
                    src={`https://www.youtube.com/embed/${props.code}?rel=0`}
                    title={`YouTube Video ${props.code}`}
                    frameBorder="0"
                    allowFullScreen
                ></iframe>
            </div>
        )

    if (acceptCookies === "true")
        return (
            <div className="embed-container">
                <iframe
                    src={`https://www.youtube.com/embed/${props.code}?rel=0`}
                    title={`YouTube Video ${props.code}`}
                    frameBorder="0"
                    allowFullScreen
                ></iframe>
            </div>
        )

    // Maybe add a default ?
    return (
        <Alert variant="warning">
            <p>
                Not sure what's up here, but hey, better safe than sorry :)
            </p>
        </Alert>
    )
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...