【问题标题】:ReactJS and Twilio - TypeError: this.props.history not definedReactJS 和 Twilio - TypeError:this.props.history 未定义
【发布时间】:2021-11-24 01:08:32
【问题描述】:

第一次学习 React,并选择使用 Twilio 的聊天应用演示来快速入门。

我可以毫无问题地呈现欢迎屏幕,但在我登录时不会路由到聊天屏幕/聊天室。包括演示链接、代码 sn-ps、注释等。

有人看到这里发生了什么并可以提供建议吗?到目前为止,我已经修复了一些由于发布演示后的更新而出现的问题(将 Switch 更改为 Routes 等),但无法解决这个问题 TypeError。欢迎任何和所有的帮助和 TIA!

Twilio 演示链接:Twilio Programmable Chat App Demo

在 login() 函数中引发错误,在以下行:this.props.history.push('chat', { email, room });,错误显示为 Uncaught TypeError: this.props.history is undefined

顺便说一句,我试图从react-router-dom 导入withRouter 方法,但该方法不是从react-router-dom 导出的,而且我在网上找到的有关此方法的所有信息都指向旧版本的react-router-dom比我正在使用的,所以这不是一个可行的解决方案。我也尝试在调用login()onClick 上应用.bind(this),但这也不起作用。

WelcomeScreen.js

import React from "react";
import {
  Grid,
  TextField,
  Card,
  AppBar,
  Toolbar,
  Typography,
  Button,
 } from "@material-ui/core";


class WelcomeScreen extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
        email: "",
        room: "",
    };
}

login = () => {
    const { room, email } = this.state;
    if (room && email) {
        this.props.history.push("chat", { room, email });
    }
}

handleChange = (event) => {
    this.setState({ [event.target.name]: event.target.value });
};

render() {
    const { email, room } = this.state;
    return (
      <>
        <AppBar style={styles.header} elevation={10}>
          <Toolbar>
            <Typography variant="h6">
              Chat App with Twilio Programmable Chat and React
            </Typography>
          </Toolbar>
        </AppBar>
        <Grid
          style={styles.grid}
          container
          direction="column"
          justify="center"
          alignItems="center">
          <Card style={styles.card} elevation={10}>
            <Grid item style={styles.gridItem}>
              <TextField
                name="email"
                required
                style={styles.textField}
                label="Email address"
                placeholder="Enter email address"
                variant="outlined"
                type="email"
                value={email}
                onChange={this.handleChange}/>
            </Grid>
            <Grid item style={styles.gridItem}>
              <TextField
                name="room"
                required
                style={styles.textField}
                label="Room"
                placeholder="Enter room name"
                variant="outlined"
                value={room}
                onChange={this.handleChange}/>
            </Grid>
            <Grid item style={styles.gridItem}>
              <Button
                color="primary"
                variant="contained"
                style={styles.button}
                onClick={this.login}>
                Login
              </Button>
            </Grid>
          </Card>
        </Grid>
      </>
    );
  }      
}

const styles = {
  header: {},
  grid: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 },
  card: { padding: 40 },
  textField: { width: 300 },
  gridItem: { paddingTop: 12, paddingBottom: 12 },
  button: { width: 300 },
};  

export default WelcomeScreen;  

ChatScreen.js

import React from "react";
import {
  AppBar,
  Backdrop,
  CircularProgress,
  Container,
  CssBaseline,
  Grid,
  IconButton,
  List,
  TextField,
  Toolbar,
  Typography,
} from "@material-ui/core";
import { Send } from "@material-ui/icons";
import axios from "axios";
import ChatItem from "./ChatItem";
const Chat = require("twilio-chat");


 class ChatScreen extends React.Component {
   constructor(props) {
    super(props);

    this.state = {
        text: "",
        messages: [],
        loading: false,
        channel: null,
    };

    this.scrollDiv = React.createRef();
}

componentDidMount = async () => {
    const { location } = this.props;
    const { state } = location || {};
    const { email, room } = state || {};
    let token = "";
  
    if (!email || !room) {
        this.props.history.replace("/");
    }
  
    this.setState({ loading: true });
  
    try {
        token = await this.getToken(email);
    } catch {
        throw new Error("Unable to get token, please reload this page");
    }

    const client = await Chat.Client.create(token);

    client.on("tokenAboutToExpire", async () => {
        const token = await this.getToken(email);
        client.updateToken(token);
    });

    client.on("tokenExpired", async () => {
        const token = await this.getToken(email);
        client.updateToken(token);
    });

    client.on("channelJoined", async (channel) => {
        // getting list of all messages since this is an existing channel
        const messages = await channel.getMessages();
        this.setState({ messages: messages.items || [] });
        this.scrollToBottom();
    });
    
    try {
        const channel = await client.getChannelByUniqueName(room);
        this.joinChannel(channel);
    } catch(err) {
        try {
            const channel = await client.createChannel({
                uniqueName: room,
                friendlyName: room,
        });
      
            this.joinChannel(channel);
        } catch {
            throw new Error("Unable to create channel, please reload this page");
        }
    }
}
  
getToken = async (email) => {
    const response = await axios.get(`http://localhost:5000/token/${email}`);
    const { data } = response;
    return data.token;
}

joinChannel = async (channel) => {
    if (channel.channelState.status !== "joined") {
        await channel.join();
   }
 
    this.setState({ 
        channel:channel, 
        loading: false 
    });
 
   channel.on("messageAdded", this.handleMessageAdded);
   this.scrollToBottom();
};
 
 
handleMessageAdded = (message) => {
    const { messages } = this.state;
    this.setState({
        messages: [...messages, message],
    },
        this.scrollToBottom
    );
};
 
scrollToBottom = () => {
    const scrollHeight = this.scrollDiv.current.scrollHeight;
    const height = this.scrollDiv.current.clientHeight;
    const maxScrollTop = scrollHeight - height;
    this.scrollDiv.current.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
};

sendMessage = () => {
    const { text, channel } = this.state;
    if (text) {
        this.setState({ loading: true });
        channel.sendMessage(String(text).trim());
        this.setState({ text: "", loading: false });
    }
};

render() {
    const { loading, text, messages, channel } = this.state;
    const { location } = this.props;
    const { state } = location || {};
    const { email, room } = state || {};
  
    return (
      <Container component="main" maxWidth="md">
        <Backdrop open={loading} style={{ zIndex: 99999 }}>
          <CircularProgress style={{ color: "white" }} />
        </Backdrop>
  
        <AppBar elevation={10}>
          <Toolbar>
            <Typography variant="h6">
              {`Room: ${room}, User: ${email}`}
            </Typography>
          </Toolbar>
        </AppBar>
  
        <CssBaseline />
  
        <Grid container direction="column" style={styles.mainGrid}>
          <Grid item style={styles.gridItemChatList} ref={this.scrollDiv}>
            <List dense={true}>
                {messages &&
                  messages.map((message) => 
                    <ChatItem
                      key={message.index}
                      message={message}
                      email={email}/>
                  )}
            </List>
          </Grid>
  
          <Grid item style={styles.gridItemMessage}>
            <Grid
              container
              direction="row"
              justify="center"
              alignItems="center">
              <Grid item style={styles.textFieldContainer}>
                <TextField
                  required
                  style={styles.textField}
                  placeholder="Enter message"
                  variant="outlined"
                  multiline
                  rows={2}
                  value={text}
                  disabled={!channel}
                  onChange={(event) =>
                    this.setState({ text: event.target.value })
                  }/>
              </Grid>
              
              <Grid item>
                <IconButton
                  style={styles.sendButton}
                  onClick={this.sendMessage}
                  disabled={!channel}>
                  <Send style={styles.sendIcon} />
                </IconButton>
              </Grid>
            </Grid>
          </Grid>
        </Grid>
      </Container>
    );
  }      
}

