问题描述
我正在用React构建一个简单的聊天室,并希望能够通过在文本字段中键入并按发送按钮来更新带有文本的聊天窗口。 我期望当持有上下文的商店调用React.UseReducer时,它将使用传递到仪表板组件的文本值更新上下文。当我在化简器中记录文本时,它显示了应添加到聊天窗口的文本字段中的值。但是,运行useReducer后,状态仍然仅为initState,并且不包含发送的文本。
我认为allChats将使用商店中的文本字段值进行更新,然后使用React.useContext(CTX)在Dashboard.js中进行访问。 allChats仅将存储中的initState记录为日志。
该如何更新才能将文本传递到上下文中,以便聊天窗口可以显示它?
Dashboard.js
export default function Dashboard() {
const classes = useStyles();
//CTX store
const {allChats,sendChatAction,user} = React.useContext(CTX);
const topics = Object.keys(allChats);
//local state
const [activetopic,changeActivetopic] = React.useState(topics[0])
const [textValue,changeTextValue] = React.useState('')
return (
<div className={classes.root}>
<Paper className={classes.root}>
<Typography variant="h4" component="h4">
Chat Room
</Typography>
<Typography variant="h5" component="h5">
{activetopic}
</Typography>
<div className={classes.flex}>
<div className={classes.topicsWindow}>
<List>
{topics.map((topic) => (
<ListItem onClick={e=> changeActivetopic(e.target.innerText)} key={topic} button>
<ListItemText primary={topic} />
</ListItem>
))}
</List>
</div>
<div className={classes.chatwindow}>
{
allChats[activetopic].map((chat,i) => (
<div className={classes.flex} key={i}>
<Chip label={chat.from} className={classes.chip} />
<Typography variant='body1' gutterBottom>{chat.msg}</Typography>
</div>
))
}
</div>
</div>
<div className={classes.flex}>
<TextField
label="Send a Chat"
className={classes.chatBox}
value={textValue}
onChange={e => changeTextValue(e.target.value)}
/>
<Button
variant="contained"
color="primary"
className = {classes.button}
onClick ={() => {
sendChatAction({from: user,msg: textValue,topic: activetopic});
changeTextValue('');
}}
>
Send
</Button>
</div>
</Paper>
</div>
);
};
商店
import React from 'react'
import io from 'socket.io-client'
export const CTX = React.createContext()
const initState = {
general: [
{from:'matt',msg:'yo'},{from:'jeff',{from:'steve',],topic2:[
{from:'bill',{from:'joe',{from:'dave',]
}
function reducer(state,action){
//payload logs as:
//{from:'from',msg:'msg',topic:'topic'}
const {from,msg,topic} = action.payload;
switch(action.type){
case 'RECEIVE_MESSAGE':
return {
...state,[topic]:[
...state[topic],{from,msg}
]
}
default:
return state
}
}
let socket;
function sendChatAction(value){
socket.emit('chat message',value);
}
export default function Store(props){
const [allChats,dispatch] = React.useReducer(reducer,initState);
if(!socket){
socket = io(':3001');
socket.on('chat message',function(msg){
//msg logs with text field value that we need in
//chatwindow on Dashboard.
dispatch({type:'RECEIVE_MESSAGE',payload: msg});
});
}
//allChats logs only as initState,above.
const user = 'matt' + Math.random(100).toFixed(2);
return (
<CTX.Provider value={{allChats,user}}>
{props.children}
</CTX.Provider>
)
}
index.js
var app = require('express')()
var http = require('http').createServer(app);
var io = require('socket.io')(http)
app.get('/',function(req,res){
res.send('<h1>hello world</h1>')
});
io.on('connection',function(socket){
console.log('a user connected',socket);
socket.on('chat message',function(msg){
console.log('message: ' + JSON.stringify(msg))
io.emit('chat message',msg)
});
})
http.listen(3001,function(){
console.log('listening on *:3001')
});
解决方法
我整理了一个working example,用一个简单的setTimeout
取消了套接字调用。
可能最有趣的部分是将sendChatAction
函数移至组件中,以便可以使用reducer的dispatch
方法:
export default function Store(props) {
const [allChats,dispatch] = React.useReducer(reducer,initState);
function sendChatAction(value) {
dispatch({
type: "RECEIVE_MESSAGE",payload: value
});
}
...
}
一切似乎正常。将您的代码与此进行比较:
Store.jsx
import React,{ useEffect } from "react";
export const CTX = React.createContext();
const initState = {
general: [
{ from: "matt",msg: "yo" },{ from: "jeff",{ from: "steve",msg: "yo" }
],topic2: [
{ from: "bill",{ from: "joe",{ from: "dave",msg: "yo" }
]
};
function reducer(state,action) {
//payload logs as:
//{from:'from',msg:'msg',topic:'topic'}
const { from,msg,topic } = action.payload;
switch (action.type) {
case "RECEIVE_MESSAGE":
return {
...state,[topic]: [...state[topic],{ from,msg }]
};
default:
return state;
}
}
export default function Store(props) {
const [allChats,payload: value
});
}
useEffect(() => {
//msg logs with text field value that we need in
//chatWindow on Dashboard.
setTimeout(() => {
sendChatAction({ from: "matt",msg: "hey",topic: "general" });
},3000);
},[]);
//allChats logs only as initState,above.
const user = "matt" + Math.random(100).toFixed(2);
return (
<CTX.Provider value={{ allChats,sendChatAction,user }}>
{props.children}
</CTX.Provider>
);
}
Dashboard.jsx
import {
Button,Chip,List,ListItem,ListItemText,Paper,TextField,Typography
} from "@material-ui/core";
import React from "react";
import { CTX } from "./Store";
export default function Dashboard() {
const classes = {};
//CTX store
const { allChats,user } = React.useContext(CTX);
const topics = Object.keys(allChats);
//local state
const [activeTopic,changeActiveTopic] = React.useState(topics[0]);
const [textValue,changeTextValue] = React.useState("");
return (
<div className={classes.root}>
<Paper className={classes.root}>
<Typography variant="h4" component="h4">
Chat Room
</Typography>
<Typography variant="h5" component="h5">
{activeTopic}
</Typography>
<div className={classes.flex}>
<div className={classes.topicsWindow}>
<List>
{topics.map((topic) => (
<ListItem
onClick={(e) => changeActiveTopic(e.target.innerText)}
key={topic}
button
>
<ListItemText primary={topic} />
</ListItem>
))}
</List>
</div>
<div className={classes.chatWindow}>
{allChats[activeTopic].map((chat,i) => (
<div className={classes.flex} key={i}>
<Chip label={chat.from} className={classes.chip} />
<Typography variant="body1" gutterBottom>
{chat.msg}
</Typography>
</div>
))}
</div>
</div>
<div className={classes.flex}>
<TextField
label="Send a Chat"
className={classes.chatBox}
value={textValue}
onChange={(e) => changeTextValue(e.target.value)}
/>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={() => {
sendChatAction({
from: user,msg: textValue,topic: activeTopic
});
changeTextValue("");
}}
>
Send
</Button>
</div>
</Paper>
</div>
);
}
App.js
import React from "react";
import Dashboard from "./Dashboard";
import Store from "./Store";
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Store>
<Dashboard />
</Store>
</div>
);
}