Arcandio
Arcandio

Reputation: 310

Calling a function in children in react?

I have an app with multiple Views. Each View has some number of mapped child Entities. Each of those entities has a state for whether or not it's collapsed/hidden called: this.state.show. Each Entity has a button that toggles the show state. That all works fine.

What I'd like to do is add a button up on the View that shows or hides all Entities at the same time. I thought this would be simple, but there are several complicating factors:

What's the right strategy to pursue here? Am I not thinking "react" enough?

View.jsx

import React, { Component } from 'react'
import Entity from './Entity.jsx'
import Filter from './Filter.jsx'

class View extends Component {
    render() {
        return (
            <div className="view main">
                {this.props.entities ? this.props.entities.map(
                    (e, i) => (
                        <Entity
                            entity={e}
                            propertyTypes={this.props.propertyTypes}
                            key={i}
                            index={i}
                            taglists={this.props.taglists}
                            ee={this.props.ee}
                        />
                    )
                ) : ""}
            </div>
        )
    }
}

export default View

Entity.jsx

import React, { Component } from 'react'
import Property from './Property.jsx'
import Event from './Event.jsx'
import Relationship from './Relationship.jsx'

class Entity extends Component {
    constructor (props) {
        super(props);
        this.state = {
            show: true
        }
        this.showHide = this.showHide.bind(this)
    }

    showHide() {
        this.setState({
            show: !this.state.show
        })
    }

    render() {
        return (
            <div className="entity" id={this.props.entity.name}>
                <div className="entity-header">
                    <h2>
                        <input
                            type="text"
                            value={this.props.entity.name}
                            onChange={this.emit}
                            data-original={this.props.entity.name}
                            data-index={this.props.index}
                            data-event="rename"
                        ></input>
                    </h2>
                    <button
                        className="entity-collapse"
                        data-index={this.props.index}
                        onClick={this.showHide}
                    >{this.state.show ? "-" : "+"}</button>
                </div>
                <div className={this.state.show ? "entity-content" : "entity-content hidden"}>
                    ...
                    removed for clarity
                    ...
                </div>
            </div>
        )
    }
}

export default Entity

Upvotes: 0

Views: 73

Answers (2)

mediaguru
mediaguru

Reputation: 1917

Your state should only be in the parent component, not any child components. You would then pass your functions and state as props to the children.

Upvotes: 2

Keith
Keith

Reputation: 24181

Below is an example of storing state in a higher order component.

If you look at the code there is just one place where state is stored, and that's in the View, This is known as single source of truth, and it makes coding in React much simpler.

For example if you click the all checkbox, all will check. And if you individually check each one, the all and none will also keep in sync.

You can of course alter this to suite you needs, I've used checkbox here as it's easy to see the state changes working.

For larger applications the single source of truth method becomes even more important, and storing state inside React is best avoided. This is what Redux is known for, like I mentioned in comments Redux is not something I use personally, but rolled my own single source of truth state management using proxy's, if I get time I might create an NPM module using this, as I'm sure other users would find it useful.

ps,. I've used React Hooks in the example as I believe it's the future of React, but it would be trivial to alter to use classes..

const {useState, cloneElement} = React;


function MultiCheck(props) {
  const {checkallto, list, setList} = props;
  const checked = !list.some(f => f !== checkallto);
  return <div>
    <input
      onClick={() => {
        setList(new Array(list.length).fill(checkallto));
      }}
      onChange={() => {}}
      checked={checked} 
      type="checkbox"/>
    {props.children}
  </div>;
}


function View(props) {
  const [list, setList] = useState(new Array(props.children.length).fill(false));
 
  function setEntityChecked(ix) {
    return (check) => {
      const newlist = [...list];
      newlist[ix] = check;
      setList(newlist);
    };
  }
  
  const children = props.children.map((
    f, ix) => cloneElement(f, {key:ix, checked: list[ix],
      setChecked: setEntityChecked(ix)}));
  return <div>
    <MultiCheck checkallto={true} list={list} setList={setList}>
      All
    </MultiCheck>
    <MultiCheck checkallto={false} list={list} setList={setList}>
      None
    </MultiCheck>
    <hr/>
    {children}
  </div>;
}

function Entity(props) {
  const {checked, setChecked} = props;
  return <div><input
    checked={checked}
    onChange={()=>{}}
    onClick={() => setChecked(!checked)}
    type="checkbox"/>{props.children}</div>;
}


ReactDOM.render(<React.Fragment>
  <View>
    <Entity>One</Entity>
    <Entity>Two</Entity>
    <Entity>Two</Entity>
  </View>
</React.Fragment>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="mount"></div>

Upvotes: 0

Related Questions