Настройка контекста не обновляет состояние - PullRequest
0 голосов
/ 31 октября 2019

Контекст всегда ставит меня в тупик. Я надеюсь, что новый Context API поможет, но до сих пор застрял. Я могу получить начальное значение для отображения, но обновления, основанные на моем эффекте, не обновляют компонент со значением useContext. Что я делаю не так?

Здесь «нужно» контекст излишне, но я хочу иметь контекстный API, который дает значение позиции прокрутки окна, просто чтобы проверить, как работает поставщик контекста / потребительвзаимодействует с детьми:

context.js

import React, { useState, FC, useEffect } from "react";

interface IInitialContext {
  scrollY: number;
  // do to - implement removeEventListener function
}
const intialContext = {
  scrollY: window.scrollY,
  // removeEventListener: () => {},
};

const ScrollContext = React.createContext<IInitialContext>(intialContext);

const VerticalScrollContextProvider: FC = ({ children }) => {
  const [scrollY, setScrollY] = useState(intialContext.scrollY);
  // correctly logs scrollY

  useEffect(() => {
    window.addEventListener(
      "scroll",
      debounce(() => setScrollY(window.scrollY), 100) // correctly captures new scrollY position
    );

    return () => window.removeEventListener("scroll", debounce)
  }, [scrollY]); // re-add listener when scrollY changes

  return (
    <ScrollContext.Provider value={{ scrollY }}> //i don't really need the value passed to component
      {children}
    </ScrollContext.Provider>
  );
};

function useScrollState() {
  const context = React.useContext(ScrollContext);
  if (context === undefined) {
    throw new Error(
      "useScrollState must be used within a VerticalScrollContextProvider"
    );
  }
  return context; // correctly tracks initial state 
}

export { VerticalScrollContextProvider, useScrollState };

и моим компонентом:

const Home = () => {
  const { scrollY } = useScrollState();
  // correctly has value on app first loading, does not receive updates.
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    console.log("clicked button");
  };
  return (
    <VerticalScrollContextProvider>
      <StyledHome>
        <div id="text-wrapper">
          <StyledFirstName>Phil</StyledFirstName>
          <StyledLastName>Lucks</StyledLastName>
        </div>
        <Button text="clicker" onClick={handleClick} />
      </StyledHome>
    </VerticalScrollContextProvider>
  );
};

export default Home;

Я ценю любую проницательность.

1 Ответ

0 голосов
/ 31 октября 2019

Чтобы контекст работал, компонент должен находиться внутри провайдера.

Из документов useContext

Принимает объект контекста (значение, возвращенное из React.createContext) и возвращает текущее значение контекста для этого контекста. Текущее значение контекста определяется значением prop ближайшего над вызывающим компонентом в дереве.

Когда обновляется ближайший выше компонента, этот хук инициирует повторное рендеринг с последним значением контекста, переданным этому MyContextПоставщик.

Теперь внимательно посмотрим на компонент <Home />.

const Home = () => {
  const { scrollY } = useScrollState();
  // correctly has value on app first loading, does not receive updates.
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    console.log("clicked button");
  };
  return (
    <VerticalScrollContextProvider>
      <StyledHome>
        <div id="text-wrapper">
          <StyledFirstName>Phil</StyledFirstName>
          <StyledLastName>Lucks</StyledLastName>
        </div>
        <Button text="clicker" onClick={handleClick} />
      </StyledHome>
    </VerticalScrollContextProvider>
  );
};

Эта строка, которая нас интересует, это const { scrollY } = useScrollState();, которая пытается получить context изродительский Provider

Но, является ли <Home /> Компонент внутри <Provider />?

Нет. <Provider /> находится внутри <Home /> Component

Запустите приведенный ниже фрагмент кода и просмотрите журнал консоли, чтобы увидеть, какой компонент запускается первым.

const { useState, useEffect } = React;

const intialContext = {
  scrollY: window.scrollY,
};

const ScrollContext = React.createContext(intialContext);

