Skip to content Skip to sidebar Skip to footer

Toggling Visibility Of Array Of Stateless React Components

I am trying to simply map over some data returned from an api and create a stateless component for each object returned. I want to be able to click on any of the components to togg

Solution 1:

You could keep an object visible in your parent component that will have keys representing a person index and a value saying if the person is visible or not. This way you can toggle the person's index in this single object instead of having stateful child components.

Example

class PersonList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      resource: [],
      visible: {}
    };
  }

  // ...

  toggleVisibility = index => {
    this.setState(previousState => {
      const visible = { ...previousState.visibile };
      visible[index] = !visible[index];
      return { visible };
    });
  };

  render() {
    const mappedPeople = this.state.resource.map((person, i) => (
      <Person
        key={i}
        {...person}
        visible={this.state.visible[i]}
        onClick={() => this.toggleVisibility(i)}
      />
    ));
    return <div>{mappedPeople}</div>;
  }
}

const Person = (props) => (
  <div>
    <h1 onClick={props.onClick}>{props.name}</h1>
    {props.visible && (
      <div>
        <p>{props.height}</p>
      </div>
    )}
  </div>
);

Solution 2:

Similar idea with @Tholle but a different approach. Assuming there is an id in the person object we are changing visibles state and toggling ids.

class PersonList extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      resource: this.props.persons,
      visibles: {},
    }
  }

  toggleVisible = id => this.setState( prevState => ({
    visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
  }))

  render() {
    const mappedPeople =
      this.state.resource.map((person, i) =>
        <Person
          key={person.id}
          visibles={this.state.visibles}
          toggleVisible={this.toggleVisible}
          {...person}
        />
      )
      
    return (
      <div>
        {mappedPeople}
      </div>
    )
  }
}

const Person = (props) => {
  const handleVisible = () =>
    props.toggleVisible( props.id );
  
  return (
    <div>
      <h1 onClick={handleVisible}>
        {props.name}</h1>
      {props.visibles[props.id] &&
        <div>

          <p>{props.height}</p>
        </div>
      }
    </div>
  );
}

const persons = [
  { id: 1, name: "foo", height: 10 },
  { id: 2, name: "bar", height: 20 },
  { id: 3, name: "baz", height: 30 },
]

const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Solution 3:

You can make sure your "this.state.resource" array has a visibility flag on each object:

this.state.resource = [
   { ..., visibility: true },
   { ..., visibility: false}
   ...
];

Do this by modifying your fetch a little bit.

let fetchedData = await API_Call("people");
this.setState({ 
    resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});

Merge your Person component back into PersonList (like you are trying to do), and on your onclick, do this:

onClick={() => this.toggleVisible(i)}

Change toggleVisible() function to do the following.

toggleVisible = (idx) => {
    const personList = this.state.resource;
    personList[idx].visibility = !personList[idx].visibility;
    this.setState({ resource: personList });      
}

So now, when you are doing:

this.state.resource.map((person, i) => ...

... you have access to "person.visibility" and your onclick will toggle the particular index that is clicked.

I think that directly answers your question, however...

I would continue with breaking out Person into it's own component, it really is good practice!

Other than better organization, one of the main reason is to avoid lamdas in props (which i actually did above). Since you need to do an onClick per index, you either need to use data attributes, or actually use React.Component for each person item. You can research this a bit here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

BTW you can still create "components" that aren't "React.Component"s like this:

import React from 'react';

const Person = ({ exProp1, exProp2, exProp3}) => {
    return <div>{exProp1 + exProp2 + exProp3}</div>
}

Person.propTypes = {
    ...
}

export default Person;

As you can see, nothing is inheriting from React.Component, so you are getting the best of both worlds (create components without creating "Components"). I would lean towards this approach, vs putting everything inline. But if your application is not extremely large and you just want to get it done, going with the first approach isn't terribly bad.


Post a Comment for "Toggling Visibility Of Array Of Stateless React Components"