React Hooks - поставщик контекста не сразу отображает отправленное состояние - PullRequest
0 голосов
/ 25 января 2020

Моя цель: Я создаю таблицу, которая отображает данные для пользователей. Основываясь на определенном значении, хранящемся в глобальном состоянии (хранящемся в функции редуктора, которую API контекста предоставляет другим компонентам), я фиксирую этот заголовок в верхней части страницы при прокрутке, но только , когда таблица на виду . Для этого мне нужно зарегистрировать прослушиватель событий Scroll и Resize, чтобы пересчитать положение таблиц, когда пользователь прокручивает или изменяет размер экрана. Я хочу обновить глобальное состояние, чтобы оно было isHeaderActivelyFixed: true только тогда, когда таблица имеет в поле зрения, а состояние еще не установлено на isHeaderActivelyFixed: true. В противном случае я буду постоянно обновлять состояние каждый раз, когда таблица отображается, и пользователь прокручивает до isHeaderActivelyFixed: true, а также, когда его не видно, isHeaderActivelyFixed: false

Проблема: I подготовьте описанный выше сценарий так, как я считаю нужным. Однако, когда я отправляю в глобальное состояние, а затем в консольный журнал или использую это глобальное состояние, это не отражает то, что я только что отправил ему. Инструменты реагирования разработчика DO показывают обновленное состояние, которое я отправил, но мне нужно иметь возможность обновлять это новое состояние отправки в функции, в которую я отправил его. Таким образом, я знаю, что не отправлять его снова. Я надеюсь, что в этом есть смысл. Заранее спасибо!

Код: (Примечание: я удалил ненужный код, поэтому некоторые вещи могут показаться странными. Я оставил некоторый код, чтобы обеспечить контекст для проблемы. Области где я прокомментировал, где возникают проблемы. Функция isActivelyViewed() просто получает таблицы getBoundingClientRect() и проверяет, видна ли она до сих пор)

ProductTableStore.jsx

import React from 'react';

const initialState = {
  isLoading: true,
  isSelectable: null,
  isHeaderFixedOnScroll: null,
  isHeaderActivelyFixed: null,
  isAddToCartEnabled: null,
  productTableActiveWidth: null,
  addToCartData: null,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'setIsHeaderFixedOnScroll':
      return {
        ...state,
        isHeaderFixedOnScroll: action.isHeaderFixedOnScroll,
      };
    case 'setIsHeaderActivelyFixed':
      return {
        ...state,
        isHeaderActivelyFixed: action.isHeaderActivelyFixed,
      };
    case 'setProductTableActiveWidth':
      return {
        ...state,
        productTableActiveWidth: action.productTableActiveWidth,
      };
    default:
      throw new Error(
        `Unexpected or missing action type. Action type provided was: ${action.type}`
      );
  }
};

const ProductTableContext = React.createContext({});

const ProductTableStore = () => {
  return React.useContext(ProductTableContext);
};

const ProductTableProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  return (
    <ProductTableContext.Provider value={[state, dispatch]}>
      {children}
    </ProductTableContext.Provider>
  );
};

export { ProductTableStore, ProductTableProvider };

ProductTable.jsx (файл, в котором у меня проблема)

import React from 'react';
import { ProductTableStore } from './ProductTableStore/ProductTableStore';
import { isActivelyViewed } from '../../js/helpers';

