简体   繁体   English

ReactJS 和 Twilio - 类型错误:this.props.history 未定义

[英]ReactJS and Twilio - TypeError: this.props.history not defined

Learning React for the first time, and opted to use Twilio's chat app demo to get a jump start.第一次学习 React,并选择使用 Twilio 的聊天应用演示来快速入门。

I can render the welcome screen without issue, but not routed to the chat screen/room when I login.我可以毫无问题地呈现欢迎屏幕,但在我登录时不会路由到聊天屏幕/房间。 Including link to demo, code snippets, notes and more below.包括演示链接、代码片段、注释等。

Anyone out there see what's going on here and can advise?那里的任何人都看到这里发生了什么并可以提供建议? I've fixed a few issues so far that came up due to updates since the demo was posted (change Switch to Routes , etc.), but haven't been able to get past this TypeError .到目前为止,我已经修复了一些由于发布演示后的更新而出现的问题(将Switch更改为Routes等),但无法通过此TypeError Any and all help is welcome and TIA!欢迎任何和所有帮助和TIA!

Link to Twilio Demo: Twilio Programmable Chat App Demo链接到 Twilio 演示: Twilio 可编程聊天应用演示

The error is raised in the login() function, at the line: this.props.history.push('chat', { email, room });该错误在 login() function 中引发,位于以下行: this.props.history.push('chat', { email, room }); and the error reads Uncaught TypeError: this.props.history is undefined .并且错误显示为Uncaught TypeError: this.props.history is undefined

As an aside, I have attempted to import the withRouter method from react-router-dom but the method is not exported from react-router-dom and all information I am finding online about this method points to an older version of react-router-dom than what I am working with, so this is not a workable solution.顺便说一句,我试图从react-router-dom导入withRouter方法,但该方法并未从react-router-dom导出,并且我在网上找到的有关此方法的所有信息都指向较旧版本的react-router-dom比我正在使用的,所以这不是一个可行的解决方案。 I've also tried to apply .bind(this) on the onClick that calls login() , but this did not work either.我也尝试在调用login()onClick上应用.bind(this) ,但这也不起作用。

WelcomeScreen.js 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 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路由器.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;

If ChatScreen and WindowScreen are functional Components , then you can directly use useNavigate instead of useHistory .如果ChatScreenWindowScreen功能组件,那么您可以直接使用useNavigate而不是useHistory But can't find anything specific for class component.但找不到任何特定于 class 组件的内容。 It is given in migration from v5 section of the documentation.它在从文档的 v5 部分迁移时给出。

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

You can search for useNavigate .您可以搜索useNavigate


Solution ( workaround )解决方案(解决方法

You can create a Function Wrapper Component around your class components and in that component, you can pass props like您可以在 class Wrapper Component周围创建Function包装器组件,并且在该组件中,您可以传递如下道具

  • navigate from useNavigate

I have taken an idea from HOC components in react.我从反应中的HOC components中获得了一个想法。 Here is an example of what I tried and have worked.这是我尝试和工作的一个例子。 https://codesandbox.io/s/snowy-moon-30br5?file=/src/Comp.js https://codesandbox.io/s/snowy-moon-30br5?file=/src/Comp.js

This can work till we don't find a legitimate method of doing the same.这可以工作,直到我们找不到合法的方法来做同样的事情。

you can click on Click button in route /1 and it successfully navigates to route /2您可以单击路由/1中的单击按钮,它成功导航到路由/2

Note: See the console for the passed props in the Sandox's console注意:查看Sandox 控制台中传递的道具的控制台

Twilio developer evangelist here. Twilio 开发人员布道师在这里。

In your Router.js file you have:在您的Router.js文件中,您有:

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

The correct attribute is component not element .正确的属性是component而不是element So your Router.js should look like this:所以你的Router.js应该是这样的:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM