[英]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
.如果
ChatScreen
和WindowScreen
是功能组件,那么您可以直接使用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
。
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.