Unverified Commit da88341f authored by Tasso Evangelista's avatar Tasso Evangelista Committed by GitHub
Browse files

perf: Avoid re-renders due to PropsContext (#154)

* Use the same empty object to represent no props

* Replace useProps hooks with PropsProvider component
parent c43294fe
This diff is collapsed.
import PropTypes from 'prop-types';
import React, { useEffect, useState, useRef } from 'react';
import { useProps } from '../../../hooks';
import { PropsProvider } from '../PropsContext';
export function AnimatedVisibility({ children, visibility: propVisibility = AnimatedVisibility.HIDDEN, onVisible = () => {} }) {
const [visibility, setVisibility] = useState(propVisibility);
......@@ -37,7 +37,11 @@ export function AnimatedVisibility({ children, visibility: propVisibility = Anim
}
}, [visibility, propVisibility]);
const [, PropsProvider] = useProps(({ className, ...props }) => ({
if (visibility === AnimatedVisibility.HIDDEN) {
return null;
}
return <PropsProvider children={children} fn={({ className, ...props }) => ({
className: [
'rcx-box--animated',
className,
......@@ -48,13 +52,7 @@ export function AnimatedVisibility({ children, visibility: propVisibility = Anim
].filter(Boolean).join(' '),
...props,
ref,
}), [visibility, propVisibility, ref]);
if (visibility === AnimatedVisibility.HIDDEN) {
return null;
}
return <PropsProvider children={children} />;
})} memoized />;
}
AnimatedVisibility.HIDDEN = 'hidden';
......
import PropTypes from 'prop-types';
import React from 'react';
import { useProps } from '../../../hooks';
import { PropsProvider } from '../PropsContext';
export function FlexContainer({ inline = false, children, direction, wrap, alignItems, alignContent, justifyContent }) {
const [, PropsProvider] = useProps(({ className, ...props }) => ({
return <PropsProvider children={children} fn={({ className, ...props }) => ({
className: [
className,
'rcx-box--flex',
......@@ -16,9 +16,7 @@ export function FlexContainer({ inline = false, children, direction, wrap, align
justifyContent && `rcx-box--flex-justify-${ justifyContent }`,
].filter(Boolean).join(' '),
...props,
}), [inline, direction, wrap, alignItems, alignContent, justifyContent]);
return <PropsProvider children={children} />;
})} memoized />;
}
FlexContainer.propTypes = {
......@@ -31,7 +29,7 @@ FlexContainer.propTypes = {
};
export function FlexItem({ children, order, grow, shrink, basis, align }) {
const [, PropsProvider] = useProps(({ className, style, ...props }) => ({
return <PropsProvider children={children} fn={({ className, style, ...props }) => ({
className: [
className,
align && `rcx-box--flex-self-${ align }`,
......@@ -44,9 +42,7 @@ export function FlexItem({ children, order, grow, shrink, basis, align }) {
flexBasis: basis,
},
...props,
}), [order, grow, shrink, basis, align]);
return <PropsProvider children={children} />;
})} memoized />;
}
FlexItem.propTypes = {
......
import PropTypes from 'prop-types';
import React from 'react';
import { useProps } from '../../../hooks';
import { PropsProvider } from '../PropsContext';
const mapSpacing = (spacing) => {
if (typeof spacing === 'number') {
......@@ -27,7 +27,7 @@ export function Margins({
inlineStart,
inlineEnd,
}) {
const [, PropsProvider] = useProps(({ className, ...props }) => ({
return <PropsProvider children={children} fn={({ className, ...props }) => ({
className: [
className,
all && `rcx-box--m-${ mapSpacing(all) }`,
......@@ -39,9 +39,7 @@ export function Margins({
inlineEnd && `rcx-box--mie-${ mapSpacing(inlineEnd) }`,
].filter(Boolean).join(' '),
...props,
}), [all, block, blockStart, blockEnd, inline, inlineStart, inlineEnd]);
return <PropsProvider children={children} />;
})} memoized />;
}
Margins.propTypes = {
......
import React, { createContext, useContext, memo } from 'react';
export const noProps = {};
export const PropsContext = createContext(noProps);
export const useProps = () => useContext(PropsContext);
const MemoizedPropsProvider = memo(function MemoizedProps({ children, ...props }) {
return <PropsContext.Provider children={children} value={props} />;
});
export function PropsProvider({ children, fn, memoized = false }) {
if (!fn) {
return <PropsContext.Provider children={children} value={noProps} />;
}
const props = fn(useContext(PropsContext));
if (memoized) {
return <MemoizedPropsProvider children={children} {...props} />;
}
return <PropsContext.Provider children={children} value={props} />;
}
import PropTypes from 'prop-types';
import React, { useCallback, useRef } from 'react';
import { useProps } from '../../../hooks';
import { PropsProvider } from '../PropsContext';
export function Scrollable({ children, horizontal, vertical, smooth, onScrollContent }) {
const scrollTimeoutRef = useRef();
const handleScroll = useCallback(function(event) {
const { target } = event;
const returnTouchingEdges = () => ({ top: !target.scrollTop, bottom: !(target.scrollTop + target.clientHeight - target.scrollHeight), left: !target.scrollLeft, right: !(target.scrollLeft + target.clientWidth - target.scrollWidth) });
const getTouchingEdges = () => ({
top: !target.scrollTop,
bottom: !(target.scrollTop + target.clientHeight - target.scrollHeight),
left: !target.scrollLeft,
right: !(target.scrollLeft + target.clientWidth - target.scrollWidth),
});
if (!scrollTimeoutRef.current) {
onScrollContent(returnTouchingEdges());
onScrollContent(getTouchingEdges());
}
clearTimeout(scrollTimeoutRef.current);
scrollTimeoutRef.current = setTimeout(() => {
scrollTimeoutRef.current = false;
onScrollContent(returnTouchingEdges());
onScrollContent(getTouchingEdges());
}, 200);
}, [onScrollContent]);
const [, PropsProvider] = useProps(({ className, ...props }) => ({
return <PropsProvider children={children} fn={({ className, ...props }) => ({
className: [
className,
'rcx-box--scrollable',
......@@ -29,9 +34,7 @@ export function Scrollable({ children, horizontal, vertical, smooth, onScrollCon
].filter(Boolean).join(' '),
onScroll: typeof onScrollContent !== 'undefined' ? handleScroll : undefined,
...props,
}), [horizontal, vertical, smooth]);
return <PropsProvider children={children} />;
})} memoized />;
}
Scrollable.propTypes = {
......
import PropTypes from 'prop-types';
import React, { createElement, forwardRef, memo } from 'react';
import React, { createElement, forwardRef, memo, useLayoutEffect } from 'react';
import { useStyleSheet, useProps } from '../../hooks';
import { use, unuse } from '../../index.scss';
import { useProps, PropsProvider } from './PropsContext';
const getClassNamesFromModifiers = (element, modifiers) => {
const modifierClassNames = [];
......@@ -54,12 +55,17 @@ export const Box = memo(forwardRef(function Box({
textStyle,
...props
}, ref) {
useStyleSheet();
const [{
useLayoutEffect(() => {
use();
return unuse;
}, [use, unuse]);
const {
className: contextualClassName,
style: contextualStyle,
...contextualProps
}, PropsProvider] = useProps();
} = useProps();
const [modifiersProps, otherProps] = filterModifierProps({ ...contextualProps, ...props });
const children = createElement(is, {
......@@ -121,6 +127,8 @@ Box.extend = (componentClassName, is) => {
return BoxExtension;
};
export * from './PropsContext';
export * from './AnimatedVisibility';
export * from './Flex';
export * from './Margins';
......
import PropTypes from 'prop-types';
import React from 'react';
import { useProps } from '../../hooks';
import { Box } from '../Box';
import { Box, PropsProvider } from '../Box';
const Base = Box.extend('rcx-button-group');
function ButtonGroupChild({ children }) {
const [, PropsProvider] = useProps(({ className }) => ({
className: [className, 'rcx-button-group__item'].filter(Boolean).join(' '),
}));
return <PropsProvider children={children} />;
}
export function ButtonGroup({
align,
children,
......@@ -30,9 +21,9 @@ export function ButtonGroup({
role='group'
{...props}
>
<ButtonGroupChild>
{children}
</ButtonGroupChild>
<PropsProvider children={children} fn={({ className }) => ({
className: [className, 'rcx-button-group__item'].filter(Boolean).join(' '),
})} />
</Base>;
}
......
export * from './useProps';
export * from './useStyleSheet';
import React, { createContext, useCallback, useContext } from 'react';
const PropsContext = createContext({});
export const useProps = (fn = () => ({}), deps = []) => [
useContext(PropsContext),
useCallback(function PropsProvider({ children }) {
const ancestorProps = useContext(PropsContext);
return <PropsContext.Provider children={children} value={fn(ancestorProps)} />;
}, deps),
];
import { useLayoutEffect } from 'react';
import css from '../index.scss';
export const useStyleSheet = () => {
useLayoutEffect(() => {
css.use();
return () => {
css.unuse();
};
}, [css]);
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment