Dal
Dal

Reputation: 164

Trying to create a special design pattern in react, unsure how to achieve it

I have two connected components, they both need the same parent so that they can talk to each other. However I would like to be able to render them anywhere, 100% separate. I have a jsfiddle example to show the idea, it is a terrible solution to my problem since I'm creating a new Img component whenever I need to change the props passed in. But it shows the idea. I feel like I'm probably going about this wrong but maybe there is a way to pass props to the Img without just making a new instance. Having a non React class be the parent is not ideal for sure.

Fiddle explanation: make an Img component that takes in a prop telling it if it should render or not make a Switch component that will change the prop passed into Img component when clicked

They can be rendered anywhere separately and are controlled by a parent class. The forceUpdate is just to make the example work, I know that is not a good use of it.

The code:

const Img = (props) => {

  return (
    <div><img style={{ display: props.isShowing ? 'inline' : 'none', width: '100px' }} src="http://blog.nationalgeographic.org/wp-content/uploads/2010/04/Greatest-Nature-Photographs-of-All-Time-3.jpg" /></div>
  );
};

const Switch = (props) => {

  return (
    <div style={{ width: '50px', height: '50px', background: 'black', color: 'white'}} onClick={() => props.toggleImg()}>
    click me
    </div>
  );
};

class MasterComponent {
  constructor(outerThis) {
    this.outerThis = outerThis;
    this.toggleState = true;
    this.img = <Img isShowing={ true } />;
    this.switch = <Switch toggleImg={ () => this.toggleImg() } />;
  }

  toggleImg() {
    this.toggleState = !this.toggleState;
    this.img = <Img isShowing={ this.toggleState } />;
    this.outerThis.forceUpdate();
  }

}


class Example extends React.Component {
    constructor(props) {
        super(props);
    this.masterComponent = new MasterComponent(this);
  }

  render() {
    return <div>
    {this.masterComponent.img}
    {this.masterComponent.switch}
    </div>;
  }
}

edit: So the question is basically this. I want the 'MasterComponent' to be some sort of parent that gives you two children that interact with each other in the realm of state/props but are rendered separately like in the Example's render. So imagine importing MasterComponent and then using it like I did in the Example component without knowing what is going on behind the scenes. That is the design pattern I hoped for, but it doesn't seem achievable with React alone maybe.

My version of the MasterComponent is bad because I'm replacing the Img component with a new instance of Img with different props when I really just want to update the props it had. Using forceUpdate over setState is bad too but I'm less concerned about that.

I think since MasterComponent isn't a react component with state that can cause a rerender and Img and Switch aren't inside a render function where they can organically receive that state, maybe my idea doesn't work.

Upvotes: 0

Views: 82

Answers (2)

Alexander Nied
Alexander Nied

Reputation: 13623

So... I don't know that this is a good pattern... it's not super React-y, but I think it would achieve what you are looking for. I haven't tested it, but I'm thinking something like this:

function getImgSwitchPair() {

  const state = {
    isShowing: true
  };

  const toggleImg = () => {
    state.isShowing = !state.isShowing;
  };

  const Img = () => {
    return (
      <div><img style={{ display: state.isShowing ? 'inline' : 'none', width: '100px' }} src="http://blog.nationalgeographic.org/wp-content/uploads/2010/04/Greatest-Nature-Photographs-of-All-Time-3.jpg" /></div>
    );
  };

  const Switch = () => {
    return (
      <div style={{ width: '50px', height: '50px', background: 'black', color: 'white'}} onClick={toggleImg}>
      click me
      </div>
    );
  };

  return {
    Img,
    Switch,
    toggleImg
  };

}


class Example extends React.Component {
  constructor(props) {
        super(props);
        const {Img, Switch} = getImgSwitchPair();
        this.Img = Img;
        this.Switch = Switch;
  }

  render() {
    return ( 
      <div>
        <Img/>
        <Switch/>
      </div>
    );
  }
}

getImgSwitchPair generates and returns a coupled Img and Switch component, as well as the toggleImg function if you want to call it manually. We use a state object to mutate to change isShowing state.

I think this would work. However, it would exist and update state completely outside of the React lifecycle, which I think would be problematic. So while this may work, I'm not positive it is a good pattern.

I'm hesitant to post this w/o testing and knowing it may be a problematic pattern, but I'm hoping perhaps it gets you down the path of what you're looking for...

Upvotes: 1

alex
alex

Reputation: 361

So i think it's pretty dang good, however to make it more "React tm" like:

const Img = (props) => {

  return (
    <div>
      <img
        style={{ display: props.isShowing ? 'inline' : 'none', width: '100px' }}
        src="http://blog.nationalgeographic.org/wp-content/uploads/2010/04/Greatest-Nature-Photographs-of-All-Time-3.jpg"/>
    </div>
  );
};

const Switch = (props) => {

  return (
    <div
      style={{ width: '50px', height: '50px', background: 'black', color: 'white'}}
      onClick={props.toggleImg}>
    click me
    </div>
  );
};

class MasterComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showImg: true,
    };

    this.toggleImg = this.toggleImg.bind(this);
  }

  toggleImg() {
    this.setState({showImg: !this.state.showImg});
  }

  render() {
    return <div>
      <Img isShowing={this.state.showImg} />
      <Switch toggleImg={this.toggleImg} />
    </div>;
  }
}


class Example extends React.Component {
  render() {
    return <div>
      <MasterComponent />
    </div>;
  }
}


ReactDOM.render(
  <Example />,
  document.getElementById('container')
);

like this is more composable and using react Components classes

also you maybe interested in something like:

{this.state.showImg && <Img isShowing={true} />}

Upvotes: 0

Related Questions