const ProductTable = ({ heading, ariaLabel, children }) => {
  const [globalState, dispatch] = ProductTableStore();

  const [isOnScrollResizeEventRegistered, setIsOnScrollResizeEventRegistered] = React.useState(
    null
  );

  const ProductTableRef = React.useRef(null);

  const registerOnScrollResizeEvent = (ref, resolve) => {
    console.log('Registering onScrollandResize');
    window.addEventListener(
      'scroll',
      _.throttle(() => {
        calculatePosition(ref);
      }),
      10
    );
    window.addEventListener(
      'resize',
      _.throttle(() => {
        calculateProductTableValues(ref);
      }),
      10
    );
    if (resolve) resolve();
  };

  const setIsHeaderActivelyFixed = (isHeaderActivelyFixed) => {
    console.log('fx setIsHeaderActivelyFixed. Passed argument:', isHeaderActivelyFixed);
    dispatch({
      type: 'setIsHeaderActivelyFixed',
      isHeaderActivelyFixed,
    });
    console.log('isHeaderActivelyFixed', globalState.isHeaderActivelyFixed); 
    // This comes back null and idk why! I thought it may be because there hasn't been a re-render but 
    // changing the callback on the effect below doesn't seem to change that

  };

  const setProductTableActiveWidth = (productTableActiveWidth) => {
    console.log('fx setProductTableActiveWidth');
    dispatch({
      type: 'setProductTableActiveWidth',
      productTableActiveWidth: `${productTableActiveWidth}px`,
    });
    console.log('productTableActiveWidth', globalState.productTableActiveWidth);
    // This comes back null and idk why! I thought it may be because there hasn't been a re-render but 
    // changing the callback on the effect below doesn't seem to change that
  };

  const calculatePosition = (ref) => {
    if (isActivelyViewed(ref.current) && !globalState.isHeaderActivelyFixed) {
      setIsHeaderActivelyFixed(true);
    } else if (!isActivelyViewed(ref.current) && globalState.isHeaderActivelyFixed) {
      // This never works because globalState never reflects updates in this function
      setIsHeaderActivelyFixed(false);
    } else {
      console.log('None of these...');
    }
  };

  const calculateWidth = (ref) => {
    if (ref.current.offsetWidth !== globalState.productTableActiveWidth) {
      setProductTableActiveWidth(ref.current.offsetWidth);
    }
  };

  const calculateProductTableValues = (ProductTableRef, resolve) => {
    calculateWidth(ProductTableRef);
    calculatePosition(ProductTableRef);
    if (resolve) resolve();
  };

  React.useEffect(() => {
    if (!globalState.isHeaderFixedOnScroll) return;
    new Promise((resolve, reject) => {
      if (isOnScrollResizeEventRegistered) reject();
      if (!isOnScrollResizeEventRegistered) {
        // Calculate intital PT width so that we only have to recalculate on resize
        calculateProductTableValues(ProductTableRef, resolve);
      }
    })
      .then(() => {
        registerOnScrollResizeEvent(ProductTableRef);
      })
      .then(() => {
        setIsOnScrollResizeEventRegistered(true);
      })
      .catch((err) => {
        console.error(
          'Unable to create promise for fixing the Product Table Header on scroll. The error returned was: ',
          err
        );
      });
  }, [globalState.isHeaderFixedOnScroll]);

  return (
    <ThemeProvider theme={getSiteTheme(_app.i18n.getString({ code: 'styledComponents.theme' }))}>
      <StyledProductTableContainer>
        {globalState.isAddToCartEnabled && (
          <StyledAddToCartContainer>
            <AddToCartForm
              buttonText={_app.i18n.getString({ code: 'cart.add.allItems' })}
              isDisabled={globalState.addToCartData.length === 0}
              ajaxData={globalState.addToCartData}
            />
          </StyledAddToCartContainer>
        )}
        {heading && <FeaturedHeader>{heading}</FeaturedHeader>}
        <StyledProductTable ref={ProductTableRef} ariaLabel={ariaLabel}>
          {globalState.isLoading && (
            <ThemeProvider theme={loadingStyles}>
              <StyledLoadingSpinner />
            </ThemeProvider>
          )}
          {children}
        </StyledProductTable>
      </StyledProductTableContainer>
    </ThemeProvider>
  );
};

const ProductTableHeader = ({ children }) => {
  const [globalState] = ProductTableStore();
  return (
    <StyledProductTableHeader
      isSelectable={globalState.isSelectable}
      isHeaderFixedOnScroll={globalState.isHeaderFixedOnScroll}
      isHeaderActivelyFixed={globalState.isHeaderActivelyFixed}
      fixedWidth={globalState.productTableActiveWidth}
    >
      {globalState.isSelectable && (
        <StyledProductTableLabel isSelect>Select</StyledProductTableLabel>
      )}
      {children}
    </StyledProductTableHeader>
  );
};

const ProductTableRow = ({ children }) => {
  const [globalState] = ProductTableStore();

  return (
    <StyledProductTableRow isSelectable={globalState.isSelectable}>
      {globalState.isSelectable && (
        <StyledProductTableCell isSelect>
          <GenericCheckbox />
        </StyledProductTableCell>
      )}
      {children}
    </StyledProductTableRow>
  );
};

export {
  ProductTable,
  ProductTableHeader,
  ProductTableRow,
};

Ответы [ 2 ]

0 голосов
/ 06 февраля 2020

Решение

В итоге я создал собственный хук, чтобы обрабатывать это. Основная проблема заключалась в том, что я пытался опираться на глобальные значения состояния, которые не обновлялись сразу. Вместо этого я создал ссылки, которые соответствовали значениям, которые я отправлял в глобальное состояние, и вместо этого проверил их по ссылкам.

ProductTableStore.jsx (файл глобального состояния) import React from 'реагировать';

const initialState = {
  isLoading: true,
  isSelectable: null,
  isHeaderFixedOnScroll: null,
  isHeaderActivelyFixed: null,
  isAddToCartEnabled: null,
  productTableActiveWidth: null,
  addToCartData: null,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'setIsHeaderFixedOnScroll':
      return {
        ...state,
        isHeaderFixedOnScroll: action.isHeaderFixedOnScroll,
      };
    case 'setIsHeaderActivelyFixed':
      return {
        ...state,
        isHeaderActivelyFixed: action.isHeaderActivelyFixed,
      };
    case 'setProductTableActiveWidth':
      return {
        ...state,
        productTableActiveWidth: action.productTableActiveWidth,
      };
    default:
      throw new Error(
        `Unexpected or missing action type. Action type provided was: ${action.type}`
      );
  }
};

const ProductTableContext = React.createContext({});

const ProductTableStore = () => {
  return React.useContext(ProductTableContext);
};

const ProductTableProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  return (
    <ProductTableContext.Provider value={[state, dispatch]}>
      {children}
    </ProductTableContext.Provider>
  );
};

export { ProductTableStore, ProductTableProvider };

ProductTable.jsx

const ProductTable = ({ heading, ariaLabel, children }) => {
  const [globalState, dispatch] = ProductTableStore();

  const ProductTableRef = React.useRef(null);

  const setIsHeaderActivelyFixed = (isHeaderActivelyFixed) => {
    dispatch({
      type: 'setIsHeaderActivelyFixed',
      isHeaderActivelyFixed,
    });
  };

  const setProductTableActiveWidth = (productTableActiveWidth) => {
    dispatch({
      type: 'setProductTableActiveWidth',
      productTableActiveWidth: `${productTableActiveWidth}px`,
    });
  };

  const useShouldHeaderBeFixed = (ref) => {
    if (!globalState.isHeaderFixedOnScroll) return;

    // keep mutable refs of values pertinent to the fixed header for the lifetime of the component
    const fixedState = React.useRef(null);
    const fixedWidth = React.useRef(null);

    const [shouldHeaderBeFixed, setShouldHeaderBeFixed] = React.useState(false);

    const calculateTablePosition = () => {
      if (!fixedState.current && isActivelyViewed(ref.current)) {
        setShouldHeaderBeFixed(true);
        fixedState.current = true;
      } else if (!!fixedState.current && !isActivelyViewed(ref.current)) {
        setShouldHeaderBeFixed(false);
        fixedState.current = false;
      }
    };

    const calculateTableWidth = () => {
      if (fixedWidth.current !== ProductTableRef.current.offsetWidth) {
        setProductTableActiveWidth(ProductTableRef.current.offsetWidth);
        fixedWidth.current = ProductTableRef.current.offsetWidth;
      }
    };

    const calculateTablePositionAndWidth = () => {
      calculateTablePosition();
      calculateTableWidth();
    };

    React.useEffect(() => {
      calculateTablePositionAndWidth();
    }, []);

    React.useEffect(() => {
      window.addEventListener('scroll', calculateTablePosition);
      window.addEventListener('resize', calculateTablePositionAndWidth);
      return () => {
        window.removeEventListener('scroll', calculateTablePosition);
        window.removeEventListener('resize', calculateTablePositionAndWidth);
      };
    }, [isActivelyViewed(ref.current)]);

    return shouldHeaderBeFixed;
  };

  // initiallize our custom hook
  const shouldHeaderBeFixed = useShouldHeaderBeFixed(ProductTableRef);

  // listen only to our custom hook to set global state for the fixed header
  React.useEffect(() => {
    setIsHeaderActivelyFixed(shouldHeaderBeFixed);
  }, [shouldHeaderBeFixed, globalState.isHeaderFixedOnScroll]);
...
0 голосов
/ 27 января 2020

Попробуйте это

  const setIsHeaderActivelyFixed = (isHeader) => {
       dispatch({
          type: 'setIsHeaderActivelyFixed',
          isHeaderActivelyFixed: isHeader
          });
   };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...