how to render visible array in react js?

Viewed 133

I am trying to implement TABS in my demo application. condition : I want to render those TABS or menu item which have length less than 100px

I created tab with following steps

export const TabsContext = React.createContext({
              selectedTabId: "",
              setSelectedTabId: () => {},
              tabsVisible: [],
              tabsHidden: [],
              setTabsVisible: () => {},
              setTabsHidden: () => {}
            });
  • Created a context which have selectedIndex, visibleArray, hiddenArray is present
  • In TABLIST component I checked all element width .if it is greater than 100 I pushed to visible else hidden.

but how to render visible array/ ITEM

const TabList = ({
  isAlign,
  isSize,
  isBoxed,
  isToggle,
  isToggleRounded,
  isFullwidth,
  children,
  ...rest
}) => {
  const tabsRef = useRef(null);
  const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
    TabsContext
  );

  const getWidth = () => {
    let hidden = [],
      visible = [];
    [...tabsRef.current.children].forEach((element) => {
      console.log(element.offsetWidth);
      if (element.offsetWidth > 100) {
        hidden.push(element);
      } else {
        visible.push(element);
      }
    });
  };

  useEffect(() => {
    getWidth();
  }, []);
  return (
    <div>
      <ul className="tabs" ref={tabsRef}>
        {children}
      </ul>
    </div>
  );
};

TabList.displayName = "Tabs.TabList";

export { TabList };


  

here is my whole code https://codesandbox.io/s/intelligent-wescoff-dd3xd?file=/src/tablist.js:141-924

Getting Error while rendering

Objects are not valid as a React child (found: [object HTMLLIElement]).

I set the default value of visible array

<Tabs
      defaultTabId="1"
      defaultTabsVisible={data.map((i) => (
        <Tabs.Tab tabId={i} key={i}>
          {i}
        </Tabs.Tab>
      ))}
    >
      <Tabs.TabList isSize="medium"></Tabs.TabList>
    </Tabs>

and try to updated array

const getWidth = () => {
    let hidden = [],
      visible = [];
    [...tabsRef.current.children].forEach((element) => {
      console.log(element.offsetWidth);
      if (element.offsetWidth > 100) {
        hidden.push(element);
      } else {
        visible.push(element);
      }
    });
    setTabsVisible(visible);
    setTabsHidden(hidden);
  };

while update I am getting this error Objects are not valid as a React child (found: [object HTMLLIElement]). I

3 Answers

You have to use the property opacity of CSS to hide your tabs, after this step you have to create a state tabsToDisplay to store the tabs that verify your condition.

styles.css

.tabs-hidden {
  opacity: 0; // <=== hide your tabs
  display: flex;
  list-style: none;
}

tabList.js

import React, { useRef, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { TabsContext } from "./context";

const TabList = ({
  isAlign,
  isSize,
  isBoxed,
  isToggle,
  isToggleRounded,
  isFullwidth,
  children,
  ...rest
}) => {
  const tabsRef = useRef(null);
  const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
    TabsContext
  );
  const [tabsToDisplay, setTabsToDisplay] = useState([]); // <=== create this state to store the tabs that match your condition

  const getWidth = () => {
    let hidden = [],
      visible = [],
      tabsToDisplay = [];

    [...tabsRef.current.children].forEach((element, i) => {
      if (element.offsetWidth > 100) {
        hidden.push(element);
      } else {
        visible.push(element);
        tabsToDisplay.push(children[i]);
      }
    });

    console.log("test", tabsToDisplay);
    setTabsToDisplay(tabsToDisplay);
  };

  useEffect(() => {
    getWidth();
  }, []);
  return (
    <div>
      <ul className="tabs-hidden" ref={tabsRef}>
        {children}
      </ul>
      <ul className="tabs">{tabsToDisplay}</ul>
    </div>
  );
};

TabList.displayName = "Tabs.TabList";

export { TabList };

You can check the demo: https://codesandbox.io/s/hungry-galileo-qk09i?file=/src/tablist.js:0-1156

Ok so this is what I came up with

import React, { useRef, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { TabsContext } from "./context";

const TabList = ({
  isAlign,
  isSize,
  isBoxed,
  isToggle,
  isToggleRounded,
  isFullwidth,
  children,
  ...rest
}) => {
  const tabsRef = useRef(null);
  const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
    TabsContext
  );

  const [firstRender, setFirstRender] = useState(true);

  const getWidth = () => {
    let hidden = [],
      visible = [];
    [...tabsRef.current.children].forEach((element, i) => {
      console.log(element.offsetWidth);
      if (element.offsetWidth > 100) {
        hidden.push(children[i]);
      } else {
        visible.push(children[i]);
      }
    });

    setTabsVisible(visible);
    setTabsHidden(hidden);
    if(tabsRef.current)
       setFirstRender(false)
  };

  useEffect(() => {
    getWidth();
  }, []);
  return (
    <div>
      <ul className="tabs" ref={tabsRef}>
        {firstRender ? children : tabsVisible }
      </ul>
    </div>
  );
};

TabList.displayName = "Tabs.TabList";

export { TabList };

This will check all children size on initial render and update the visible array. Then after the first render it will only display the visible children.


Update - aria-hidden

{firstRender ? 
  (<ul className="tabs" ref={tabsRef} aria-hidden={false}>
     {children}
  </ul>)
 : 
  (<>
     <ul className="tabs" ref={tabsRef} aria-hidden={false}>
       {tabsVisible}
     </ul>
     <ul className="tabs-hidden" ref={tabsRef} aria-hidden={true}>
       {tabsHidden}
     </ul>
   </>)
}

If you want aria-hidden property to be individual for each tab, you would have to check for element width inside the tab component also. Then add the aria-hidden hidden property in there.

As you're using ref, you can do it using them. This is an alternative to current answers, using useLayoutEffect

const TabList = ({
      isAlign,
      isSize,
      isBoxed,
      isToggle,
      isToggleRounded,
      isFullwidth,
      children,
      ...rest
    }) => {
      const tabsRef = useRef(null);
      const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
        TabsContext
      );
    
      React.useLayoutEffect(() => {
        let hidden = [],
          visible = [];
        [...tabsRef.current.children].forEach((element) => {
          console.log(element.offsetWidth);
          if (element.offsetWidth > 100) {
            element.style.display = "none";
            element.setAttribute("aria-hidden", "true");
            hidden.push(element);        
          } else if (element.style.display !== 'none') {
            element.style.display = "block";
            element.setAttribute("aria-hidden", "false");
            visible.push(element);
          }      
        });
        setTabsVisible(visible)
        setTabsHidden(hidden)
      }, [setTabsVisible, setTabsHidden, tabsRef])
    
      return (
        <div>
          <ul className="tabs" ref={tabsRef}>
            {children}
          </ul>
        </div>
      );
    };

Changed tabs to prevent infinite rerendering

return React.createElement(
    TabsContext.Provider,
    {
      value: {
        selectedTabId,
        setSelectedTabId,
        tabsVisible,
        tabsHidden,
        setTabsVisible,
        setTabsHidden
      }
    },
    children
  );

Also note the

} else if (element.style.display !== "none") {

This prevents the element that is displayed with none, to be displayed as having width < 100 on a further rerender

https://codesandbox.io/s/pedantic-kirch-ykdc3?file=/src/tablist.js

Related