How to Use the ChatEngine Muter Plugin to Silence or Block Other Users in Chat Apps

Whether you’re muting a user so you can focus on your work or blocking a cyberbully, giving your chat users the ability to mute and block other chat users is a core feature of any chat application.

In this tutorial, we’ll show you how to implement user muting and blocking using the ChatEngine Muter Plugin. We’ll quickly build a simple React chat app with realtime messaging and a user list, then show you how to implement the muting functionality. The full GitHub repo for this project is available here.

Configure ChatEngine

Before we dive into the coding, you’ll first need to configure ChatEngine and get your keys:

Step 1: Simple React Chat App

Start by creating a React project. To do that, you’ll need the Node Package Manager. To get npm, install the Node.js package. From there, navigate to your terminal and create a project using the React project creation package.

Once the package has been created, change into that directory so that you can start working with the project.

$ npx create-react-app muter-chatengine-react
$ cd muter-chatengine-react

Once inside the project, you can see our application live by running the start script using npm.

$ npm start

Now navigate to localhost:3000 in your browser, and you’ll see the generic React app webpage. Have a look at the files in our project. We see that there are three folders, but the one that we’ll be looking at is the src folder. In here we’ll be creating two new files which will represent components that will make up your app.

For this project, you’ll be using the Material-UI library for React to polish the look and feel of your chat app. To add that to your project, install the package using npm.

$ npm i @material-ui/core

A crucial part of the chat section will be messages, so create a Message.js file to store that component.

import React, { PureComponent } from 'react';
import './index.css';

export default class Message extends PureComponent{
  render () {
      return ( 
        <div>
            { this.props.uuid }  : { this.props.text } </div>
      );
  }
};

All you’re doing here is creating a component that shows the user id with their message. Now let’s work on the index.js to create the main section of the app.

We’ll first need to initialize the ChatEngine object. Add ChatEngine and the Muter plugin to the project. We can do that using npm in the root of our project.

$ npm i chat-engine chat-engine-muter

Go to index.js in the src folder and initialize the ChatEngine object.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import ChatEngineCore from 'chat-engine';

import muter from 'chat-engine-muter';
import Message from './Messages';

const now = new Date().getTime();
const username = ['user', now].join('-');


const ChatClient = ChatEngineCore.create({
    publishKey: 'Your-Public-Key',
    subscribeKey: 'You-Private-Key'
}, {
    globalChannel: 'chatting'
});

ChatClient.connect(username, {
  signedOnTime: now
}, 'auth-key');

const styles = {
  card: {
    maxWidth: 345
  },
  openCard:{
    maxWidth: 200
  },
  openMedia: {
    height: 80,
  },
  media: {
    objectFit: 'cover',
  },
  container: {
    display: 'flex',
    flexWrap: 'wrap',
  },
};

Once that’s done, you can add the Muter plugin to your project by injecting it into the object in the constructor method within our ChatEngine object.

class App extends Component {

  constructor(props) {
    super(props);
    this.chat = new ChatClient.Chat('muter');
    this.chat.plugin(muter());

    this.state = {
      messages: [],
      chatInput: '' 
    };
  }
//... Other Code
}

You can also define the user interface by adding the elements to the render function of the component.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import ChatEngineCore from 'chat-engine';
import Message from './Messages';
import muter from 'chat-engine-muter';

import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import Input from '@material-ui/core/Input';


const now = new Date().getTime();
const username = ['user', now].join('-');

const apiKey = "5b9afbf1719dea2000a02376";

const ChatClient = ChatEngineCore.create({
    publishKey: 'your publish key',
    subscribeKey: 'your subscribe key'
}, {
    globalChannel: 'chatting'
});

ChatClient.connect(username, {
  signedOnTime: now
}, 'auth-key');

const styles = {
  card: {
    maxWidth: 345
  },
  openCard:{
    maxWidth: 200
  },
  openMedia: {
    height: 80,
  },
  media: {
    objectFit: 'cover',
  },
  container: {
    display: 'flex',
    flexWrap: 'wrap',
  },
};

class App extends Component {

  constructor(props) {
    super(props);
    this.chat = new ChatClient.Chat(`openGraph`);
    this.chat.plugin(muter());

    this.state = {
      messages: [],
      chatInput: '' 
    };
  }

  sendChat = () => {
    if (this.state.chatInput) {
        this.chat.emit('message', {
            text: this.state.chatInput,
            uuid: username
        });
        this.setState({ chatInput: '' })
    }

  }

  setChatInput = (event) => {
    this.setState({ chatInput: event.target.value })
  }

  componentDidMount() {
    this.chat.on('message', (payload) => {
        const { data } = payload;
        let messages = this.state.messages;

        messages.push(
            <Message key={ this.state.messages.length } uuid={ payload.data.uuid } text={       payload.data.text }/>
          );
        }
        this.setState({
            messages: messages
        });

    });
  }

  handleKeyPress = (e) => {
    if (e.key === 'Enter') {
        this.sendChat();
    }
  }

  render(){
    const { classes } = this.props;
    return(
      <Card className={classes.card} >
          <CardContent>
            <Typography gutterBottom variant="headline" component="h2">
              Messages
            </Typography>
              <div className={classes.root}>
                <List component="nav">
                  <ListItem>
                  <Typography component="div">
                    { this.state.messages }
                  </Typography>
                  </ListItem>
                </List>
              </div>
          </CardContent>
          <CardActions>
            <Input
              placeholder="Enter a message"
              value={this.state.chatInput}
              className={classes.input}
              onKeyDown={this.handleKeyPress}
              onChange={this.setChatInput}
              inputProps={{
                'aria-label': 'Description',
              }}
            />
            <Button size="small" color="primary">
              Github
            </Button>
            <Button size="small" color="primary">
              Article
            </Button>
          </CardActions>
        </Card>
      );
    }
  }