const VerticalScrollContextProvider = ({ children }) => {
  const [scrollY, setScrollY] = React.useState(intialContext.scrollY);
  // correctly logs scrollY
  console.log("Rendering context");

  useEffect(() => {
    const eventListener = () => setScrollY(window.scrollY);
    window.addEventListener(
      "scroll",
       eventListener// correctly captures new scrollY position
    );

    return () => window.removeEventListener("scroll", eventListener)
  }, [scrollY]); // re-add listener when scrollY changes

  return (
    <ScrollContext.Provider value={{ scrollY }}>
      {children}
    </ScrollContext.Provider>
  );
};

function useScrollState() {
  const context = React.useContext(ScrollContext);
  if (context === undefined) {
    throw new Error(
      "useScrollState must be used within a VerticalScrollContextProvider"
    );
  }
  return context; // correctly tracks initial state 
}





const Home = () => {
  const { scrollY } = useScrollState();
  console.log("Rendering Home");
  // correctly has value on app first loading, does not receive updates.
  const handleClick = (e) => {
    e.preventDefault();
    console.log("clicked button");
  };
  return (
    <VerticalScrollContextProvider>
        <div id="text-wrapper">
          <h3>Phil</h3>
          <h3>Lucks</h3>
        </div>
        <button onClick={handleClick}>Button</button>
        <h2>Scroll and see the console logs</h2>
    </VerticalScrollContextProvider>
  );
};

ReactDOM.render(<Home />, document.getElementById("root"));
#root {
height: 1000px;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

И решение ...

Оберните Home компонент внутри Provider

import React from "react";
import {VerticalScrollContextProvider, useScrollState} from "./context";

const Home = () => {
  const { scrollY } = useScrollState();
  // correctly has value on app first loading, does not receive updates.
  const handleClick = (e) => {
    e.preventDefault();
    console.log("clicked button");
  };
  return (
      <div>
        <div id="text-wrapper">
          <h3>Phil</h3>
          <h3>Lucks</h3>
          {scrollY}
        </div>
        <button text="clicker" onClick={handleClick} />
      </div>
  );
};

export default () => (
  <VerticalScrollContextProvider><Home /></VerticalScrollContextProvider>
);

Запустите фрагмент ниже и прокрутите список журналов консоли.

const {
  useState,
  useEffect
} = React;

const intialContext = {
  scrollY: window.scrollY,
};

const ScrollContext = React.createContext(intialContext);

const VerticalScrollContextProvider = ({
  children
}) => {
  const [scrollY, setScrollY] = React.useState(intialContext.scrollY);
  // correctly logs scrollY
  console.log("Rendering context");

  useEffect(() => {
    const eventListener = () => setScrollY(window.scrollY);
    window.addEventListener(
      "scroll",
      eventListener // correctly captures new scrollY position
    );

    return () => window.removeEventListener("scroll", eventListener)
  }, [scrollY]); // re-add listener when scrollY changes

  return ( <
    ScrollContext.Provider value = {
      {
        scrollY
      }
    } > {
      children
    } <
    /ScrollContext.Provider>
  );
};

function useScrollState() {
  const context = React.useContext(ScrollContext);
  if (context === undefined) {
    throw new Error(
      "useScrollState must be used within a VerticalScrollContextProvider"
    );
  }
  return context; // correctly tracks initial state 
}





const Home = () => {
  const {
    scrollY
  } = useScrollState();
  console.log("Rendering Home", scrollY);
  // correctly has value on app first loading, does not receive updates.
  const handleClick = (e) => {
    e.preventDefault();
    console.log("clicked button");
  };
  return ( <
    React.Fragment >
    <
    div id = "text-wrapper" >
    <
    h3 > Phil < /h3> <
    h3 > Lucks < /h3> <
    /div> <
    button onClick = {
      handleClick
    } > Button < /button> <
    h2 > Scroll and see the console logs < /h2> <
    /React.Fragment>
  );
};

const HomeWithProvider = () => {
  return <VerticalScrollContextProvider >
    <
    Home / >
    <
    /VerticalScrollContextProvider>
}

ReactDOM.render( < HomeWithProvider / > , document.getElementById("root"));
#root {
  height: 1000px;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...