React Store 未使用 React.useReducer 更新上下文

React Store not updating context with React.useReducer

我正在使用 React 构建一个简单的聊天室,并且希望能够通过在文本字段中键入并按下发送按钮来使用文本更新聊天 window。 我期望当保存上下文的 Store 调用 React.UseReducer 时,它会使用传递到 Dashboard 组件的文本值更新上下文。当我在 reducer 中记录文本时,它显示了文本字段中应该添加到聊天中的值 window。但是,运行使用Reducer后,状态仍然只是initState,不包括发送的文本。

我认为 allChats 将使用商店中的文本字段值进行更新,然后在 Dashboard.js 中使用 React.useContext(CTX) 进行访问。 allChats 仅记录存储中的 initState。

如何更新它以将文本传递到上下文中,以便聊天 window 能够显示它?

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',msg:'yo'},
    {from:'steve',msg:'yo'},
  ],
  topic2:[
    {from:'bill',msg:'yo'},
    {from:'joe',msg:'yo'},
    {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
  }
}

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,sendChatAction,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 函数移动到组件中,以便它可以使用减速器的 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", msg: "yo" },
    { from: "steve", msg: "yo" }
  ],
  topic2: [
    { from: "bill", msg: "yo" },
    { from: "joe", msg: "yo" },
    { 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, dispatch] = React.useReducer(reducer, initState);

  function sendChatAction(value) {
    dispatch({
      type: "RECEIVE_MESSAGE",
      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, 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>
  );
}

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>
  );
}