Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions