Nozbe / WatermelonDB

🍉 Reactive & asynchronous database for powerful React and React Native apps ⚡️
https://watermelondb.dev
MIT License
10.42k stars 584 forks source link

Poor list performance with minimal data. #1690

Open ibb2 opened 10 months ago

ibb2 commented 10 months ago

I am a little lost trying to solve the problem of my tasks app. Users can create tasks, and these tasks immediately get added to the database. The tasks are then rendered out on the in 2 Flat lists on the home view. The Flat lists have been moved out into there own separate components and are re used twice in the home view in order to render out a list of tasks in progress and a list of completed tasks.

When users decide to create a task, there is a lag when the item add to database function is called. And then another delay for when the item is rendered out onto the home page view. I could understand this there was a lot of items in the database however this problem starts with as little as 2 tasks created and get progressively worse.

I am unsure how to resolve these performance issues. I don't know what I am doing wrong. I have tried using the react dev tools, I've attached below the profile export (I was unsure what is was really doing but got a performance clip of adding a task). The peformance report was made with only ~24 items in the database. The problem is less severe on an actual mobile device, my galaxy s9, however there is still poor performance with too few items.

Section of home view

<ScrollView f={1} space="$4" paddingHorizontal={"$4"} mt={"$5"} theme="neutral">
  <DatabaseProvider database={database}>
    <EnhancedTaskList
      title="In Progress"
      userId={userId}
      filterQuery="is_completed"
      filter={false}
    />
    <EnhancedTaskList
      title="Is Completed"
      userId={userId}
      filterQuery="is_completed"
      filter
    />
  </DatabaseProvider>
</ScrollView>;

EhancedTaskList

const enhance = compose(
  withDatabase,
  withObservables(["userId"], ({ database, userId, filterQuery, filter }) => ({
    tasks: database
      .get("tasks")
      .query(
        Q.where(filterQuery, filter),
        Q.where("user_id", userId),
        Q.where("is_archived", Q.notEq(true))
      )
      .observe(),
    archiveTasksCount: database
      .get("tasks")
      .query(
        Q.where(filterQuery, filter),
        Q.where("user_id", userId),
        Q.where("is_archived", Q.notEq(true))
      )
      .observeCount(),
  }))
);

const EnhancedTaskList = enhance(TaskList);

export default EnhancedTaskList;

TaskList

const Task = ({ currentTask }) => {
  const [taskInput, setTaskInput] = useState<any>(currentTask.title);
  const task = useTaskStore((state) => state.task);
  const setTask = useTaskStore((state) => state.setTask);

  const updateTask = async function (
    updatedTitle: string,
    taskId: string
  ): Promise<void> {
    const task: any = await database.get("tasks").find(taskId);
    await task.updateTask(updatedTitle);

    const updateTask = {
      taskId: task.taskId,
      edit: false,
    };
    setTask(updateTask);
  };

  return (
    <YStack width="100%" padding={0} theme="primary">
      {task.taskId == currentTask.id && task.edit ? (
        <Input
          width="100%"
          m={0}
          onChangeText={(text) => {
            setTaskInput(text);
          }}
          value={taskInput}
          onSubmitEditing={() => {
            void updateTask(taskInput, currentTask.id);
          }}
          editable={task.taskId == currentTask.id ? task.edit : false}
        />
      ) : (
        <Text paddingLeft="$4">{taskInput}</Text>
      )}
    </YStack>
  );
};

const TaskList = (props: TaskListProps) => {
  const { title, tasks, archiveTasksCount } = props;

  // Collapse sections
  const [isCollapsed, setIsCollapsed] = useState<boolean>(false);

  return (
    <YStack>
      <Pressable
        onPress={() => {
          setIsCollapsed(!isCollapsed);
        }}
      >
        <XStack jc="space-between" ai="center" alignContent="center">
          <SizableText size="$5" fontWeight="bold">
            {title}
          </SizableText>
          {archiveTasksCount > 0 && title === "Completed" && (
            <Button
              size="$2"
              onPress={() => {
                void archiveTasks();
              }}
              theme="neutral"
            >
              Archive
            </Button>
          )}
        </XStack>
      </Pressable>
      <Collapsible collapsed={isCollapsed}>
        <YGroup bordered size="$4" theme="primary" p={0}>
          <FlatList
            style={{ padding: 0, margin: 0 }}
            scrollEnabled={false}
            initialNumToRender={25}
            data={tasks}
            renderItem={({ item }) => {
              return (
                <CustomStyleSwipeableRow
                  taskTitle={item.title}
                  taskId={item.id}
                  isCompleted={item.isCompleted}
                >
                  <YGroup.Item>
                    <ListItem>
                      <Task currentTask={item} />
                    </ListItem>
                  </YGroup.Item>
                </CustomStyleSwipeableRow>
              );
            }}
          />
        </YGroup>
      </Collapsible>
    </YStack>
  );
};

profiling-data.19-10-2023.21-58-25.json

stichingsd-vitrion commented 7 months ago

Any update about above?

lucaswitch commented 7 months ago

Maybe you can try without observing the data, only using the fetch function.

ibb2 commented 7 months ago

Yea I fixed the issue. It was ages ago so I don't remember the specifics, but I switched to Flashlist by Shopify, put fetching data into my own custom hooks using the approach from https://github.com/bndkt/sharemystack, and generally just rewrote a bunch of my watermelondb implementation as they were done inefficiently or completely unnecessary. Again can't really remember which specifically fixed my problem.