I using Syncfusion React Toast with singnalr for showing popup notification to user. but after fetch data from server with signalr and set message state with fetched value, toast content shows initial value which it`s assigned in constructor function.
import React, { Component } from 'react';
import { ToastComponent } from '#syncfusion/ej2-react-notifications';
import * as signalR from '#aspnet/signalr';
class Notification extends ToastComponent {
constructor() {
super(...arguments);
this.position = { X: 'Right', Y: 'Top' }
this.state = { message: '555'}
}
toastCreated() {
this.toastInstance.show({ timeOut: 0 })
}
componentDidMount() {
const notifConn = new signalR.HubConnectionBuilder().withUrl("/notif").build();
notifConn.on("ReceiveMessage",(msg) => {
this.setState({ message: msg });
});
notifConn.start().then(function () {
notifConn.invoke("SendNotification");
}).catch(function (er) {
console.log(er.toString());
});
}
render() {
return (
<div>
<ToastComponent
id='toast_target'
ref={toast => this.toastInstance = toast}
title={this.state.message}//it shows 555!!!
content={this.state.message} //it shows 555!!!
position={this.position}
showCloseButton
created={this.toastCreated = this.toastCreated.bind(this)}
/>
</div>
);
}
}
export default Notification;
Greetings from Syncfusion support.
By default, we couldn’t modify the content dynamically for existing displaying toasts. Dynamically changed content will be shown on notify the next toasts.
So, we suggest you to hide the previous toast and show the next toast after setState.
Please find the below code for your reference.
Chat.js
constructor(props) {
super(props);
this.state = {
message: '',
};
componentDidMount = () => {
const nick = window.prompt('Your name:', 'John');
const hubConnection = new HubConnection('http://localhost:5000/chat');
this.setState({ hubConnection, nick }, () => {
…………..
this.state.hubConnection.on('sendToAll', (nick, receivedMessage) => {
this.setState({ message: receivedMessage }); // Change the toast content using state
this.toastObj.hide(); // You can hide the toast
this.toastObj.show(); // You can show the toast
});
});
};
create = () => {
this.toastObj.show({ timeOut: 0 });
}
<ToastComponent ref={ (toast) => { this.toastObj = toast; } } content = { this.state.message } id = 'toast_default' created = { this.create.bind(this) } > </ToastComponent>
We have created sample for an ASP.NET core signalr with the react toast component.
Sample: https://www.syncfusion.com/downloads/support/directtrac/general/ze/AspNetCoreSignalRToastReact1983063835
Please find the below steps to run the above sample.
Navigate inside of AspNetCoreSignalR_React.Client folder and enter
‘npm install’.
Start the client app by entering ‘npm start’.
Run the AspNetCoreSignalR_React.Server project.
If the above solution doesn’t meet your requirement, kindly send the below details.
Have any reason on using static toasts with dynamic content update?
Regards,
Narayanasamy P.
Related
I have created a simple get request, but I am unsure what to do as it has asked for a key. This is a reduced version of my code:
import React, { Component, useEffect } from 'react';
import firebase from "gatsby-plugin-firebase";
var db = firebase.firestore();
var word1 = db.collection("menue").doc("word1");
class newMenue extends Component {
constructor(props) {
super(props)
this.state = {
wordOne: "",
}
}
render() {
word1.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
this.setState({
wordOne: doc.data()
})
}
})
.catch(err => {
console.log('Error getting document', err);
});
return (
<div>
<h2 >{this.state.wordOne}</h2>
<br/>
<br/>
<h5>4 Slices of grilled Cypriot Halloumi</h5>
</div>
)
}
}
export default newMenue
this is my database:
Document data: {One: "hi"}
This is what the dev tools states.
Error: Objects are not valid as a React child (found: object with keys
{One}). If you meant to render a collection of children, use an array
instead.
What do I do in order to add the key to display the information?
Specifically, I am trying to set up a website for my friend's burger place where he can put in the menu changes on the website himself and I am stuck at this last bit.
You are trying to render an object:
<h2>{this.state.wordOne}</h2> // which is {One: "hi"}
You need to render the property of the object:
<h2>{this.state.wordOne.One}</h2>
Is there a way to cache react component in client side. If a user comes a page say A and then navigate to another page say B, when again he comes back to A I want render should not execute ,no api call should be executed, the page should be served from cache .
You can cache state
let cachedState = null;
class ExampleComponent extend React.Component {
constructor(props) {
super(props);
this.state = cachedState !== null ? cachedState : {
apiData: null
}
}
componentWillUnmount() {
cachedState = this.state;
}
componentDidMount() {
if (this.state.apiData === null) {
this.loadApiData()
.then(apiData => {
this.setState({apiData});
});
}
}
loadApiData = () => {
// code to load apiData
};
}
As long as the input props are not getting changed, you can use React.memo().
(This is not useMemo Hook. Please don't get confused)
const Greeting = React.memo(props => {
console.log("Greeting Comp render");
return <h1>Hi {props.name}!</h1>;
});
Read this article for further clarifications -> https://linguinecode.com/post/prevent-re-renders-react-functional-components-react-memo
I have created a Reactjs component that receives a mapStateToProps function call. Everything works fine except the ajax call using Axios.
The class on a mapStateToProps update needs to call the server and add its payload to the state of the component and update the textarea.
The error I am getting from the console is,
ReactDOMIDOperations.js:47 Uncaught RangeError: Maximum call stack size exceeded
Below is what I have so far. Can anyone show me how to fix this issue?
import React from "react";
import { connect } from "react-redux";
import ApiCalls from "../../../utils/ApiCalls";
const mapStateToProps = state => {
return { passFilePath: state.passFilePath };
};
/**
* This component is a template to display
* widgets of information
*/
class IdeTextEditorClass extends React.Component {
constructor() {
super();
this.state = {
newData: [],
pathData: []
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidUpdate() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
try {
this.state.newData
} catch (err) {
this.setState({ newData: '' });
}
return (
<fieldset>
<input type="text" value={this.state.pathData.data} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
const IdeTextEditor = connect(mapStateToProps)(IdeTextEditorClass);
export default IdeTextEditor;
class IdeTextEditorClass extends React.Component {
constructor() {
super();
/*
based on your original code it seems the default data should be empty string ,as you set them to be empty string when you cannot get data from server.
*/
this.state = {
newData: '',
pathData: ''
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidMount() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
//by default your newData is already empty string. so skip the checking here.
let path = this.state.pathData ? this.state.pathData.data : '';
return (
<fieldset>
<input type="text" value={path} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
Explanation:
The major change is to change componentDidUpdate to componentDidMount.
Putting the data initializing logic in componentDidMount because:
called only once, thus avoiding the endless update loop mentioned in the comments. Also, initialization logic is usually expected here.
this method is called after initial render, so you can at least display something to user during the wait for data (from server). for example, in your render method, you can check newData and if it is not available, display a loading icon. Then React calls componentDidMount, and fetch your data -> update state -> trigger render again -> displays your input / text area using new data fetched from server. Of course, if you don't want to bother showing a loading icon, it is also fine, because your view will probably be updated quickly, when the ajax call returns.
I'm not sure if this is possible due to the way meteor works. I'm trying to figure out how to unsubscribe and subscribe to collections and have the data removed from mini mongo on the client side when the user clicks a button. The problem I have in the example below is that when a user clicks the handleButtonAllCompanies all the data is delivered to the client and then if they click handleButtonTop100 it doesn't resubscribe. So the data on the client side doesn't change. Is it possible to do this?
Path: CompaniesPage.jsx
export default class CompaniesPage extends Component {
constructor(props) {
super(props);
this.handleButtonAllCompanies = this.handleButtonAllCompanies.bind(this);
this.handleButtonTop100 = this.handleButtonTop100.bind(this);
}
handleButtonAllCompanies(event) {
event.preventDefault();
Meteor.subscribe('companiesAll');
}
handleButtonTop100(event) {
event.preventDefault();
Meteor.subscribe('companiesTop100');
}
render() {
// console.log(1, this.props.companiesASX);
return (
<div>
<Button color="primary" onClick={this.handleButtonAllCompanies}>All</Button>
<Button color="primary" onClick={this.handleButtonTop100}>Top 100</Button>
</div>
);
}
}
Path: Publish.js
Meteor.publish('admin.handleButtonAllCompanies', function() {
return CompaniesASX.find({});
});
Meteor.publish('admin.handleButtonTop100', function() {
return CompaniesASX.find({}, { limit: 100});
});
This is definitely possible, but the way to do that is to fix your publication. You want to think MVC, i.e., separate as much as possible the data and mode from the way you are going to present it. This means that you should not maintain two publications of the same collection, for two specific purposes. Rather you want to reuse the same publication, but just change the parameters as needed.
Meteor.publish('companies', function(limit) {
if (limit) {
return CompaniesASX.find({}, { limit });
} else {
return CompaniesASX.find({});
}
});
Then you can define your button handlers as:
handleButtonAllCompanies(event) {
event.preventDefault();
Meteor.subscribe('companies');
}
handleButtonTop100(event) {
event.preventDefault();
Meteor.subscribe('companies', 100);
}
This way you are changing an existing subscription, rather than creating a new one.
I'm not yet super familiar with react in meteor. But in blaze you wouldn't even need to re-subscribe. You would just provide a reactive variable as the subscription parameter and whenever that would change, meteor would update the subscription reactively. The same may be possible in react, but I'm not sure how.
Ok, first thanks to #Christian Fritz, his suggestion got me onto the right track. I hope this helps someone else.
I didn't realise that subscriptions should be controlled within the Container component, not in the page component. By using Session.set and Session.get I'm able to update the Container component which updates the subscription.
This works (if there is a better or more meteor way please post) and I hope this helps others if they come across a similar problem.
Path CompaniesContainer.js
export default UploadCSVFileContainer = withTracker(({ match }) => {
const limit = Session.get('limit');
const companiesHandle = Meteor.subscribe('companies', limit);
const companiesLoading = !companiesHandle.ready();
const companies = Companies.find().fetch();
const companiesExists = !companiesLoading && !!companies;
return {
companiesLoading,
companies,
companiesExists,
showCompanies: companiesExists ? Companies.find().fetch() : []
};
})(UploadCSVFilePage);
Path: CompaniesPage.jsx
export default class CompaniesPage extends Component {
constructor(props) {
super(props);
this.handleButtonAllCompanies = this.handleButtonAllCompanies.bind(this);
this.handleButtonTop100 = this.handleButtonTop100.bind(this);
}
handleButtonAllCompanies(event) {
event.preventDefault();
// mongodb limit value of 0 is equivalent to setting no limit
Session.set('limit', 0)
}
handleButtonTop100(event) {
event.preventDefault();
Session.set('limit', 100)
}
render() {
return (
<div>
<Button color="primary" onClick={this.handleButtonAllCompanies}>All</Button>
<Button color="primary" onClick={this.handleButtonTop100}>Top 100</Button>
</div>
);
}
}
Path: Publish.js
Meteor.publish('companies', function() {
if (limit || limit === 0) {
return Companies.find({}, { limit: limit });
}
});
Path CompaniesContainer.js
export default CompaniesContainer = withTracker(() => {
let companiesHandle; // or fire subscribe here if you want the data to be loaded initially
const getCompanies = (limit) => {
companiesHandle = Meteor.subscribe('companies', limit);
}
return {
getCompanies,
companiesLoading: !companiesHandle.ready(),
companies: Companies.find().fetch(),
};
})(CompaniesPage);
Path: CompaniesPage.jsx
export default class CompaniesPage extends Component {
constructor(props) {
super(props);
this.handleButtonAllCompanies = this.handleButtonAllCompanies.bind(this);
this.handleButtonTop100 = this.handleButtonTop100.bind(this);
}
getCompanies(limit) {
this.props.getCompanies(limit);
}
handleButtonAllCompanies(event) {
event.preventDefault();
// mongodb limit value of 0 is equivalent to setting no limit
this.getCompanies(0);
}
handleButtonTop100(event) {
event.preventDefault();
this.getCompanies(100);
}
render() {
return (
<div>
<Button color="primary" onClick={this.handleButtonAllCompanies}>All</Button>
<Button color="primary" onClick={this.handleButtonTop100}>Top 100</Button>
</div>
);
}
}
Path: Publish.js
Meteor.publish('companies', function(limit) {
if (!limit) { limit = 0; }
return Companies.find({}, { limit: limit });
});
I have been working on a chat application using react and websockets, My problem is the method
componetWillUnmount()
doesn't get called when the state changes and component re-renders.
I have been trying to add 'li' elements to my chatArea component for every new message coming in a chat, and as soon as I am selecting another chat, I want to remove all those 'li' elements that were rendered in previous chat, for that I have tried 2 things, one to remove all child of or I am changing the state. But componentWillUnmount is not getting called. And i am not able to remove li elements.
Below is my code
import React from 'react'
export default class ChatArea extends React.Component {
constructor (props) {
super(props)
this.state = {
currentUser: this.props.currentUser,
selectedUser: this.props.selectedUser,
messages: []
}
this.handleMessage = this.handleMessage.bind(this)
}
handleMessage (obj) {
let messages = this.state.messages
messages.push(obj)
this.setState({
messages: messages
})
}
componentWillMount () {
window.socket.on('show message', obj => {
this.handleMessage(obj)
})
}
componentDidMount () {
window.socket.emit('join', {
sender: this.state.currentUser,
receiver: this.state.selectedUser
})
}
componentWillUnmount () {
console.log('something')
const chatList = this.refs.chatList
while (chatList.hasChildNodes()) {
console.log('removing children', chatList.lastChild)
chatList.removeChild(chatList.lastChild)
}
orrrrrrrrrrrrrr
this.setState({
messages: []
})
}
render () {
console.log('chatARea state', this.state)
let messages = this.state.messages
let i = 0
return (
<div className='row chat-area'>
<ul className='col m12' ref='chatList'>
{messages.map(msg => <li key={i++}>{msg.sentBy.firstname + ': ' + msg.message}</li>)}
</ul>
</div>
)
}
}
module.exports = ChatArea
I found out the answer of my own question, the state of the component was not getting changed, so the method componentWillMount() was not getting called.
Thanks everyone for the help