RaphaMex
RaphaMex

Reputation: 2839

Reactjs best practice to make a component controllable by other components?

I have a Chat component that displays like a popup. I want to give users the possibility to open/close it as actions of deeper components. I have spent hours on it because I'll use the same pattern for other components. I would really appreciate a clean / React solution!

I have managed to get something working so far, but it really looks clumsy.


EDIT: I made a singleton of it to make it look better. Still wondering what the React way is.

import React, { Component } from 'react';

var instance = null;

class Chat extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: false
    };
  }

  render() {
    return this.state.show ? (<div>CHAT WINDOW</div>) : null;
  }

  open  = () => this.setState({show: true})
  close = () => this.setState({show: false})
}

export const openChat = () => instance.open();

export default function ChatSingleton(props) {
  if (instance === null)
    instance = new Chat(props);
  return instance;
}

Now I can call open/close methods globally, like this:

import {openChat} from 'components/Chat';
export default function Foo(props) {
  return (<div onClick={openChat}>Open Chat</div>);
}

I found solutions to similar problems on SO that use Redux or React Context, but I really want a simple solution. HOC would be ok.

Thanks!

Upvotes: 0

Views: 192

Answers (1)

Alessio Koci
Alessio Koci

Reputation: 1113

Before looking for a solution try to understand if the "show" status must belong to chat or to his father(Chain - of - responsibility pattern) Once you understand this, here are some possible solutions

Using props:

class Parent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showChat: false
    };
  }
  openChat  = () => this.setState({showChat: true})
  closeChat = () => this.setState({showChat: false})

  render() {
    return (
      <div>
        <Chat show={this.state.showChat}>
        <Foo onOpenClick={this.openChat} onCloseClick={this.closeChat}>
      </div>
  }
}

Using Context:


class MyProvider extends React.Component {
 state = {
   isOpen: false
 };

openChat = e => {
  this.setState({ isOpen: true });
};

closeChat = e => {
  this.setState({ isOpen: false });
};

render() {
  return (
    <MyProvider
       value={{
         state: this.state,
        openChat: this.openChat,
        openChat: this.closeChat
       }}
     >
     <div >{this.props.children}</div
    </MyProvider>
 );
}

App:

import { MyProvider } from './ChatContext';
 <MyProvider>
   <Chat />
   <OtherComponent>
    <Foo>
   </OtherComponent>
</MyProvider>

Foo:


import React, { Component } from 'react';
import { MyConsumer } from './ChatContext';

class Foo extends Component {
    state = {};
    render() {
        return (
            <MyConsumer>
                {({ openChat }) => (
                    <div>
                        This is Foo
                        <button onClick={openChat}>open</span>
                    </div>
                )}
            </MyConsumer>
        );
    }
}

export default Foo;

Read more about context: https://kentcdodds.com/blog/how-to-use-react-context-effectively


or using React-redux: https://react-redux.js.org/

Upvotes: 1

Related Questions