Home > database >  Recursive kanban board made with react redux hooks. How can you fix the component, so that it does n
Recursive kanban board made with react redux hooks. How can you fix the component, so that it does n

Time:03-14

I am working on a project with React and Redux hooks. It has recursively nested kanban boards. When you add a new card to a kanban column that is scrolled all of the way to the right, the component re-renders and jerks the kanban board back to x=0, y-0 scroll. It is not the window that is scrolling. It is the kanban's parent element.

Does anyone know how you need to re-structure the List component (or any other part of the code), so that you can add a card to the columns (that are in a scrolled container), and not ruin the scroll position?

Thank you for any pointers!

When you try to add a new card to the "complete" column (in the example linked above), you can quickly see what the issue is: The whole kanban board jerks back to scroll x=0, y=0.

I think the board list is being re-rendered, so it is re-setting the scroll position.

I have tried using a ref to get the scroll position of the scrolled div right before the dispatch to add new card, and then set that position again as soon as the dispatch has added the card; but this did not work.

The data on the redux state is:

listSlice.js

list.nodes:

{
    "0": {
        "text": "board-one",
        "id": 0,
        "view": "board",
        "childIds": [
            100,
            101,
            102,
            103,
            104
        ],
        "parentId": null,
        "order": 0,
        "isExpanded": true
    },
    "1": {
        "text": "board-two",
        "id": 1,
        "view": "board",
        "childIds": [
            200,
            201,
            202,
            203,
            204
        ],
        "parentId": null,
        "order": 1,
        "isExpanded": true
    },
    "2": {
        "text": "board-three",
        "id": 2,
        "view": "board",
        "childIds": [
            300,
            301,
            302,
            303,
            304
        ],
        "parentId": null,
        "order": 2,
        "isExpanded": true
    },
    "3": {
        "text": "board-four",
        "id": 3,
        "view": "board",
        "childIds": [
            400,
            401,
            402,
            403,
            404
        ],
        "parentId": null,
        "order": 3,
        "isExpanded": true
    },
    "100": {
        "text": "backlog",
        "id": 100,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "101": {
        "text": "to do",
        "id": 101,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "102": {
        "text": "in progress",
        "id": 102,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "103": {
        "text": "blocked",
        "id": 103,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "104": {
        "text": "complete",
        "id": 104,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "200": {
        "text": "backlog",
        "id": 200,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "201": {
        "text": "to do",
        "id": 201,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "202": {
        "text": "in progress",
        "id": 202,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "203": {
        "text": "blocked",
        "id": 203,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "204": {
        "text": "complete",
        "id": 204,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "300": {
        "text": "backlog",
        "id": 300,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "301": {
        "text": "to do",
        "id": 301,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "302": {
        "text": "in progress",
        "id": 302,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "303": {
        "text": "blocked",
        "id": 303,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "304": {
        "text": "complete",
        "id": 304,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "400": {
        "text": "backlog",
        "id": 400,
        "childIds": [
            1000,
            1001
        ],
        "view": "column",
        "order": 0,
        "isExpanded": true
    },
    "401": {
        "text": "to do",
        "id": 401,
        "childIds": [
            2000,
            2001
        ],
        "view": "column",
        "order": 1,
        "isExpanded": true
    },
    "402": {
        "text": "in progress",
        "id": 402,
        "childIds": [
            3000,
            3001
        ],
        "view": "column",
        "order": 2,
        "isExpanded": true
    },
    "403": {
        "text": "blocked",
        "id": 403,
        "childIds": [
            4000,
            4001
        ],
        "view": "column",
        "order": 3,
        "isExpanded": true
    },
    "404": {
        "text": "complete",
        "id": 404,
        "childIds": [
            5000,
            5001
        ],
        "view": "column",
        "order": 4,
        "isExpanded": true
    },
    "1000": {
        "text": "card-one",
        "id": 1000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "1001": {
        "text": "card-two",
        "id": 1001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "2000": {
        "text": "card-one",
        "id": 2000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "2001": {
        "text": "card-two",
        "id": 2001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "3000": {
        "text": "card-one",
        "id": 3000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "3001": {
        "text": "card-two",
        "id": 3001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "4000": {
        "text": "card-one",
        "id": 4000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "4001": {
        "text": "card-two",
        "id": 4001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    },
    "5000": {
        "text": "card-one",
        "id": 5000,
        "childIds": [],
        "order": 0,
        "isExpanded": true,
        "view": "card"
    },
    "5001": {
        "text": "card-two",
        "id": 5001,
        "childIds": [],
        "order": 1,
        "isExpanded": true,
        "view": "card"
    }
}

listSlice.js

list.visibleIds:

[
    "0",
    "1",
    "2",
    "3"
]

The component: (to keep the example simple, there is only an add button on the column. The error occurs when the board is scrolled, to see a column at the end; and you try to add a card to one of those columns at the end). List.js

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { v4 as uuid } from "uuid";
import {
  fetchNodesAsync,
  selectVisibleIds,
  selectParentId,
  addNewNodeAsync,
  selectNodes
} from "./listSlice";
import "./List.css";

export function List(props = {}) {
  let { isRoot } = props;
  const nodes = useSelector(selectNodes);
  const visibleIds = useSelector(selectVisibleIds);
  const parentId = useSelector(selectParentId);
  const dispatch = useDispatch();

  useEffect(() => {
    if (isRoot) {
      dispatch(fetchNodesAsync());
    }
  }, []);

  const renderList = (params) => {
    let { ids, parentView } = params;

    return (
  // This is the element that is scrolled when the error occurs.  It is scrolled to the left, so that you can see the last columns in the list:
      <ul className={`${parentView || "root"}-container`}>
        {ids.map((id) => {
          let node = nodes[id];
          return (
            <li key={uuid()} className={node.view}>
              <div className="header">
                {node.view === "column" && (
                  <button
                    onClick={(e) => {
                      e.preventDefault();
                      dispatch(addNewNodeAsync({ parentId: id }));
                    }}
                  >
                      add item
                  </button>
                )}
                id: {node.id} {node.text}
              </div>
              <div className={`${node.view}-scroll`}>
                {node.isExpanded &&
                  node.childIds.length >= 1 &&
                  renderList({
                    ids: node.childIds,
                    parentId: id,
                    parentView: node.view
                  })}
              </div>
            </li>
          );
        })}
      </ul>
    );
  };

  return <ul className="List">{renderList({ ids: visibleIds, parentId })}</ul>;
}

I'm trying to implement the data-structure that Dan Abramov describes here: https://github.com/reduxjs/redux/issues/1629

CodePudding user response:

You cannot do this: <li key={uuid()} ... /> because uuid will generate a new random ID at every render, which will make React generate new DOM nodes at every state change and therefore will lose the scroll amount stored inside the previous nodes. You should use <li key={id} ... /> instead, so that the IDs stay the same.

  • Related