You’ll also need to ensure that the component renders once ChatEngine is ready. Add the following lines of code to the bottom of your file to make that happen.

const ChatComponent = withStyles(styles)(App);

ChatClient.on('$.ready', () => {
    ReactDOM.render(<ChatComponent />, document.getElementById('root'));
});

Great! We now have core chat – sending and receiving messages.

Step 2: Realtime User List

Now we’re going to create another component that shows what users that are connected to the chat. Call this component UserList.js .

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import ListItemText from '@material-ui/core/ListItemText';
import ListSubheader from '@material-ui/core/ListSubheader';
import Switch from '@material-ui/core/Switch';

const styles = theme => ({
  root: {
    width: '100%',
    maxWidth: 360,
    backgroundColor: theme.palette.background.paper,
  },
});

class UserList extends Component {
  state = {
    checked: [],
  };

  handleToggle = value => () => {
    const { checked } = this.state;
    const currentIndex = checked.indexOf(value);
    const newChecked = [...checked];
    
    // muting based on toggle
    if (currentIndex === -1) {
      this.props.callback(value, true);
      newChecked.push(value);
    } else {
      this.props.callback(value, false);
      newChecked.splice(currentIndex, 1);
    }

    this.setState({
      checked: newChecked,
    });
  };

  render() {
    const { users } = this.props;
    return (
        <List subheader={<ListSubheader>Users - Mute</ListSubheader>}>
            {
                Object.keys(users).map((name, index) => { 
                    return(
                        <ListItem key={index} >
                            <ListItemText primary={name} />
                            <ListItemSecondaryAction>
                            <Switch
                                onChange={this.handleToggle(name)}
                                checked={this.state.checked.indexOf(name) !== -1}
                            />
                            </ListItemSecondaryAction>
                        </ListItem>
                    );
                })
            }
        </List>
    );
  }
}

UserList.propTypes = {
  classes: PropTypes.object.isRequired,
  users: PropTypes.object.isRequired,
  callback: PropTypes.func.isRequired,
};

export default withStyles(styles)(UserList);

Note that we’re expecting a users object which contains all the users in the chat to display. Also, expect a callback which will allow the user to choose someone to mute by sending back the user and whether they’re muted.

Step 3: Muting Chat Users

Now let’s get to why you’re here, adding muting capabilities! Move back into the index.js file and add functions inside of our component to enable the muting functionality.

class App extends Component {

  constructor(props) {
    super(props);
    this.chat = new ChatClient.Chat('muter');
    this.chat.plugin(muter());

    this.state = {
      messages: [],
      chatInput: '' 
    };
  }

  sendChat = () => {
    if (this.state.chatInput) {
        this.chat.emit('message', {
            text: this.state.chatInput,
            uuid: username
        });
        this.setState({ chatInput: '' })
    }

  }

  setChatInput = (event) => {
    this.setState({ chatInput: event.target.value })
  }

  componentDidMount() {
    this.chat.on('message', (payload) => {
        const { uuid, text } = payload.data;

        let messages = this.state.messages;
        if(!this.chat.muter.isMuted(uuid)){
          messages.push(
            <Message key={ this.state.messages.length } uuid={ uuid } text={ text }/>
          );
          this.setState({
              messages: messages
          });
        }
        
    });
  }

  handleKeyPress = (e) => {
    if (e.key === 'Enter') {
        this.sendChat();
    }
  }

// Other code
}

Add the UserList to our render function so that it displays the toggle.

import UserList from './UserList';
// Other Code
class App extends Component {
// Other Code
  render(){
    const { classes } = this.props;
    return(
      <Card className={classes.card} >
          <CardContent>
            <UserList users={this.chat.users} callback={(uuid, muteState) => {
              if(muteState){
                this.chat.muter.mute(uuid);
              } else {
                this.chat.muter.unmute(uuid);
              }
            }}> 
            </UserList>
            <Typography gutterBottom variant="headline" component="h2">
              Messages
            </Typography>
              <div className={classes.root}>
                <List component="nav">
                  <ListItem>
                  <Typography component="div">
                    { this.state.messages }
                  </Typography>
                  </ListItem>
                </List>
              </div>
          </CardContent>
          <CardActions>
            <Input
              placeholder="Enter a message"
              value={this.state.chatInput}
              className={classes.input}
              onKeyDown={this.handleKeyPress}
              onChange={this.setChatInput}
              inputProps={{
                'aria-label': 'Description',
              }}
            />
            <Button size="small" color="primary" link="https://github.com/nxsyed/Chat-Engine-OpenGraph">
              Github
            </Button>
            <Button size="small" color="primary">
              Article
            </Button>
          </CardActions>
        </Card>
      );
    }
  }

Next Steps and Further Learning

One of the benefits of ChatEngine is all the powerful plugins available at your fingertips, allowing you to build the chat features that your users will love. For example, you could add simple typing indicators, or unfurl URLs into rich media. It’s all available in the ChatEngine plugin library.

Try PubNub Today