scrollIntoView does not work on first load and after each refresh - reactjs

I am seeing a very strange behavior in my chat. Once the chat is opened, the scroll moves only a tiny bit down only when there are images in the chat. When there is only text, it goes all the way down. Also, if I close the chat and open it again, the scroll goes all the way down regardless of the content. However, If I refresh the page, the scroll returns to its weird behavior. I am puzzled as to why this is happening. Here's my code:
Here's how the chat starts:
startChat () {
document.getElementById("myForm").style.display = "block";
const ref = firebase.firestore().collection('Chats').doc(this.state.uid).collection('Messages');
const query = ref.orderBy('timestamp', 'desc').limit(10)
this.unsubFromMessages = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
firebase.firestore().collection('Chats').doc(this.state.uid).
set({
name: this.state.displayName,
uid: this.state.uid,
email: this.state.email
}).then(console.log("info saved"))
.catch((error) => {
console.log("Error saving info to document: ", error);
});
}
snapshot.docChanges().reverse().forEach((change) => {
if (change.type === 'removed') {
console.log(change.doc.data().content)
}
else if (change.type === 'added') {
this.setState(state => {
const messages = [...state.messages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
else if (change.type === 'modified') {
const filteredMessages = this.state.messages.filter(message => message.body.allowed === "yes")
this.setState(state => {
const messages = [...filteredMessages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
});
}, (error) => {console.log(error)});
}
Here's the scroll function:
scrollToBottom = () => {
this.myRef.current.scrollIntoView({ behavior: "smooth" });
}
Here's the JSX of the chat:
<div className="form-popup" id="myForm">
<form className="form-container" onSubmit={this.chatFormSubmit}>
<h1>Chat</h1>
<label htmlFor="msg"><b>Message</b></label>
<div className="chatArea" id='messages'>
{
this.state.messages.map((message, index) => {
return message.body.uid === this.state.uid && !message.body.imageUrl
?
<p className="message-sent" key={index}>{message.body.content}</p>
:
message.body.uid === this.state.uid && message.body.imageUrl
?
<img src={message.body.imageUrl} className="message-sent" key={index}></img>
:
<p className="message-received" key={index}>{message.body.content}</p>
})
}
<div style={{ float:"left", clear: "both" }}
ref={this.myRef}>
</div>
</div>
And if the functions for closing and submitting messages to the chat are of any use, here they are:
closeForm() {
document.getElementById("myForm").style.display = "none";
this.setState({messages: []})
this.unsubFromMessages();
}
chatFormSubmit(e) {
e.preventDefault();
this.setState({ writeError: null });
firebase.firestore()
.collection('Chats')
.doc(this.state.uid)
.collection('Messages')
.doc()
.set({
docId: this.state.docId,
content: this.chatArea.current.value,
allowed: "yes",
timestamp: new Date(),
uid: this.state.uid,
name: this.state.displayName,
email: this.state.email
}, { merge: true })
.catch((error) => {
this.setState({ writeError: error.message });
})
.then(this.chatArea.current.value = '')
}

Again, I figured it out myself. Instead of calling "this.scrollToBottom()" in setTimeout, I should have simply passed it like this setTimeout( this.scrollToBottom, 2000). That is why setTimeout was not working and the scroll stopped half way. Credit goes to Felix Kling's comment in ReactJS: setTimeout() not working?.

Related

How to change "Request to join" button text to "Join Meeting" in iframe

I'm new to daily-co integration in react js. Can you please suggest how to change
"Request to Join" text to "Join Meeting". Thank in advance.
At present in Iframe all content is coming. Can any one please suggest how to change
the "Request to Join" text to "Join Meeting".
My Observations:
One api is calling at the time of page is loaded:
https://b.daily.co/call-ui/16c545a8520b661e39dc13c62b335ffea4cb3651/locales/en/translation.js
{ ....
"haircheck": {
....
"setup": {
"requestToJoin": "Request to join",
"title": "Are you ready to join?",
}
},
}
//React Class component:
import React from 'react';
import DailyIframe from '#daily-co/daily-js';
import Cookies from 'universal-cookie';
import axios from '../../util/axios';
import util from '../../util/util';
const cookies = new Cookies();
class VideoCallFrame2 extends React.Component {
constructor(props) {
super(props);
this.iframeRef = React.createRef();
this.state = {
authorizationToken: 'Bearer ------------',
roomName: '',
room: null,
token: null,
rooms: [],
roomUrlWithToken: null,
isVideoHidden: false,
joinedObject: null,
status: '',
askedQuestions: [],
};
}
componentDidMount() {
this.daily = DailyIframe.wrap(
this.iframeRef.current,
{
showLeaveButton: true,
});
this.setState({
...this.state,
roomUrlWithToken: this.props.meetingRoomUrl
});
this.startRoom();
let temp = this.daily.meetingState();
this.setState({ status: temp });
this.get_candidate_position();
}
get_candidate_position = (e) => {
this.setState({
positionDetails: response.data.candidate[0]
})
}
onHandleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
joinMeetingEvents = () => {
// Meeting related events
this.daily.on('loading', () => { console.log('Loading......') })
this.daily.on('loaded', () => { console.log('Loaded......') })
this.daily.on('joining-meeting', () => { console.log('Joining......') })
this.daily.on('joined-meeting', () => {
console.log('Joined......')
})
this.daily.on('app-message', (e) => {
console.log('app-messageapp-message app-message>>>>>>> ', e)
})
this.daily.on('left-meeting', (e) => {
console.log('Left Meeting......', e)
this.props.history.push('/thankyou')
})
this.daily.on('participant-joined', (e) => {
console.log('Partcipand Joined......', e);
this.setState({
...this.state,
isVideoHidden: true
})
if (this.state.joinedObject.user_id == '') {
}
})
this.daily.on('error', (e) => {
console.log('ERROR......', e)
})
}
leftMeeeting = () => {
this.daily.leave();
this.daily.destroy();
}
startRoom = async () => {
let res = await this.daily.join({
url: this.props.meetingRoomUrl
})
this.setState({
...this.state,
joinedObject: null
})
this.daily.on('loading', () => { console.log('Loading......') })
this.daily.on('loaded', () => { console.log('Loaded......') })
this.daily.on('joining-meeting', () => { console.log('joining-meeting......') })
this.daily.on('joined-meeting', () => {
console.log('Joined meeting......');
})
this.daily.on('joined-meeting', () => {
console.log('Joined meeting......');
})
this.daily.on('meeting-session-updated', () => {
console.log('meeting-session-updated......');
});
this.daily.on('access-state-updated', (evt) => {
console.log('access-state-updated......', evt);
if (evt.access.level == 'lobby') {
//Some code
}
});
this.daily.on('participant-joining', () => { console.log('participant-joining') })
this.daily.on('left-meeting', (e) => {
this.props.history.push('/thankyouPage');
});
this.daily.on("app-message", (e) => {
let Arr = this.state.askedQuestions;
if (
e &&
e.data &&
e.data.message &&
e.data.message.endInterview == "end") {
this.leftMeeeting();
}
});
this.daily.on('participant-joined', (e) => {
console.log('Partcipand Joined......', e);
setTimeout(() => {
this.daily.sendAppMessage({ message: { intervieweraskedQuestions: this.state.askedQuestions } }, '*');
}, 3000)
})
this.daily.on('error', (e) => {
console.log('ERROR......', e)
})
}
render() {
return (
<div className="" style={{ height: '450px' }}>
<iframe className="Video-Frame video-call-frame"
title="video call iframe"
ref={this.iframeRef}
allow="camera; microphone; fullscreen; display-capture"
></iframe>
</div>
)
}
}
export default VideoCallFrame2;
I work at Daily. :) "Request to join" is shown for private rooms with "knocking" enabled. We use this language because clicking the button will alert the room owner to let you in, so you do need to ask to join -- you can't just go straight in.
This can be turned off though. If you make the room public, it will say "Join meeting" instead of "Request to join" because anyone can join. Alternatively, you can make a meeting token for anyone trying to enter a private room so they don't need to ask to join. (In this case, the button text would also be updated to "Join meeting").
More generally, you can't update button text to something custom in the Daily Prebuilt UI, but you can build your on custom UI with our APIs. That's probably too much effort just to update one button, though. :)
I have not used daily.co before but I did a little digging and confirmed my suspicions: As far as I can tell, this is not possible.
In order to for a page to edit the contents of an iFrame, the frame must be on the same origin as its parent page, as per the Same Origin Policy.
Your page is on the origin http://localhost:3001, while the frame is on an origin owned by Daily, e.g. https://server.daily.co.
This policy exists for security reasons, an example is imagine some website https://attacker.com with a frame to https://bankaccount.com, without this policy the attacker could change a button on the frame from "Click to send all money to attacker" to "Click to receive your $1 million reward!"
The only method I have found that may be plausible after doing a couple searches for "origin", "host", etc. on docs.daily.co is this reference page for "Daily's Video Component System (VCS)", but from what I can tell this cannot solve the problem as this only allows you to add an overlay to the video call, not the frame itself.

this.setState isn't making changes in state

I am using functions that change a value in a nested object in the state :
an I am calling those functions in a button , they are executed when I click on that button , but one of those functions doesn't make changes to the state
This is the state :
state = {
data: {
attributesLength: this.props.product.attributes.length,
modalMessage: "",
isOpen: false,
},
};
and these are the functions :
addToCart = (id) => {
let data = { ...this.state.data };
if (Object.keys(this.state).length === 1) {
data.modalMessage = "Please, select product attributes";
this.setState({ data});
return;
}
if (
Object.keys(this.state).length - 1 ===
this.state.data.attributesLength
) {
const attributes = Object.entries(this.state).filter(
([key, value]) => key !== "data"
);
if (this.props.cartProducts.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
const product = this.props.cartProducts.filter((item) => item.id === id);
if (product.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
if (product.length !== 0) {
this.props.changeQuantity({ id: id, case: "increase" });
data.modalMessage = "Quantity increased !";
this.setState({ data });
return;
}
if (this.state.data.attributesLength === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
} else {
data.modalMessage = 'please, select "ALL" product attributes!';
this.setState({ data });
}
};
changeModalBoolean = () => {
let data = { ...this.state.data };
data.isOpen = !data.isOpen;
this.setState({ data });
};
and this is where I am calling functions :
<button
className={product.inStock ? null : "disabled"}
disabled={product.inStock ? false : true}
onClick={() => {
this.addToCart(product.id);
this.changeModalBoolean();
}}
>
{product.inStock ? "add to cart" : "out of stock"}
</button>
NOTE
changeModalBoolean function works and change state isOpen value,
this.addToCart(product.id);
this.changeModalBoolean();
This code run synchronously one after the other. In every function, you create a copy of previous state let data = { ...this.state.data };
so the this.changeModalBoolean(); just replace state which you set in this.addToCart(product.id); to fix this problem, use this.setState((state) => /*modify state*/)
changeModalBoolean = () => {
this.setState((state) => {
let data = { ...state.data };
data.isOpen = !data.isOpen;
return { data };
})
};
or modify the same object in both functions

Crashing while rendering "# with random text" in draftjs

I am using Draftjs and Draftjs mention plugin. When there is a suggestion for mention name it renders fine but if suggestion doesn't exist and I use # followed by random text it crashes. Can anyone help me out with this. Will be very grateful. Thank you.
this.mentionMembersPlugin = createMentionPlugin({
entityMutability: "IMMUTABLE",
positionSuggestions,
mentionTrigger: "#",
mentionPrefix: "",
supportWhitespace: true
});
onChangeEditor = editorState => {
this.setState({ emptyField: false });
this.setState({ editorState });
};
onSearchMemberChange = ({ value }) => {
this.setState({
suggestionMembers: defaultSuggestionsFilter(
value,
this.state.mentionMembers
)
});
};
handleKeyCommand(command: string): DraftHandleValue {
if (command === "save_teamsync") {
// Perform a request to save your contents, set
// a new `editorState`, etc.
this.add_taskComment();
return "handled";
}
return "not-handled";
}
add_taskComment() {
let newData = convertToRaw(this.state.editorState.getCurrentContent());
let checkText = newData.blocks[0].text;
this.setState({ clicked: true });
var r = JSON.stringify(
convertToRaw(this.state.editorState.getCurrentContent())
);
//e.preventDefault();
if (checkText.trim().length !== 0) {
if (this.isValid(r)) {
var data = {};
data.text = r;
data.mentionMembers = r.entityMap;
this.props
.addTaskComment(this.props.task._id, this.showTags, data)
.then(res => {
if (res.data.success) {
const editorState = EditorState.push(
this.state.editorState,
ContentState.createFromText("")
);
this.setState({
editorState: EditorState.moveFocusToEnd(editorState)
});
//this.setState({ editorState: EditorState.moveFocusToStart(EditorState.createEmpty()), clicked:false });
this.lastComment.scrollIntoView({ behavior: "smooth" });
}
});
}
} else {
this.setState({ isCommentEmpty: true });
}
}
<Editor
blockStyleFn={"myBlockStyleFn"}
editorState={this.state.editorState}
onChange={this.onChangeEditor}
plugins={this.plugins}
handleKeyCommand={this.handleKeyCommand}
keyBindingFn={this.myKeyBindingFn}
placeholder="Write a comment"
ref={element => {
this.editor = element;
}}
/>
<MentionMembersSuggestions
onSearchChange={this.onSearchMemberChange}
suggestions={this.state.suggestionMembers}
/>
This are all the code that are being used to render the comments.
This is the error I am getting "Unhandled Rejection (TypeError): this.props.getEditorState is not a function".

ReactJS: reloading/re-rendering a component on click

I have a component called "categories", where I have 2 buttons active and inactive. Each button calls an api to either activates or deactivates the selected record. However, the button part and the api part are running completely fine and getting proper response after getting the task done. But, I need the "categories" component to reload/re-render after the api response has returned the success code.
I googled for the solution but the answers I got were little confusing for me because I am currently learning the ReactJS.
Here is my code:
...
setInactive(e)
{
Axios
.get("/api/category/deactivate/" + $(e.currentTarget).data('token'))
.then((response) => {
if (response.data.success == true)
{
this.setState({
state: this.state
});
}
});
}
setActive(e)
{
Axios
.get("/api/category/reactivate/" + $(e.currentTarget).data('token'))
.then(response => {
if (response.data.success == true)
{
this.setState({
state: this.state
});
}
});
}
render()
{
...
(category.status == "Y")
? <button data-token={category.token} className="lightRed" onClick={this.setInactive}>Inactive</button>
: <button data-token={category.token} className="green" onClick={this.setActive}>Active</button>
...
}
Also can this code be reduced/optimized..?
I figured out the solution (I got an idea from another answer, but unfortunately I lost that link. I'll paste it if I find it again).
Here is my solution:
...
componentWillMount()
{
this.fetchData();
}
fetchData()
{
Axios
.get("/api/categories")
.then(response => this.setPagination(response.data));
}
setStatus(token, status)
{
Axios
.get("/api/category/status/" + token + "/" + status)
.then((response) => {
if (response.data.success == true)
{
this.setState({
message: <Alerts alertClass="alert-success">
Category status has been modified successfully.
</Alerts>
});
this.fetchData();
}
else
{
this.setState({
message: <Alerts alertClass="alert-danger">
{response.data.message}
</Alerts>
});
}
});
}
setPagination(response)
{
this.setState({
categories: response.data,
totalItemsCount: response.total,
itemsCountPerPage: response.per_page,
activePage: response.current_page,
paginationIndex: ((response.current_page - 1) > 0)
? (((response.current_page - 1) * response.per_page) + 1)
: 1
});
}
pagination(pageNumber)
{
Axios
.get("/api/categories?page=" + pageNumber)
.then(response => this.setPagination(response.data));
this.setState({
activePage: pageNumber
});
}
render()
{
return (
...
{
(category.status == "Y")
? <button className="buttons tiny lightRed" onClick={() =>
this.setStatus(category.token, 'N')
}>Inactive</button>
: <button className="buttons tiny green" onClick={() =>
this.setStatus(category.token, 'Y')
}>Active</button>
}
...
)
}
I hope this may come in handy for someone. However, if there is any room for improvement then please let me know. :)

Prevent Facebook login from remounting the app

There seems to be an issue with my implementation of the Facebook login for react-native
When I click on "Log in with the Facebook app" it opens the facebook app, then when I click "Continue" it reopens my app but it performs a remount. Part of my logic is to open another modal after that for some extra information from the user but calling this.setState() in the callback from the facebook login gives a `Can't call setState (or forceUpdate) on an unmounted component. And I can't seem to figure out how to get around this.
facebookLogin = async () => {
let result;
try {
this.setState({
loggingInWithFacebook: true
})
LoginManager.setLoginBehavior('native');
result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
} catch (nativeError) {
this.setState({
loggingInWithFacebook: false
})
this.props.dispatch(openSnackBar({
message: "There was an error opening facebook to login"
}))
}
if (result.isCancelled) {
this.setState({
loggingInWithFacebook: false
})
} else {
this.FBGraphRequest('first_name, last_name, id, email', this.FBLoginCallback);
}
}
FBGraphRequest = async (fields, callback) => {
const accessData = await AccessToken.getCurrentAccessToken();
const infoRequest = new GraphRequest('/me', {
accessToken: accessData.accessToken,
parameters: {
fields: {
string: fields
}
}
}, callback);
new GraphRequestManager().addRequest(infoRequest).start();
}
FBLoginCallback = async (error, result) => {
if (error) {
this.setState({
loggingInWithFacebook: false
});
} else {
const {id, picture, ...rest} = result
this._handleSocialLogin({...rest, facebook: id})
}
}
_handleSocialLogin = (data) => {
this.props.dispatch(socialLogin(data)).then(res => {
this.props.dispatch(getGroups()).then(res => {
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
AsyncStorage.getItem('onBoarded').then((res)=> {
if (res === 'true') {
this.props.navigation.navigate("Map")
} else {
this.props.navigation.navigate("OnBoarding")
}
})
}).catch(err => console.log(err.response.body))
}).catch(err => {
if (err.err === 'not found') {
this.setState({
enterPhoneModalVisible: true,
socialData: data
})
} else {
this.props.dispatch(
openSnackBar({
message: "There was an error logging in, please try again"
})
);
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
}
})
}
EDIT:
My Appdelegate looks like this
#import <FBSDKCoreKit/FBSDKCoreKit.h>
In my didFinishLaunchingWithOptions
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
BOOL handledFB = [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
];
BOOL handledRCT = [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
return handledFB || handledRCT;
EDIT 2:
I seem to have gotten it to work by changing my openUrl to the following
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
] || [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
Any idea why this works?
EDIT 3:
I'm still getting a Login Failed error randomly and there doesn't seem to be any definitive way to cause this to happen it just happens sometimes and then trying to login again right after it usually goes through

Resources