我使用 react、node 和 socket.io 构建了一个聊天应用程序 所涉及的三个组件是
以下是所有三个组件的代码。
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
我收到以下错误
答案 0 :(得分:0)
我很确定您在函数内部和 useEffect
外部初始化 Imgur API 类的事实,或者传递参数,它在每次渲染时被调用,因此您对 API 的引用每次都会重新计算更新(也就是立即更新!)
您可能应该在另一个模块中启动 Imgur API 类并将其用作导入!