how to show selected item in react js?

Viewed 935

I have a list of items (Example names). I am trying to show the selected item in the list. .Selected items have a selected class which will add a border to that element.

.selected {
  border: 1px solid blue;
}

selected Item decide with a variable const selectedItem = 1; if it 1 it will select second item .If const selectedItem = 0; it will select first element.

Problem & statement

  1. I have wrapper div className="container" which has width 200px. It will contain as much as list element it can. It depends on Item width.

Example list Item (see example 1 it only shows three Items as all text are small.)

const data = [
  { title: "xahsdha" },
  { title: "hello" },
  { title: "hessllo" },
  { title: "hesslssslo" },
  { title: "navveiii" }
];

enter image description here

Example 2 with new data: See Item first text is big. It shows only one item.

const data = [
  { title: "xahsdhaadasdasdasdass" },
  { title: "hello" },
  { title: "hessllo" },
  { title: "hesslssslo" },
  { title: "navveiii" }
];

enter image description here

Issue:

I am not able to select that item that goes outside the container.

Example given data

const data = [
  { title: "xahsdh" },
  { title: "hello" },
  { title: "hessllo" },
  { title: "hesslssslo" },
  { title: "navveiii" }
];

if I give const selectedItem = 1;

my output and expected out are the same. see I am able to show selected Item enter image description here

but if I give const selectedItem = 3; with same data set my output is this

enter image description here

But the expected output is this enter image description here

can we move forward the hidden element if it is selected ?

Scenario 2

  1. we need to take care of hidden element width also .so selected item width depends on how many items I will show in the container.

example if we pass const selectedItem = 3; with this data set

const data = [
  { title: "xahsdh" },
  { title: "hello" },
  { title: "hessllo" },
  { title: "hesslsssloasdasdasdasdasdsa" },
  { title: "navveiii" }
];

The expected output is this enter image description here

is this functionality feasible?

here is my code https://codesandbox.io/s/unruffled-hellman-hswdl?file=/src/App.js

import "./styles.css";
import React, { createRef, useMemo, useRef } from "react";
const data = [
  { title: "xahsdh" },
  { title: "hello" },
  { title: "hessllo" },
  { title: "hesslssslo" },
  { title: "navveiii" }
];
const selectedItem = 1;
export default function App() {
  const arrLength = data.length;
  const [defaultState, setDefaultState] = React.useState(true);

  const elRefs = useMemo(
    () => Array.from({ length: data.length }).map(() => createRef()),
    []
  );
  const [state, setState] = React.useState({
    total: 0,
    tabWidth: 0,
    dimension: {}
  });
  const ulRef = useRef(null);

  React.useEffect(() => {
    setDimention();
  }, []);

  const setDimention = () => {
    let total = ulRef.current.offsetWidth,
      dimension = {},
      tabWidth = 0;
    console.log(total);
    elRefs.forEach((item, i) => {
      dimension[i] = item.current?.offsetWidth || 0;
      tabWidth += item.current?.offsetWidth || 0;
    });
    console.log({
      total,
      dimension,
      tabWidth
    });
    setDefaultState(false);
    setState({
      total,
      dimension,
      tabWidth
    });
  };

  const getTabs = () => {
    const { tabWidth, dimension, total } = state;
    var visible = [],
      avaiable = total,
      hidden = [];
    if (defaultState) {
      return {
        visible,
        hidden
      };
    }
    data.forEach((element, index) => {
      if (avaiable - dimension[index] > 0) {
        visible.push(element);
        avaiable = avaiable - dimension[index];
      } else {
        hidden.push(element);
      }
    });
    return {
      visible,
      hidden
    };
  };
  const { visible = [], hidden = [] } = getTabs();
  return (
    <div className="container" ref={ulRef}>
      <ul id="dummy">
        {visible.length === 0 &&
          hidden.length === 0 &&
          data.map((el, i) => (
            <li
              className={i === selectedItem ? "selected" : ""}
              ref={elRefs[i]}
              key={i}
            >
              {el.title}
            </li>
          ))}
      </ul>
      <ul id="visible">
        {visible.map((el, i) => (
          <li className={i === selectedItem ? "selected" : ""} key={i}>
            {el.title}
          </li>
        ))}
      </ul>
      <ul id="hidden">
        {hidden.map((el, i) => (
          <li className="hide" key={i}>
            {el.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

NOTE & update:I don't want scrollable list . I want first priority given to "selected" item and if space available add other element in front .

Any suggestion ?

In jQuery it is possible .so sure whether we achieve this in react or not.I checked this jQuery example selected item is in last but still it come in view

https://codepen.io/naveennsit/pen/poPJOPM?editors=1010

1 Answers

I think the main issue here is figuring out which elements should be displayed when not rendering from the beginning.

I suggest computing the arrays of visible and hidden elements starting from the currently selected item, and a visible offset. First go in reverse and compute the visible/hidden elements moving towards the front of the array, then do a second loop starting from the element after the selected item moving towards the end of the array.

const getTabs = () => {
  const { dimension, total } = state;

  const visible = [];
  const hidden = [];

  let offset = 0;
  let available = total;

  if (!initialRender) {
    // check forward
    for (let i = selectedItem; i >= 0; i--) {
      if (available > dimension[i]) {
        visible.unshift(data[i]);
        available -= dimension[i];
      } else {
        hidden.unshift(data[i]);
        offset++;
      }
    }

    // check aft
    for (let i = selectedItem + 1; i < arrLength; i++) {
      if (available > dimension[i]) {
        visible.push(data[i]);
        available -= dimension[i];
      } else {
        hidden.push(data[i]);
      }
    }
  }

  return {
    hidden,
    offset,
    visible
  };
};

When rendering the visible and hidden arrays, just compare the current mapped index + offset to the currently selected item index.

<ul id="visible">
  {visible.map((el, i) => (
    <li
      className={i + offset === selectedItem ? "selected" : ""}
      key={i}
    >
      {el.title}
    </li>
  ))}
</ul>
<ul id="hidden">
  {hidden.map((el, i) => (
    <li className="hide" key={i}>
      {el.title}
    </li>
  ))}
</ul>

Edit how-to-show-selected-item-in-react-js

EDIT

Since the idea is to have in the visible area, the currently selected item, and then only those elements from the array, from the beginning of the array, that still fit, also visible, we can modify the above solution to instead make the assumption the currently selected item will always fit.

First subtract the selected items size from the availability, then iterate from the beginning of the array. If the current index doesn't match the current selected index, push the current element into the visible array and subtract from the availability if it fits. If currently looking at the selected item, push it into the visible array.

For this algorithm and rendering I added a GUID to each item.

const data = [
  { title: "xahsdh", id: uuidV4() },
  { title: "hello", id: uuidV4() },
  { title: "hessllo", id: uuidV4() },
  { title: "hesslssslo", id: uuidV4() },
  { title: "navveiii", id: uuidV4() },
];

...

const getTabs = () => {
  const { dimension, total } = state;

  const visible = [];
  const hidden = [];

  const selectedIndex = data.findIndex((el) => el.id === selectedItem);
  if (!initialRender && selectedIndex !== -1) {
    let available = total - dimension[selectedIndex];

    data.forEach((el, i) => {
      if (i === selectedIndex) {
        visible.push(el);
        return;
      }
      if (available > dimension[i]) {
        visible.push(el);
        available -= dimension[i];
      } else {
        hidden.push(el);
      }
    });
  }

  return {
    hidden,
    visible
  };
};

When rendering check the GUID.

<ul id="visible">
  {visible.map((el, i) => (
    <li
      className={el.id === selectedItem ? "selected" : ""}
      key={el.id}
    >
      {el.title}
    </li>
  ))}
</ul>
<ul id="hidden">
  {hidden.map((el, i) => (
    <li className="hide" key={el.id}>
      {el.title}
    </li>
  ))}
</ul>

Edit how-to-show-selected-item-in-react-js (forked)

Related