chramos / react-native-skeleton-placeholder

SkeletonPlaceholder is a React Native library to easily create an amazing loading effect.
MIT License
674 stars 120 forks source link

Extract Skeleton Items to another component then reuse it? #56

Open hoangvu12 opened 3 years ago

hoangvu12 commented 3 years ago

I have a component called Loader.tsx

import React from "react";
import SkeletonPlaceholder from "react-native-skeleton-placeholder";

type LoaderProps = {
  children: JSX.Element | JSX.Element[];
};

export default function Loader(props: LoaderProps) {
  const { children } = props;

  return (
    <SkeletonPlaceholder highlightColor="#333333" backgroundColor="#121212">
      {children}
    </SkeletonPlaceholder>
  );
}

Now I create a component contains Skeleton items

const VideoCardLoader = () => {
  return (
      <SkeletonPlaceholder.Item
        width={CardWidth}
        marginRight={CardPaddingRight}
        marginBottom={CardPaddingBottom}
      >
        <SkeletonPlaceholder.Item
          width={CardWidth}
          height={ImageHeight * ImageRatio}
          marginBottom={5}
        />
        <SkeletonPlaceholder.Item
          height={TitleFontSize}
          width={CardWidth * 0.7}
          marginBottom={5}
        />
        <SkeletonPlaceholder.Item
          height={StudiosFontSize}
          width={CardWidth * 0.4}
        />
      </SkeletonPlaceholder.Item>
  );
};

Now when I try to use it in HomeScreen, I would write

export default function HomeScreen() {
  const { data, isLoading, isError } = useHomePage();

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <Loader>
          <VideoCardLoader />
        </Loader>
      </View>
    );
  }

 return <Text>Loaded....</Text>
}

Sadly this way didn't works as I expected. This should works but somehow it didn't...

Edit: After minutes of trying, I found out this way works as I expected. But the syntax looks not that good.

import React from 'react';
import { SafeAreaView, Dimensions } from 'react-native';
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';

const { width: windowWidth } = Dimensions.get('window');

const Loader = ({ loader: Loader }) => (
  <SkeletonPlaceholder>{Loader}</SkeletonPlaceholder>
);

const AvatarLoader = (
  <SkeletonPlaceholder.Item flexDirection="row" alignItems="center">
    <SkeletonPlaceholder.Item width={60} height={60} borderRadius={50} />
    <SkeletonPlaceholder.Item marginLeft={20}>
      <SkeletonPlaceholder.Item width={120} height={20} borderRadius={4} />
      <SkeletonPlaceholder.Item
        marginTop={6}
        width={80}
        height={20}
        borderRadius={4}
      />
    </SkeletonPlaceholder.Item>
  </SkeletonPlaceholder.Item>
);

const PostLoader = (
  <>
    {AvatarLoader}
    <SkeletonPlaceholder.Item width={windowWidth} height={300} marginTop={10} />
  </>
);

const App = () => {
  return (
    <SafeAreaView>
      <Loader loader={PostLoader} />
    </SafeAreaView>
  );
};

export default App;
chramos commented 3 years ago

hmmm, good catch I'll try to do something to make it work

hoangvu12 commented 3 years ago

hmmm, good catch I'll try to do something to make it work

After looking at your source code, I figured out why it didn't work. So I tried this approach, take a look at it to see if it helps.

const getChildren = React.useCallback(
  (element: JSX.Element | JSX.Element[]) => {
    return React.Children.map(element, (child: JSX.Element, index: number) => {

      // If the component neither SkeletonItem nor View.
      // Then assume it is a custom component that using SkeletonPlaceholder
      // just return it directly.

      if (
        child.type.displayName !== "SkeletonPlaceholderItem" ||
        child.type.displayName !== "View"
      ) {
        return child;
      }

      let style: ViewStyle;
      if (child.type.displayName === "SkeletonPlaceholderItem") {
        const { children, ...styles } = child.props;
        style = styles;
      } else {
        style = child.props.style;
      }
      if (child.props.children) {
        return (
          <View key={index} style={style}>
            {getChildren(child.props.children)}
          </View>
        );
      } else {
        return (
          <View key={index} style={styles.childContainer}>
            <View style={[style, viewStyle]} />
          </View>
        );
      }
    });
  },
  [viewStyle]
);
Kakaranara commented 2 months ago

is this issue solved? im trying to make my component reusable