const styles = {
    textField: { width: "100%", borderWidth: 0, borderColor: "transparent" },
    textFieldContainer: { flex: 1, marginRight: 12 },
    gridItem: { paddingTop: 12, paddingBottom: 12 },
    gridItemChatList: { overflow: "auto", height: "70vh" },
    gridItemMessage: { marginTop: 12, marginBottom: 12 },
    sendButton: { backgroundColor: "#3f51b5" },
    sendIcon: { color: "white" },
    mainGrid: { paddingTop: 100, borderWidth: 1 },
};

export default ChatScreen;

Router.js

import React from "react";
import { BrowserRouter, Routes, Route, } from "react-router-dom";
import WelcomeScreen from "./WelcomeScreen";
import ChatScreen from "./ChatScreen";

function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/chat" element={<ChatScreen/>} />
        <Route path="/" element={<WelcomeScreen/>} />
      </Routes>
    </BrowserRouter>
  );
}

export default Router;

【问题讨论】:

  • 如果ChatScreenWindowScreen功能组件,那么您可以使用useNavigate 而不是useHistory。但找不到任何特定于 类组件
  • 这基本上是因为,element in &lt;Route/&gt; 没有传递任何历史道具。

标签: javascript reactjs twilio-programmable-chat


【解决方案1】:

如果ChatScreenWindowScreen功能组件,那么可以直接用useNavigate代替useHistory但找不到任何特定于类组件的内容。 它在从文档的 v5 部分迁移时给出。

https://reactrouter.com/docs/en/v6/upgrading/v5

您可以搜索useNavigate


解决方案(解决方法

你可以在你的班级周围创建一个函数 Wrapper Component 组件,在该组件中,您可以传递诸如

之类的道具
  • navigate from useNavigate

我从 HOC components 那里得到了一个想法。 这是我尝试和工作的一个例子。 https://codesandbox.io/s/snowy-moon-30br5?file=/src/Comp.js

这可以在我们找不到合法的方法之前一直有效。

您可以点击路由/1中的Click按钮,它成功导航到路由/2

注意:Sandox 的控制台

中查看控制台以获取传递的道具

【讨论】:

    【解决方案2】:

    这里是 Twilio 开发者宣传员。

    在您的 Router.js 文件中,您有:

            <Route path="/chat" element={<ChatScreen/>} />
            <Route path="/" element={<WelcomeScreen/>} />
    

    正确的属性是component 而不是element。所以你的Router.js 应该是这样的:

            <Route path="/chat" component={<ChatScreen/>} />
            <Route path="/"     component={<WelcomeScreen/>} />
    

    【讨论】:

    • 这是不正确的。 element 用于 react-router-dom v6 代替组件。 Switch也被Routes取代。根据我之前的说明,呈现欢迎屏幕没有问题,因此这不是问题,也不会引发与道具相关的错误。
    • 啊,没有意识到你在学习教程的同时也在更新 React Router。看起来历史不再通过道具传递。您可以尝试更新this.props.location.pathname。或者换成一个函数式组件,使用‘useNavigate`钩子返回的navigate函数。
    • 我的错,在那种情况下可能应该提到这一点。如果有什么问题,我会多玩这个,然后转回来,但感谢您对此的关注。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2018-08-13
    • 2016-09-01
    • 2016-05-22
    • 2015-09-21
    • 1970-01-01
    • 2017-12-02
    • 2016-05-06
    • 2017-06-10
    相关资源
    最近更新 更多