DEV Community

loading...
Cover image for Keeping track of on/off states of React components

Keeping track of on/off states of React components

dance2die profile image Sung M. Kim Originally published at slightedgecoder.com on ・4 min read

Adokiye Iruene asked a question on StackOverflow regarding applying a style on a clicked component not all the sibling components.

Change style for only clicked on view not all views

The problem is that the Asokiye generated components with a list of records in a state.

When a user clicked on a component, not only clicked component had a style applied, but also sibling components.

How can we apply a style only on a clicked component from a list of components?

If you want to know the answer you can cheat 😈 by going directly to the answer.

🗞 Preface

I will use a simple webpage that displays a list of texts wrapped in a component, Child.

Sample Code Output

How it works

When you click on an item, the page will highlight only the clicked line. by applying the following class, highlight.

Let’s see how to apply that style per component on click.

Edit so.answer.51516825
You can follow along on CodeSandBox

👩‍💻 Relevant Codes

Child component returns texts and applies a style depending whether it’s clicked or not (using isClicked prop).

App.js renders Child components.

📊 Analysis

What needs to happen is that we need to keep a track of all on/off states of each component so that we can turn the state of each component on/off.

So let’s track on/off states.

I’ve declared it as an object, instead of as an array, I will get to it later.
(I promise 🤞)

Let’s look at what happens when a user clicks on a Child component

OK, it’s kind of hard to digest it so let’s go through it line by line.

On line#4, I am getting all previously clicked states.
const clicked = { ...prevState.clicked };
using an object spread syntax.

, toggle previous state.
clicked[i] = !clicked[i];

Lastly, set the clicked state to the updated one.
return { clicked }; // same as return { clicked: clicked }; Note that if the property name is same as the object key, you can shorten it.

Now the question is, there is no property in the first place to set to in clicked[i] = !clicked[i]???

⚒ A bit of Hack

OK, I’ve used a bit of JavaScript weirdness to set the clicked state of currently selected item.

I won’t go into too much details as JavaScript’s truthiness gets very hairy 😠💢).

So I refer you to this article, Mastering JavaScript’s && and || logical operators by Nicolas Marcora if you want more details.

But what you need to know is that !undefined returns true.

So what happens in clicked[i] = !clicked[i] is that, clicked is an empty object initially {}.

And !clicked[i] will be undefined and negating it with ! operator will turn it into true.

Table Flip

Negating undefined

clicked object will have an item with value of 1 as the key and the on/off state as the value.

Now let’s get back to the previous question, why use an object instead of an array to keep a track of clicked states?

🤔 Why use an object?

This is to save previous memory as setting an empty array by index greater than 0 results in filling rest of space with undefined.

Suppose that we declared state = { clicked: []}, then setting a value above first item would populate the array with undefined as shown below.

Wasted memory

I’ve set a value for the 4th item, clicked[3] = !clicked[3]; and the array ended up adding the !clicked[3] value with undefined (<3 empty slots>) for first 3 items.

Array vs. Object

You can 👀 see ☝ that the object version stores states of clicked items only.

🚀 Full Source Code

As mentioned above, you can see the working demo on CodeSandBox.

Here is the full source code (for completeness).

👋 Partying Words

The gist is that, keep a track of each component state in an object and toggle it.


The post Keeping track of on/off states of React components appeared first on Sung's Technical Blog.

Discussion (3)

pic
Editor guide
Collapse
kepta profile image
Kushan Joshi • Edited

Great article Kim, I just wanted to add another approach which works better in separating the concerns of managing the state and rendering the style.

Higher order components are great tool to solve this functionality. The function AddClick simply manages the state of one component hence avoiding the trouble of maintaining the state of all the children component in one single component. And the best part is, you can wrap any component with this HOC AddClick and it will get the isClicked prop, hence improving the usability of code.

Here is my alteration to your original code to make HOC work.

const Child = ({ id, isClicked }) => (
  <div
    className={isClicked ? `highlight` : ``}
  >{`ID ${id} --- I am a child element`}</div>
);

function AddClick(Comp) {
  return class HocClick extends Component {
    state = {
      clicked: null
    }
    onClick = i => {
      this.setState({
        clicked: !this.state.clicked
      });
    }
    render() {
      return <div onClick={this.onClick}>
        <Comp {...this.props} isClicked={this.state.clicked} />
      </div>;
    }
  };
}

const ClickableChild = AddClick(Child);

class App extends Component {
  render() {
    const items = [1, 2, 3, 4, 5].map((id, i) => {
      return (
        <ClickableChild id={id} key={id} />
      );
    });
    return (
      <div>{items}</div>
    );
  }
}

Here is a working example of the app stackblitz.com/edit/react-o8qsku

Collapse
dance2die profile image
Sung M. Kim Author • Edited

Thank Kushan 👍,

That's a great way to abstract the click state functionality.

I love how each Child component gets to track its own clicked state (after wrapping it with AddClick HoC).

I've also gotten a feedback on Reddit and how someone already created a library react-on-off for such a scenario.

I believe that your HoC version is handy when good-enough is just good enough.

Collapse
barryblando profile image
Barry⚡ • Edited

Never forget to use prevState. 👍

  onClick = i => {
    this.setState(prevState => ({
      clicked: !prevState.clicked
    }));
  }