5

My initial goal to solve:

  • I have 4 components lined up next to each other. I want to be able to toggle/onClick one of the components and have the other 3 components disappear. Then re-toggling that same component to have the other components reappear. But also have the ability to do the same task with toggling any of the other components.
  • Each of components in simple terms is a div tag with an image tag within.

My initial take on the problem:

export const Test = () => {
    const intialValue = [{id: 0, component: null}]
    const array = [
                    {id: 1, component: <CompOne/>}, 
                    {id: 2, component: <CompTwo/>}, 
                    {id: 3, component: <CompThree/>}, 
                    {id: 4, component: <CompFour/>}
                  ]
    
    const [chosenNumber, setChosen] = useState(0)
    const [statearray, setArray] = useState(array)

    useEffect(() => {
        console.log(chosenNumber)
        const arr = array
        if(chosenNumber === 0 ) setArray(array)
        else setArray(arr.filter( num => num === chosenNumber))
    }, [chosenNumber])

    const handleClick = (number) => {
        if(number === chosenNumber) setChosen(0)
        else setChosen(number)
    }
        return (
            <div className="flex" >
                {statearray.map((comp, number) => <div key={number} className="h-12 w-12 bg-gray-400 m-1 flex items-center justify-center" onClick={() => handleClick(comp.id)}>{comp.id}</div>)}
            </div>
        );

}

My thought process is when I click the component (div), I'll set the chosenNumber, if the div I clicked is the same as chosen, reset chosen to zero.

Every time the chosenNumber changes, useEffect should detect it and filter the array with chosenNumber, if chose is zero, reset stateArray.

Right now when I click one of the components, they all disappear. I am just not sure if using zero is the right thing to use to compare each object or what I need to use instead.

Thanks for the help!

3 Answers3

1

If I were you, I would simplify things. First, I would extract array from the component as you don't want that to be re-rendered every time a component is re-rendered. Then, I would change your state and leave only the items state that will contain array items. I would also extend the array items by providing a flag isVisible. Then, I would remove your useEffect and improve the handleClick as you want to trigger this only when a button is clicked. In the handleClick function, I would create a new set of items by mapping through your items and changing "not clicked" items isVisible to false. This way, you know which items to hide. Lastly, I would render the components based on the isVisible attribute. So if isVisible is true, the item will be rendered with hidden set to false and vice versa.

This way the code is much simpler, more performant and easier to understand. Plus, it does what you asked.

Here's the link of the example working code: codesandbox

import React, { useState } from "react";

const Comp1 = () => <div>hi</div>;
const Comp2 = () => <div>hi2</div>;
const Comp3 = () => <div>hi3</div>;

const array = [
  { id: 1, component: <Comp1 />, isVisible: true },
  { id: 2, component: <Comp2 />, isVisible: true },
  { id: 3, component: <Comp3 />, isVisible: true }
];

export const Test = () => {
  const [items, setItems] = useState(array);

  const handleClick = (number) => {
    const triggeredItems = items.map((item) => {
      if (item.id !== number) {
        item.isVisible = !item.isVisible;
      }

      return item;
    });

    setItems(triggeredItems);
  };

  return (
    <div className="flex">
      {items.map(({ id, component, isVisible }) => (
        <div
          key={id}
          className="h-12 w-12 bg-gray-400 m-1 flex items-center justify-center"
          onClick={() => handleClick(id)}
          hidden={!isVisible}
        >
          {component}
        </div>
      ))}
    </div>
  );
};

export default Test;
Mantas Astra
  • 952
  • 6
  • 14
  • Thank you for the example and advice. I really appreciate it. At the moment, this prints out the id of each component, but ideally want the div tag corresponding to the id to be what is getting printed. I understand the map function takes the id as the index to index through the array but I would like the divs to be what is being toggled visible or not visible. Any thoughts? – Patrik Kozak Dec 10 '20 at 17:45
  • 1
    You can still apply the same technique as long as you wrap the components in a `div` or something else so that you can toggle it on/off. I've edited my initial answer with the example that takes in components and renders them. @PatrikKozak – Mantas Astra Dec 10 '20 at 17:50
  • 1
    I just needed to add 'component' to the map function. Thank you again for the help! – Patrik Kozak Dec 10 '20 at 17:51
  • I want to add a new object/element in correspondence to each specific object in the array, for example a 'description' of said component and I only want their description to show when clicking on one of the components. If I set up an array within each object of this initial array that holds the 'description' info I am able to display the info when each object is present, in other worlds 'isVisible = true' always. But it needs to start as false, then on click of component, change to true, and on click of component again, would change it back to false. I can't seem to figure out that part out. – Patrik Kozak Dec 10 '20 at 22:06
0

One simple way is to have a boolean saved in state (such as isVisible = true ) in either useState or mobx etc. Then in the react component you can use a double ampersand, like below:

{isVisible && <MyComponent/>}

This will then only show if isVisible = true

JDtheGeek
  • 71
  • 2
  • Thanks for the response! This was my initial take on the problem as well but I had difficulties on how to update the state of each component in the sense that if component 1 is toggled, make components 2-4 not visible (false) and then repeating this logic for the other components. Hope this makes sense. – Patrik Kozak Dec 10 '20 at 17:15
0
const [currentComponent, setCurrentComponent] = useState(1);

const handleClick = () =>{
    switch(currentComponent){
        case 1:
            setCurrentComponent(2)
        case 2:
            setCurrentComponent(3)
        case 3:
            setCurrentComponent(4)
        case 4:
            setCurrentComponent(1)

    }
}


<button onClick={()=> handleClick()}>Click me!</button>


{currentComponent == 1 && <Comp1/>};
{currentComponent == 2 && <Comp2/>};
{currentComponent == 3 && <Comp3/>};
{currentComponent == 4 && <Comp4/>};

Josh Merrian
  • 291
  • 3
  • 11
  • I appreciate the example! I re-updated my question to include my initial take on the problem. I tried using your solution but having some troubles implementing it. The button does not do anything atm and only the first component is visible. – Patrik Kozak Dec 10 '20 at 17:07
  • Sorry! Forgot to add break; at the end of every case in the switch statement. Try that – Josh Merrian Dec 10 '20 at 20:31