类型错误:无法读取未定义的 Socket.io

时间:2021-01-03 10:03:59

标签: reactjs socket.io

我使用 react、node 和 socket.io 构建了一个聊天应用程序 所涉及的三个组件是

  1. SocketProvider.js - 管理套接字的上下文 API。
  2. ConversationProvider.js - 管理聊天对话的上下文 API
  3. Openconversation.js - 单个对话 UI 组件,带有消息框、Uploadimage UI 组件、发送按钮和对话消息。
  4. UploadImage.js - 具有输入类型文件字段和 Imgur API 的 UI 组件,用于将所选图像上传到 imgur 并返回上传图像的 URL。

以下是所有三个组件的代码。

SocketProvider.js

import React, { useContext, useEffect, useState } from 'react'
import io from 'socket.io-client'
import { useContacts } from './ContactsProvider';

const SocketContext = React.createContext();


export function useSocket() {
    return useContext(SocketContext)
}

export function SocketProvider({id,children}) {
    const [socket, setSocket] = useState();
    const {contacts} = useContacts();
    const stringContacts =  JSON.stringify(contacts);
    
    useEffect(() => {
        const newSocket = io(
            'http://192.168.0.113:5000',
            {transports: ['websocket'],query: {id,stringContacts}}, 
        )
        setSocket(newSocket)

        return () => newSocket.close()
    }, [id,stringContacts] )

    return (
        <SocketContext.Provider value={socket}>
            {children}
        </SocketContext.Provider>
    )
}

Conversationprovider.js

import React, { useContext, useState, useCallback, useEffect } from 'react'
import useLocalStorage from '../hooks/useLocalStorage';
import { useContacts } from '../contexts/ContactsProvider'
import { useSocket } from './SocketProvider';

const ConversationsContext = React.createContext();

export function useConversations() {
    return useContext(ConversationsContext)
}

export function ConversationsProvider({ children, id }) {
    const [conversations, setConversations] = useLocalStorage('conversations', [])
    const { contacts } = useContacts();
    const socket = useSocket();
    const [selectedConversationIndex, setSelectedConversationIndex] = useState(0);

    const addMessageToConversation = useCallback(({ recipients, text, sender }) => {
        setConversations(prevConversations => {
            let madeChanges = false;
            const newMessage = { sender, text }
            const newConversations = prevConversations.map(conversation => {
                if (arrayEquality(conversation.recipients, recipients)) {
                    madeChanges = true;
                    return { ...conversation, messages: [...conversation.messages, newMessage] }
                }

                return conversation
            })

            if (madeChanges) {
                return newConversations
            }

            else {
                return [...prevConversations, { recipients, messages: [newMessage] }]
            }
        })


    }, [setConversations])

    useEffect(() => {
        if(socket == null) return
        
        socket.on('recieve-message', addMessageToConversation)
        return () => socket.off('recieve-message')
    }, [socket,addMessageToConversation])

    function sendMessage(recipients, text) {
        socket.emit('send-message', {recipients, text})
        addMessageToConversation({ recipients, text, sender: id })
    }

    

    function createConversations(recipients) {
        setConversations(prevConversations => {
            return [...prevConversations, { recipients, messages: [] }]
        })
    }

    const formattedConversations = conversations.map((conversation, index) => {
        const recipients = conversation.recipients.map(recipient => {
            const contact = contacts.find(contact => {
                return contact.id === recipient
            })
            const name = (contact && contact.name) || recipient;
            return { id: recipient, name }
        })
        const messages = conversation.messages.map(message => {
            const contact = contacts.find(contact => {
                return contact.id === message.sender
            })
            const name = (contact && contact.name) || message.sender;
            const fromMe = id === message.sender;
            return { ...message, senderName: name, fromMe }

        })
        const selected = selectedConversationIndex === index
        return { ...conversation, recipients, selected, messages }
    })

    const value = {
        formattedConversations,
        selectedConversation: formattedConversations[selectedConversationIndex],
        sendMessage,
        conversationSelected: setSelectedConversationIndex,
        createConversations
    }

    return (
        <ConversationsContext.Provider value={value}>
            {children}
        </ConversationsContext.Provider>
    )
}

