React JS Custom Sidebar Navigation

Viewed 414

I have Created a toggled navigation in React APP. Everything working fine except the toggle. Below is the code of toggle. I am using the check of parentId if parentId exist then include the children id's and it will open the toggle. If not exist then add the main Id which one is parentId in children.

Problem is I parentId already exist and if I click the toggle it is not closing the old one and displaying the new one along with old.

const handleArrowClick = ( data: CoursesNav ) => {
    const { id, parentId } = data;
    let newtoggledMenus = [...toggledMenus];
    if( parentId && newtoggledMenus.includes(parentId)){
        if (newtoggledMenus.includes(id)) {
            var index = newtoggledMenus.indexOf(id);
            if (index > -1) {
                newtoggledMenus.splice(index, 1);
            }
        } else {
            newtoggledMenus.push(id);
        }
        settoggledMenus(newtoggledMenus);
    }else{
        settoggledMenus([id]);
    }
};
    

I have created the nav structure given below in which under the main item children are added.

    export const CourseMenu = ( book: Book ): CoursesNav[] => {
        if( course ){
           // Chapters from book
            return book.chapters.map( c => {
                return {
                    id: c.id,
                    name: c.name,
                    link: `/my-book/${course.id}/chapter/${c.id}/edit`,
                    parentId: c.id,
                    // get topics and put under children
                    children: c.topics.map( m => {
                        return {
                            id: m.id,
                            name: m.name,
                            link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
                            parentId: c.id,
                            //get blocks and put under blocks
                            children: m.blocks.map( b => {
                                return {
                                    id: b.id,
                                    name: b.title,
                                    link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
                                    parentId: m.id
                                }
                            })
                        }
                    })
                }
            });
        }
    }

Nav logic is added just to show you that what I am trying to create. Every parent has button for toggle to show the sub items. If any sub item has children again the toggle will displayed.

Only I have the problem in opening and closing the correct toggle. Right now under a main children if I open its all child previous one are not closing because they are getting the parentId in toggledMenu state which is a numeric array.

2 Answers

yow broh, why don't you use aria. don't search for the parent of no body.

The user clicks your button/link whatever the user clicks, but you need to set an aria attribute call aria-controls in which you need to put the value of which eva you want to hide/show.

start by closing any open menu or so, then wait 150 milliseconds or so. just to prevent that your loop closes the one that needs to be opened

function App () {
/**
 * togging menus
 */
 function onClickHandler (e) {
  e.preventDefault();
  
  const btnPressed = e.target;
  
  // first you need to check if there is a item toggled and stuff.
  const openMenus = document.querySelectorAll("[aria-expanded]");
  
  if (openMenus.length) {
    Object.keys(openMenus).forEach(button => {
        const toggler = openMenus[button];
        
        const menuId = toggler.getAttribute("aria-controls");
        const menuToClose = document.getElementById(menuId);
        if (menuToClose && menuToClose !== null) {
          menuToClose.classList.remove("show");
          menuToClose.setAttribute("aria-hidden", "true");
          menuToClose.classList.add("hidden");
          
          // now the button
          toggler.setAttribute("aria-expanded", "false");
        }
    })
  }
  
          //wait a minute to prevent conflicts
        
        setTimeout(()=>{
            const toOpen = document.getElementById(btnPressed.getAttribute("aria-controls"));
            
            if (!toOpen) {return;}
            
            btnPressed.setAttribute("aria-expanded", "true");
            toOpen.classList.add("show");
          toOpen.setAttribute("aria-hidden", "false");
          toOpen.classList.remove("hidden");
          
        },[150])
}
  
  
  return (
  <div className="accordion-example">
    <ul aria-label="Accordion Control Group Buttons" className="accordion-controls">
      <li>
        <button onClick={onClickHandler} aria-controls="content-1" aria-expanded="false" id="accordion-control-1">Apples</button>
        <div className="menu hidden" aria-hidden="true" id="content-1">
          <p>Apples are a fine fruit often associated with good health, and fewer doctor's appointments.</p>
          <p>Example. An apple a day keeps the doctor away.</p>
        </div>
      </li>
  
      <li>
        <button onClick={onClickHandler} aria-controls="content-2" aria-expanded="false" id="accordion-control-2">Lemons</button>
        <div className="menu hidden" aria-hidden="true" id="content-2">
          <p>Lemons are good with almost anything, yet are often have a negative connotation when used in conversation.</p>
          <p>Example. The bread from the french bakery is normally very good, but the one we bought today was a lemon.</p>
        </div>
      </li>
  
      <li>
        <button onClick={onClickHandler} aria-controls="content-3" aria-expanded="false" id="accordion-control-3">Kiwis</button>
        <div className="menu hidden" aria-hidden="true" id="content-3">
          <p>Kiwis are a fun, under-appreciated fruit.</p>
        </div>
      </li>
    </ul>
  </div>)
  }



ReactDOM.render( < App / > , document.getElementById("page"));
.hidden {
  display: none;
}

.show {
  display: blocK
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id="page"></div>

I am guessing you have a 3 level hierarchy inside a Book -> chapter, topic & block. On click of (let's say) right button, you want that specific item's children to be toggled. This is evidently at chapter and topic level.

One solution is maintain the entire hierarchy which you shared (book.chapters) as a state, and have a showChildren flag at each level.

return book.chapters.map( c => {
                return {
                    id: c.id,
                    name: c.name,
                    link: `/my-book/${course.id}/chapter/${c.id}/edit`,
                    parentId: c.id,
                    showChildren: false,
                    // get topics and put under children
                    children: c.topics.map( m => {
                        return {
                            id: m.id,
                            name: m.name,

                            link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
                            parentId: c.id,
                            showChildren: false,
                            //get blocks and put under blocks
                            children: m.blocks.map( b => {
                                return {
                                    id: b.id,
                                    name: b.title,
                                    link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
                                    parentId: m.id
                                }
                            })
                        }
                    })
                }
            });
        }

Based on the click on the right arrow, toggle that particular state.

With the above approach, you can keep multiple levels in the nav hierarchy open to toggle.

Related