Skip to content

React tutorial StrictMode doubles all messages / removeListener seems to be broken #101

@nick-parker

Description

@nick-parker

The current React tutorial using create-react-app puts in index.js which causes all class initiators to be executed twice. The tutorial doesn't cleanup subscriptions / listeners in its UseEffect calls so you end up with duplicate listeners and every message is shown twice in the demo app. The code below exhibits the problem with StrictMode on, but doesn't with it off.

import React, { useState, useEffect } from 'react';
import PubNub from 'pubnub';
import { PubNubProvider, usePubNub } from 'pubnub-react';

const pubnub = new PubNub({
  publishKey: 'pub-c-816a2df2-0f37-44ac-a43e-132a1f7f3672',
  subscribeKey: 'sub-c-4cf98958-0607-4897-a543-07f0daa97aa1',
  userId: 'f8681796-a1d7-4b3e-823b-67944a5c035d',
  // logVerbosity: true,
});

function App() {
  return (
    <PubNubProvider client={pubnub}>
      <Chat />
    </PubNubProvider>
  );
}

function Chat() {
  const pubnub = usePubNub();
  const [channels] = useState(['awesome-channel']);
  const [messages, addMessage] = useState([]);
  const [message, setMessage] = useState('');

  const handleMessage = event => {
    const message = event.message;
    if (typeof message === 'string' || message.hasOwnProperty('text')) {
      const text = message.text || message;
      addMessage(messages => [...messages, text]);
    }
  };

  const sendMessage = message => {
    if (message) {
      pubnub
        .publish({ channel: channels[0], message })
        .then(() => setMessage(''));
    }
  };

  useEffect( () => {
    console.log("updating pubnub")
    console.log(handleMessage)
    pubnub.addListener({ message: handleMessage });
    return () => {
      console.log("cleanup listener")
      pubnub.removeListener(handleMessage)
    }
  }, [pubnub])

  useEffect(() => {
    console.log("Updating channels or pubnub")
    console.log(channels)
    pubnub.unsubscribeAll()
    pubnub.subscribe({ channels });
    return () => {
      console.log("cleanup channels")
      pubnub.unsubscribeAll()
    }
  }, [pubnub, channels]);

  return (
    <div style={pageStyles}>
      <div style={chatStyles}>
        <div style={headerStyles}>React Chat Example</div>
        <div style={listStyles}>
          {messages.map((message, index) => {
            return (
              <div key={`message-${index}`} style={messageStyles}>
                {message}
              </div>
            );
          })}
        </div>
        <div style={footerStyles}>
          <input
            type="text"
            style={inputStyles}
            placeholder="Type your message"
            value={message}
            onKeyPress={e => {
              if (e.key !== 'Enter') return;
              sendMessage(message);
            }}
            onChange={e => setMessage(e.target.value)}
          />
          <button
            style={buttonStyles}
            onClick={e => {
              e.preventDefault();
              sendMessage(message);
            }}
          >
            Send Message
          </button>
        </div>
      </div>
    </div>
  );
}

const pageStyles = {
  alignItems: 'center',
  background: '#282c34',
  display: 'flex',
  justifyContent: 'center',
  minHeight: '100vh',
};

const chatStyles = {
  display: 'flex',
  flexDirection: 'column',
  height: '50vh',
  width: '50%',
};

const headerStyles = {
  background: '#323742',
  color: 'white',
  fontSize: '1.4rem',
  padding: '10px 15px',
};

const listStyles = {
  alignItems: 'flex-start',
  backgroundColor: 'white',
  display: 'flex',
  flexDirection: 'column',
  flexGrow: 1,
  overflow: 'auto',
  padding: '10px',
};

const messageStyles = {
  backgroundColor: '#eee',
  borderRadius: '5px',
  color: '#333',
  fontSize: '1.1rem',
  margin: '5px',
  padding: '8px 15px',
};

const footerStyles = {
  display: 'flex',
};

const inputStyles = {
  flexGrow: 1,
  fontSize: '1.1rem',
  padding: '10px 15px',
};

const buttonStyles = {
  fontSize: '1.1rem',
  padding: '10px 15px',
};

export default App;

Notice I've tried to cleanup the example to fix this, first by ensuring channels aren't double-subscribed and then by splitting the listener part of useEffect into its own call with a cleanup function, but I haven't succeeded. Are there any more detailed docs on removeListener? It doesn't seem to have any effect for me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions