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)
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. 😁