Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In what way can each <li> in a <ul> change its classname without affecting the others?

There is going to be an unusual amount of code here because I am trying to share everything that is going on.

What I am trying to do is... In a list:

  1. Mark services as complete.
  2. Change their color and hide after completion.
  3. Show hidden services on a button press.

I managed to hide each individual service, but couldn't work with the button that hides/shows all of the completed services.

I have a context provider:

const ContextBooking = React.createContext()

const ContextProviderBooking = ({ children }) => {
  const [isHidden, setIsHidden] = useState(false); //sharing among both components to hide/show list

  return <ContextBooking.Provider value={{ isHidden, setIsHidden }}>
    {children}
  </ContextBooking.Provider>
}
export { ContextBooking, ContextProviderBooking }

Which is being passed over the BookingsDisplay component in another file

...
<ContextProviderBooking>
      <BookingsDisplay /> //this encapsulates each <Booking />
</ContextProviderBooking>
...

I am rendering each of the services in a larger component called 'BookingsDisplay'

const BookingsDisplay = () => {
  const { isHidden, setIsHidden } = useContext(ContextBooking)  
  const display = day => //function that displays each service according to the day it was booked for
    allBookings.map( //allBookings is a json file
      item =>
        item.day === day && ( 
          <Booking
            isHidden={isHidden}
            completed={item.completed} //comes from json file, all default to false
            key={item.id}
            id={item.id}
            time={item.time}
            name={item.name}
            date={item.date} 
          />
        )
    )
  return (
    <div className="bookings">
      <h2 className="ib">Next bookings</h2>
      <button //This won't work as expected and hide/show all of the 'completed' bookings
          onClick={() =>{ 
            setIsHidden(!isHidden);}
      }>
          Show hidden  
      </button>
      <h2>Today</h2>      
      <ul> {display('today')} </ul>
      <h2> Tomorrow </h2>
      <ul> {display('tomorrow')} </ul>
      <h2> General </h2>
      <ul> {display('other')} </ul>
    </div>
  )
}

Each 'Booking' component has a button that marks the service as complete. This happens by conditionally changing the class of each component. This works fine as far as I'm concerned

const Booking = (props) => {
  const [isHidden, setIsHidden] = useState(props.isHidden)
  console.log(props.isHidden) // will output true or false 16 times(there are 16 component in total)
  const [isCompleted, setIsCompleted] = useState(props.completed);
  return (
    <li
      className={
        isCompleted && isHidden ? 'booking-complete hide' //class names are not changing individually
        : isCompleted ? 'booking-complete'                //if button is pressed on one of them,
        : 'booking'                                       //it may affect the other
      }
      key={props.id}
      id={props.id}>
      <h3>{props.date}</h3>
      <h4>{props.time}</h4>
      <h5>{props.name}</h5>
      <button
        onClick={() => { //shouldn't this button work of each li and not sometimes all of them?
          if (!isCompleted && !isHidden) { 
            setIsCompleted(!isCompleted); //this changes color of the service as className changes
            setTimeout(() => setIsHidden(!isHidden), 200) //after a short time it is hidden
          }
          else if (isCompleted && !isHidden) {
            setIsCompleted(!isCompleted);
          }
          else {
            setIsCompleted(!isCompleted);
            setIsHidden(!isHidden);
          }
        }}>
        {!isCompleted ? `Completed` : `Not complete`}
      </button>
    </li>
  )
}
like image 775
uber Avatar asked Mar 29 '20 18:03

uber


People also ask

How can the style class of an element be changed?

Element Class Names Another way to alter the style of an element is by changing its class attribute. class is a reserved word in JavaScript, so in order to access the element's class, you use element. className .

How do you change the name of a class element?

To change all classes for an element:document. getElementById("MyElement"). className = "MyClass"; (You can use a space-delimited list to apply multiple classes.)

Which selector should be used in order to only select the Li inside of the UL?

The selector “ ol li ” will select any <li> that is inside an <ol> element but not other list items.

Can Li have classes?

Classes (i.e. classnames) are used for styling the li element. Multiple classnames are separated by a space. JavaScript uses classes to access elements by classname. Tip: class is a global attribute that can be applied to any HTML element.


1 Answers

There're two kind of isHidden in your app. I'mma call the one in context the global hidden isAllHidden and the one in <Booking /> the local hidden isHidden.

The problem is you misuse the two. local hidden is an internal state of <Booking />. The reason of it's existence is because you need that 200ms delay of animation, otherwise it can be replaced by isCompleted. So it should be derived from isCompleted instead of isAllHidden.

Fix 1:

const Booking = (props) => {
  const [isHidden, setIsHidden] = useState(props.completed)
}

Now global hidden and local hidden combine to decide whether a Booking should hide. You logic should reflect this fact.

Fix 2:

  const shouldBeHidden = Boolean(props.isAllHidden && isHidden)

  return (
    <li
      className={
        isCompleted && shouldBeHidden ? 'booking-complete hide' 
        : isCompleted ? 'booking-complete'on one of them,
        : 'booking'other
      }
    >
  ...

Put together:

const Booking = (props) => {
  const [isHidden, setIsHidden] = useState(props.completed)
  const [isCompleted, setIsCompleted] = useState(props.completed)

  const shouldBeHidden = props.isAllHidden && isHidden

  return (
    <li
      className={
        isCompleted && shouldBeHidden ? 'booking-complete hide' //class names are not changing individually
        : isCompleted ? 'booking-complete'                //if button is pressed on one of them,
        : 'booking'                                       //it may affect the other
      }
    >
      <input type='checkbox' checked={isCompleted} onChange={() => {
        setIsCompleted(!isCompleted)
        setTimeout(() => setIsHidden(!isHidden), 200)
      }}/>
      <span>{props.name}</span>
    </li>
  )
}

I setup a demoboard here to show the result.

like image 53
hackape Avatar answered Oct 11 '22 03:10

hackape