File List View

Create a FileTree ListView in React Native

tl;dr

Demonstration of creating a file tree listview in React Native without third-party plugin. This demonstration is using latest release of React Native (0.59.5), React Hooks and functional components.

Personal feedback: Previously, I tend to use class components for creating components. But, I found out that functional components work better with Hot Reloading compare to class components. Now, with the help of React Hooks, making the functional components better. 🙂

  • FlatList
  • Native-base (Optional)
File List View

Main()

First of all, basically this is a listview using <FlatList />component from React Native. So, we can setup a basic listview component that composed <FlatList />.

import React, { memo, useState } from "react";
import { FlatList, Platform } from "react-native";

type Props = {
  data: Array<any>
};
function FileTree(props: Props) {
  const { data } = props;

  function renderNode({ item, index }) {
    return <FileTreeNode item={item} index={index} />;
  }

  // You can add others props to the <FlatList />
  // To optimize the performance
  return (
    <FlatList
      data={data}
      renderItem={renderNode}
      keyExtractor={keyExtractor}
    />
  );
}

// To keep every item in the list as unique as possible
// This might change accordingly to your data structure
function keyExtractor(item, index) {
  return `list-${item?.node?.label}-${index}`;
}

export default memo<Props>(FileTree);

So, this is the initial setup of our <FileTree />. No worry for any error or missing part.

Now, we can start implement the component <FileTreeNode />.

type NProps = {
  item: Array<any>,
  index: number
};
const FileTreeNode = memo<NProps>(function({ item, index }) {
  const [isOpen, setIsOpen] = useState(false);
  const label = item?.node?.label;
  const numberOfChildren = item?.children?.length || 0;
  const children = numberOfChildren > 0 ? item?.children : [];

  function handleOnPress(e) {
    setIsOpen(!isOpen);
  }

  const indentation = (item?.level + 1) * 20;
  return (
    <>
      <ListItem
        onPress={numberOfChildren > 0 ? handleOnPress : null}
        iconLeft
        style={{ marginLeft: indentation }}
      >
        <Left style={{ flex: 0 }}>
          {item?.node?.flag ? (
            <Thumbnail
              small
              source={item.node.flag}
              square
              resizeMethod="resize"
              resizeMode="contain"
            />
          ) : (
            <Icon name="pin" />
          )}
        </Left>
        <Body>
          <Text>{label}</Text>
        </Body>

        {numberOfChildren > 0 ? (
          <Right>
            {isOpen ? (
              <Icon name="keyboard-arrow-down" type="MaterialIcons" />
            ) : (
              <Icon name="keyboard-arrow-left" type="MaterialIcons" />
            )}
          </Right>
        ) : null}
      </ListItem>
      {isOpen && numberOfChildren > 0 ? <FileTree data={children} /> : null}
    </>
  );
});

<>...</> – act as a container to wrap the children component.

const [isOpen, setIsOpen] = useState(false);
const label = item?.node?.label;
const numberOfChildren = item?.children?.length || 0;
const children = numberOfChildren > 0 ? item?.children : [];

useState – React Hooks
?.Optional Chaining

Maybe you might wonder why item?.node?.label and item?.children ?Well, this is depend on how you structure your data. I structure it as follow.

export default [
  {
    node: {...detailsOfNode},
    level: 0, // for dynamically set the indentation margin
    children: [
      {
        node: {...detailsOfNode},
        level: 1,
        children: []
      }
    ]
  }
]
// any node will following the same pattern, including children

Anything inside the <ListItem /> is totally up to how you want to design layout and display what kind of information to user. I use the component from native-base to simplify the job. It can be replace by a <TouchableOpacity /> component.

Last but not least, we are going to reuse the <FileTree /> component to display our children from the data instead of writing a new <FlatList />

<FileTree data={children} />

End()

If you need a third-party plugin to accomplish the task. There is one available – react-native-nested-listview.

Thanks for reading and Happy Coding.  😁