I am working with React and I am doing a chat app. As you now, everytime there is a new message or any event in the chat screen, that last event/item/message should be focus so the user doesn't have to scroll down in order to look for the new event in the chat. As a regular chat app.
Every event is attached to this.props.chatMessages.
I already accomplished this behavior but only when the user adds a new message. And I need that functionality for everything, sometimes the chat says
New user was added to the chat
or
User XXXXX leaved the chat
so, those are different events, those are informative messages and not regular user messages.
As I mentioned before, every new event is attached to this.props.chatMessages
this is what I did in order to focus in the last message when the user sends a message by himself
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
and in the render method I have something like this
chatForm = <ChatForm onAddMessage={this.addMessage} />;
here is the full function just in case. Where you see <ChatItem .../> is because that is the component to visualize every new event happening in the chat.
class ChatView extends React.Component {
static getStores () {
return [ ChatStore ];
}
static getPropsFromStores () {
return ChatStore.getState();
}
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
render () {
let messages = this.props.chatMessages.map((message) => {
return <ChatItem info={message.info} me={message.me} player={message.player} message={message.message}
onNewEvent={this.focusOnLastMessage} />;
}), chatForm, hr, dealerPlayerMessages, dealerPlayerBox, minusPlusButtons;
if (this.props.mode === 'player') {
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
hr = <hr />;
chatForm = <ChatForm onAddMessage={this.addMessage} />;
dealerPlayerBox = <div>{dealerPlayerMessages}{hr}{chatForm}</div>
}
if (this.props.mode === 'dealer') {
minusPlusButtons = <MinusPlusButtons />
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
dealerPlayerBox = <div> {minusPlusButtons} {dealerPlayerMessages}</div>
}
return <div>
{dealerPlayerBox}
</div>;
}
}
so, what should I do in order to listen to every change in this.props.chatMessages in order to focus on every new item in the chat ?
I'm just guessing here, but let's say a new person joins the chat updating this.props.chatMessages to contain a new message notifying users about this change. This means that the first lifecycle method is going to fire
componentWillReceiveProps (nextProps) {
// do something with new message
}
But you need to scroll the message after this has been painted to the dom, so luckily there's a life cycle method for that too.
componentDidUpdate (prevProps, prevState) {
// dom has been updated with new message, scroll your screen!
this.focusOnLastMessage()
}
Edit: You may need to bind this in your constructor to use this, but I don't remember. Not all lifecycle methods need it.
Lifecycle Methods in docs
Related
I have scoured the internet and found no answers to this on Stack or anywhere for that matter that actually work.
I developed a SPA (PWA). Within the SPA the user can click a button to open a new page that contains a pricelist. This is easy enough. However, from the new pricelist page I want to be able to call a function from the SPA and pass arguments to that function. The function is a module that is imported to the original SPA.
Essentially, I want the pricelist page to be able to pass the partnumber from the pricelist page to the shopping cart in the SPA.
Is this even possible? If so, can you share example or link?
I wrote the following code to handle the communications between the main application and the pricelist page. It works flawlessly. I had to do a bit of testing to figure it all out. I am using the BROADCAST CHANNEL API and the SESSIONID.
// PWA MAIN APPLICATION (index.js)
const BROADCAST_CHANNEL_01 = new BroadcastChannel(sessionStorage.getItem('SESSIONID'))
BROADCAST_CHANNEL_01.onmessage = (event) => {
if (event.data.partnumber) {
ReturnSelectedItem(event.data.partnumber, event.data.model)
BROADCAST_CHANNEL_01.postMessage({
partnumber: event.data.partnumber,
model: event.data.model
})
}
if (event.data === 'RE-OPEN-CHANNEL') {
//// RE-OPENS CHANNEL
BROADCAST_CHANNEL_01.postMessage(`${'CHANNEL_OPENED'}`)
}
if (event.data === 'beforeunload') {
//// CHANNEL WILL RE-OPEN IF PAGE WAS ONLY REFRESHED AND NOT CLOSED
setTimeout(() => {
BROADCAST_CHANNEL_01.postMessage(`${'CHANNEL_OPENED'}`)
}, 1000)
}
}
//// LET PRICELIST PAGE "KNOW" IT CAN NO LONGER ADD TO MAIN APP CART
window.addEventListener('beforeunload', function () {
BROADCAST_CHANNEL_01.postMessage(`${'beforeunload'}`)
})
//PRICELIST.js
const BROADCAST_CHANNEL_01 = new BroadcastChannel(
sessionStorage.getItem('SESSIONID')
)
BROADCAST_CHANNEL_01.onmessage = (event) => {
if (event.data === 'CHANNEL_OPENED') {
sessionStorage.setItem('BROADCAST_CHANNEL', 'OPENED')
}
if (event.data === 'beforeunload') {
sessionStorage.setItem('BROADCAST_CHANNEL', 'CLOSED')
//// CHANNEL WILL RE-OPEN IF PAGE WAS ONLY REFRESHED AND NOT CLOSED
setTimeout(() => {
BROADCAST_CHANNEL_01.postMessage(`${'RE-OPEN-CHANNEL'}`)
}, 1000)
}
}
window.addEventListener('beforeunload', function () {
BROADCAST_CHANNEL_01.postMessage(`${'beforeunload'}`)
})
//PRICELIST.js Event Listener to pass in the partnumber/model to the main application
if (event.target.matches('.quick-add-btn')) {
if (!event.target.classList.contains('added')) {
const PART_NUMBER =
event.target.parentNode.parentNode.childNodes[0].innerText
const MODEL = event.target.parentNode.parentNode.childNodes[2].innerText
if (sessionStorage.getItem('BROADCAST_CHANNEL') === 'OPENED') {
BROADCAST_CHANNEL_01.postMessage({
partnumber: PART_NUMBER,
model: MODEL
})
event.target.innerHTML = '✓' //html check mark
event.target.classList.remove('quick-add-btn')
}
if (
sessionStorage.getItem('BROADCAST_CHANNEL') === 'CLOSED' ||
!sessionStorage.getItem('BROADCAST_CHANNEL')
) {
CallModal(
'To use this feature...\r\nOpen the pricelist from the main application...\r\nUse the "Pricelist" button',
'alert'
)
}
}
return
}
I'm currently working on a project where I want to show a custom Dialogue box with my Own Content ("Save your data into drafts before leaving"). I have tried different methods but can't find a proper way to do it. I explore all the previous questions on StackOverflow but they didn't work properly in my case.
useEffect(() => {
return () => {
window.onbeforeunload = function() {
return "do you want save your data into drafts before leave?";
};
}
},[])
Currently, I've written the above code in Plain JavaScript to do, but it's just showing the dialogue box on tab close and reload while not showing on custom click events to navigate to other pages or window back button.
React can't help me in this because they remove useBlocker, usePrompt from new releases. How can I achieve it?
One way of doing this is :
import { Prompt } from 'react-router'
const MyComponent = () => (
<>
<Prompt
when={shouldBlockNavigation}
message='Do you want ot save data before leave?'
/>
{/* Component JSX */}
</>
)
If wants on page refresh or browser closing then add:
useEffect(() => {
if (shouldBlockNavigation) {
window.onbeforeunload = () => true
} else {
window.onbeforeunload = undefined
}
},[]);
Second way is to use history if using react-router
useEffect(() => {
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want leave the page without saving?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
})
},[]);
useEffect(() => {
const unloadCallback = (event) => {
event.preventDefault();
event.returnValue = "";
return "";
};
window.addEventListener("beforeunload", unloadCallback);
return () => {
window.addEventListener("popstate", confirmation());
window.removeEventListener("beforeunload", unloadCallback);
}
}, []);
I just did it with this code sample (actually I combine two events to show dialogue whenever users leave a page) and it's working fine for me. Thanks to all of you guys ... Especially #DrewReese for the help
i have two components sdk-button and an upper-component
in my sdk-button i have the following simple code:
public render() {
return html`
<button #click=${this._handleClick}>${this.text}</button>
`;
}
_handleClick() {
let click = new Event('click');
this.dispatchEvent(click);
}
This dispatches a click event so in my upper-component i have the following code:
public render() {
return html`
<h1>Hello, ${this.test}!</h1>
<sdk-button #click="${() => { this.changeProperty() }}" text="Click"></sdk-button>
`;
}
changeProperty() {
let randomString = Math.floor(Math.random() * 100).toString();
console.log(randomString)
}
This works however the changeProperty is fired twice. can anyone tell me why this is happening?
I am pretty sure that's because there are two click events: the native one from the button which bubbles up, and the one you dispatch manually. Try to either use a custom event with a different name or remove the dispatch from the sdk-button and use the native one.
I'm new to ReactJS and one thing that I'm working on is Pagination. ReactJS is a great tool to make complex UI views but sometimes the simplest thing is the hardest. I could do it in jQuery but I want to do it using React.
Basically, I would like to have a "Load More" style of pagination where items are appended to the body (like a news feed). In my code, I do an ajax request to get the data, put it in a state variable, and then render the DOM. I have successfully done this with only 1 page of data because I created a new variable for items.
onCallSuccessGetListings: function(data) {
this.setState({ hasSuccess: data.success });
if(data.success !== false) {
this.setState({
hasSuccess: JSON.parse(data).success,
newListings: false
});
if(this.props.hasClickedPagination) {
this.setState({
newListings: JSON.parse(data).result
});
} else {
this.setState({
listings: JSON.parse(data).result
});
}
}
},
Basically, I know when the person clicked on "Load More" and then I use a new variable to store the data. Here's the render function:
render: function() {
var markup = [],
nextMarkup = [];
if(this.state.hasSuccess) {
markup = Object.keys(this.state.listings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
if(this.state.newListings !== false) {
nextMarkup = Object.keys(this.state.newListings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
}
} else {
markup.push(<p key="1">No results</p>)
}
return (
<div className="property__list--grid">
{ markup }
{ nextMarkup }
</div>
);
}
As you can see, I'm duplicating the code and I couldn't find a way to do it the "React" style. I don't want to create 10 variables when the user clicks on "Load More" 10 more times.
If anyone could help, that would be great. I can't think of a solution and it has been haunting me for days.
Many thanks.
This tutorial might help everyone to implement a paginated list in React. It has the list itself and a More button to fetch the next subset of a paginated list from a backend. The backend in tutorial is already provided. I hope it helps anyone who wants to implement such a functionality.
There is no need to create a new state variable per "page of results". It looks to me that you are doing an infinite scroll result set and that the onCallSuccessGetListings lives within your Component.
Instead of creating a new variable for a new page of results, append any new results to the existing variable. e.g.
onCallSuccessGetListings: function(data) {
var jsonData = JSON.parse(data);
this.setState({
hasSuccess: jsonData.success,
listings: jsonData.success
? this.state.listings.concat(jsonData.result)
: this.state.listings
});
},
Make sure you set your initial state to be { listings = [] } (https://facebook.github.io/react/docs/component-specs.html#getinitialstate).
The above code will trigger an update on your component every time the state is set, then you can simply loop over the results. I would also consider updating the way you use the state.hasSuccess flag. I think it should rather be used to indicate if an error occurred. Here is a revised render() method:
render: function() {
var markup = [];
hasSuccess = this.state.hasSuccess,
listings = this.state.listings;
if(!hasSuccess) {
markup.push(<div>An error occurred.</div>);
} else if (listings.length > 0) {
markup = Object.keys(listings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
} else {
markup.push(<p key="1">No results</p>)
}
return (
<div className="property__list--grid">
{ markup }
</div>
);
}
I'm trying to create a simple component with React (I'm a new user), and I have some troubles to show and hide div. I'm using a state to handle a click and change state, which works fine. Problem is when I'm using the back button from the browser, and come back on the main page, I've no clue how to handle state change as there is no interaction with the user.
I tried to use the location context to change state if the URL pathname === "/", but it looks like anti react pattern because I have to force the component to rerender and check the pathname inside the initial state function. Any ideas how to handle this case?
// DIV on the main page
const Div = React.createClass({
/*contextTypes: {
location: React.PropTypes.object
},*/
getInitialState: function() {
console.log("get initial state");
return { hideDiv: false };
},
handleClick(){
this.setState({ hideDiv: true });
},
render() {
console.log(this.state.hideDiv);
let componentDOM;
if(this.state.hideDiv === true){ componentDOM = <div></div>;}
else{
componentDOM = <div id='showHide'>
<form>
<div>
<select>
<option> ... </option>
</select>
</div>
//Link to a second page
<button type='submit' onClick={this.handleClick}> <Link to {'/destination'}>Submit</Link></button>
</form>
</div>;
}
return (componentDOM);
}
});
I would advise against storing the information about whether or not the component with the form is visible in its own state. From your description, it seems to me like this information belongs higher in the hierarchy - the Div component itself is not capable of deciding whether or not it should be visible, as that depends on some context (URL / application phase) unknown to it.
I'd recommend something like this:
var App = React.createClass({
//Data for the form, you might want to keep them in a store
getInitialState(){ return {data: {}}; }
render(){
//Pass data from your routing library to the props of App
if(this.props.routingParams.url === 'form')
return <Div data={this.state.data} onDataChanged={...} />
else
return <Destination data={this.state.data} />
}
});
Plus remove the state and the hiding logic from Div completely.
To do this you should save your state, using localstorage for example, like this:
handleClick: function(e) {
this.setState({hideDiv: true});
var state = this.state; // we need to add hideDiv with new value because setState could not update instantly
state.hideDiv = true;
localStorage.setItem('MyDivComponent', JSON.stringify(state));
}
And then, when a component mount, get default state:
getInitialState: function() {
var state = JSON.parse(localStorage.getItem('MyDivComponent')) || {};
return {
hideDiv: state.hideDiv || false
};
}