function arrayEquality(a, b) {
    if (a.length != b.length) return false

    a.sort()
    b.sort()

    return a.every((element, index) => {
        return element === b[index]
    })
}

Openconversation.js

import { Button, Form, InputGroup } from 'react-bootstrap'
import { useConversations } from '../contexts/ConversationsProvider';
import Uploadimage from './UploadImage';

export default function OpenConversation() {
    const [text, setText] = useState('');
    const [url,setUrl] = useState('');
    const setRef = useCallback((node) => {
        if(node) {
            node.scrollIntoView({smooth: true})
        }
    }, []);
    const { sendMessage, selectedConversation } = useConversations()

    function handleSubmit(e) {
        e.preventDefault()
         sendMessage(selectedConversation.recipients.map(recipient => {
            return recipient.id;
        }), text)
        setText('')
    }

    function sendImage(image) {
        **sendMessage(selectedConversation.recipients.map(recipient => {
            return recipient.id;
            }), image)** 
    }


    

    return (
        <div className="d-flex flex-column flex-grow-1">
            <div className="flex-grow-1 overflow-auto ">
                <div className="d-flex flex-column align-items-start justify-content-end px-3" style={{'minHeight': '100%'}}>
                    {selectedConversation.messages.map((message, index) => {
                        const lastMessage = selectedConversation.messages.length - 1 === index; 
                        return <div ref={lastMessage ? setRef : null} key={index} className={`my-1 d-flex flex-column ${message.fromMe ? 'align-self-end' : null}`}>
                            <div className={`rounded px-2 py-1 ${message.fromMe ? 'bg-primary text-white' : 'border align-self-start'}`}>
                                {message.text.includes('https') ? <img src={message.text} width="250" /> : message.text}
                            </div>
                            <div className={`text-muted small ${message.fromMe ? 'text-right' : ''}`}>
                                {message.fromMe ? 'You' : message.senderName}
                            </div>
                        </div>
                    })}
                </div>
            </div>
            <Form onSubmit={handleSubmit}>
                <Form.Group className='m-2'>
                    <InputGroup>
                        <Form.Control as='textarea' required value={text}
                            onChange={e => setText(e.target.value)}
                            style={{ height: '75px', resize: 'none' }} />
                        <InputGroup.Append className="d-flex">
                            <Uploadimage statefunction={sendImage} uid='imgMessage' tall='100%' wide='80px'  fsize='25px'/>
                            <Button type="submit">Send</Button>
                        </InputGroup.Append>
                    </InputGroup>

                </Form.Group>
            </Form>
        </div>
    )
}

UploadImage.js

import React, {useEffect} from 'react'
import {Form, Button} from 'react-bootstrap'
import styles from '../css/styles.module.css'
import LoaderIcon from '../css/loading-spin.svg'
import { useConversations } from '../contexts/ConversationsProvider';



export default function UploadImage({statefunction,uid,tall,wide,fsize}) {

    const { sendMessage, selectedConversation } = useConversations();

    var Imgur = function (options) {
        if (!this || !(this instanceof Imgur)) {
            return new Imgur(options);
        }

        if (!options) {
            options = {};
        }

        if (!options.clientid) {
            throw 'Provide a valid Client Id here: https://api.imgur.com/';
        }

        this.clientid = options.clientid;
        this.endpoint = 'https://api.imgur.com/3/image';
        this.callback = options.callback || undefined;
        this.input = document.querySelectorAll('.'+uid);

        this.run();
    };

    Imgur.prototype = {
        createEls: function (name, props, text) {
            var el = document.createElement(name), p;
            for (p in props) {
                if (props.hasOwnProperty(p)) {
                    el[p] = props[p];
                }
            }
            if (text) {
                el.appendChild(document.createTextNode(text));
            }
            return el;
        },
        insertAfter: function (referenceNode, newNode) {
            referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
        },
        post: function (path, data, callback) {
            var xhttp = new XMLHttpRequest();

            xhttp.open('POST', path, true);
            xhttp.setRequestHeader('Authorization', 'Client-ID ' + this.clientid);
            xhttp.onreadystatechange = function () {
                if (this.readyState === 4) {
                    if (this.status >= 200 && this.status < 300) {
                        var response = '';
                        try {
                            response = JSON.parse(this.responseText);
                        } catch (err) {
                            response = this.responseText;
                        }
                        callback.call(window, response);
                    } else {
                        throw new Error(this.status + " - " + this.statusText);
                    }
                }
            };
            xhttp.send(data);
            xhttp = null;
        },
        createDragZone: function () {
            Array.prototype.forEach.call(this.input, function (zone) {
                this.upload(zone);
            }.bind(this));
        },
        loading: function () {
            var div, table, img;

            div = this.createEls('div', {className: styles.loadingModal});
            table = this.createEls('table', {className: styles.loadingTable});
            img = this.createEls('img', {className: styles.loadingImage, src: LoaderIcon});

            div.appendChild(table);
            table.appendChild(img);
            document.body.appendChild(div);
        },
        
        matchFiles: function (file, zone) {

            if (file.type.match(/image/) && file.type !== 'image/svg+xml') {
                document.body.classList.add(styles.loading);

                var fd = new FormData();
                fd.append('image', file);

                this.post(this.endpoint, fd, function (data) {
                    document.body.classList.remove(styles.loading);
                    typeof this.callback === 'function' && this.callback.call(this, data);
                }.bind(this));
            }
        },
        upload: function (zone) {
            var events = ['dragenter', 'dragleave', 'dragover', 'drop'],
                file, target, i, len;

            zone.addEventListener('change', function (e) {
                if (e.target && e.target.nodeName === 'INPUT' && e.target.type === 'file') {
                    target = e.target.files;

                    for (i = 0, len = target.length; i < len; i += 1) {
                        file = target[i];
                        this.matchFiles(file, zone);
                    }
                }
            }.bind(this), false);

            
        },
        run: function () {
            var loadingModal = document.querySelector(styles.loadingModal);

            if (!loadingModal) {
                this.loading();
            }
            this.createDragZone();
        }
    };
    
    var feedback = function(res) {
        if (res.success === true) {
            var get_link = res.data.link.replace(/^http:\/\//i, 'https://');
            statefunction(get_link)
        }
    };
    
    useEffect(() => {
        new Imgur({
            clientid: '128d58ccad247be', //You can change this ClientID
            callback: feedback
        });
        
    }, [])
   
    
    return (
        <>
        
        <Form.File id="formcheck-api-regular" className={styles.inputWrapper}>
          <Button style={{borderRadius: '50%',height: tall,width: wide}}><i className="fa fa-camera" style={{fontSize: fsize}}></i></Button>
          <Form.File.Input className={`${uid} ${styles.input}`} />
        </Form.File>
        <div className="preview"></div>        
        </>
    )
}

手头的问题是,每当我使用 OpenConversation 组件中的消息框发送文本消息时,它都会借助包裹在 OpenConversation 中的 handleSubmit 函数中的 sendMessage 函数成功传输>

sending text message text message sent

但是

每当我想将 UploadImage 组件返回的图像 URL 发送到 OpenConversation 组件时。 selecting an image

我收到以下错误

error

1 个答案:

答案 0 :(得分:0)

我很确定您在函数内部和 useEffect 外部初始化 Imgur API 类的事实,或者传递参数,它在每次渲染时被调用,因此您对 API 的引用每次都会重新计算更新(也就是立即更新!)

您可能应该在另一个模块中启动 Imgur API 类并将其用作导入!