How to show messages in a chat like manner - reactjs

I got an array of chat messages from an API call & I mapped it to a component (shown in code below). In that component I rendered the messages according to a condition set. But .map method is just replacing the previous message with a new one. I want to .concat() these messages & show them one by one like in a chat.
ChatBox Component:
this.state = {
messages: [] }
getNewMessages() {
//api called & got response
this.setState({
messages: parsedResponse });
}
render() {
return(<>
{messages.map(messages => (
<NewMessages data={messages} />))}</>)
}
NewMessages Component: -
`this.state = { message: this.props.data.message }`
`return(<p>{message}</p>)`
How can I concat this array so that I can get all the messages instead of only last one.

Spread operator can help you with it.
getNewMessages() {
//api called & got response
this.setState({
messages: [...this.state.messages, ...parsedResponse]
});
}
And just render the message itself.
render() {
return(
<>
{this.state.messages.map(messages => (<p>{message}</p>))}
</>
)
}

render() {
return messages.forEach(m => `<div>${m}</div>`);
}

Related

How to pass parameter to a onClick function in ReactJS

in my application, I defined a function click_button_response(param1) which writes the param1 to a DynamoDB table.
Then my app subscribes to an IoT Topic. It will receive a real-time message from a topic. When the button is clicked, the click_button_response(param1) function, passes the url as param1. I hope url can be saved in DynamoDB. However, it gave me the error message:
"Error Error: Pass options.removeUndefinedValues=true to remove undefined values from map/array/set."
I replaced the parameter with a string and the error went away, so I am confident that I passed the parameter in a wrong way. Thank you!
import putItemInDynamoDB from functions.js
const click_button_response = async (param1) => {
putItemInDynamoDB(param1)
}
class Sensors extends React.Component{
constructor(props) {
super(props);
this.state = {
sensorMsg: '{"null": 0}'
};
}
componentDidMount(){
Amplify.PubSub.subscribe(TOPIC).subscribe({
next: data => {
try{
this.setState({ sensorMsg: data.value });
}
catch (error){
console.log("Error, are you sending the correct data?");
}
},
error: error => console.error(error),
close: () => console.log('Done'),
});
}
render(){
const { sensorMsg } = this.state;
const url = sensorMsg['param1'];
return(
<div className="Sensor">
<button onClick={() => click_button_response({url})}>Button1</button>
</div>
)
}
}
if click_button_response expects param1 to be a string you are currently passing it an object. () => click_button_response({url}) when we enclose url in curly braces it actually gets converted into object { url: 'actual url value' }. Change the onClick handler function to this () => click_button_response(url). Also sensorMsg['param1'] is the output of this the expected url string?

function returning data but not showing

I have this component
const SummaryBar = props => {
const { MainObject} = props;
const localGetUserFromID = userID => {
getEmailFromId(userID).then(results => {
return results.data.Title; //Comment: This one returning friendly name
});
};
return (<span>Hello {localGetUserFromID(MainObject.AuthorId)}</span>)
}
but when I render it somehow the its only showing Hello and not the output I am getting from my localGetUserFromID function. Am I doing wrong? Note the AuthorId is being pass to an API and the MainObject came from the App Level,
FYI when I try to debug it using dev tools the function is retuning the text I am look for.
localGetUserFromID() returns nothing, that is, undefined, and that's why you see Hello only.
And because localGetUserFromID() makes an asynchronous call to get an email from user ID, it doesn't have to be in render() method. Now this component is defined as a state-less component, but you can re-define it as a stateful component, call the getEmailFromId() in componentDidMount() life-cycle method, and use a return value as an internal state.
Then you can show a value of the internal state after Hello.
class SummaryBar extends Component {
// Skipping prop type definition.
constructor(props) {
super(props)
this.state = {
username: '',
}
}
componentDidMount() {
const { MainObject: { AuthorId } } = this.props
getEmailFromId(AuthorId).then((results) => {
this.setState({
username: results.data.title,
})
})
}
render() {
const { username } = this.state
return (
<span>
Hello { username }
</span>
)
}
}
When things run when debugging but not when running and you are using promises as you are, the 99% of the times is because promises hasn't been resolved when you print.
localGetUserFromID indeed returns a promise that resolves to the friendly name.
You can just prepend await to localGetUserFromID(MainObject.AuthorId) and rewrite you return as this:
return (<span>Hello {await localGetUserFromID(MainObject.AuthorId)}</span>)

When mapStateToProps in create create a new axios call

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.

React component will mount is not getting called

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

Hold the component's rendering until the store has finished hydration

I'm fetching my initial data like so:
export function populate (dispatch) {
let posts = []
dispatch(requestNews())
fetch(someEndPoint)
.then(payload => payload.json())
.then(items => {
//some fetching logic that populates posts list above
})
.then(() => { dispatch(receiveNews(posts)) })
.catch(err => {
console.log(err)
})
}
function request () {
return {
type: REQUEST_NEWS,
payload: {
populating: true
}
}
}
function receive (posts) {
return {
type: RECEIVE_NEWS,
payload: {
populating: false,
posts
}
}
}
As you can see above I'm setting the store with a field called populating which starts as false and changes to true when the 'request' is dispatched and then back to false when 'received' is dispatched.
Then my component looks something like the following:
import { populateNews } from '../modules/news'
class News extends React.Component {
constructor (props) {
super(props)
//this.mapPosts = this.mapPosts.bind(this)
}
componentWillMount () {
populateNews(this.props.dispatch)
}
render () {
if (!this.props.news.populating) {
return (
<div>
{this.props.news.posts[0].title}
</div>
)
} else {
return (
<div>loading</div>
)
}
}
}
On initial load render is being called before the store is populated with the fetched posts even though my populate switch changes back and forth as expected.
I've tried dealing with it using a local state on the component, so it's constructor has: this.state = {populating: false} and then the action creator changes that, but got the same result.
So at the moment my solution is to instead check if the state has a slice called 'posts' which is being created after the content is fetched and if it does to render it. like so:
render () {
return (
<div>
{this.props.news.posts ? <div>{this.props.news.posts[0].title}</div> : null }
</div>
)
}
This of course just renders the component and then renders it again after the store is updated with the posts, and is not an optimal solution like waiting with the render until the fetch is completed and the store is populated.
There's a long discussion about it here:
https://github.com/reactjs/react-redux/issues/210
How can I delay or better put condition the render itself?
Hmm, you might want to change your if statement. Unless your store is initializing populating to true, it will be undefined on the initial load and will pass your if (!this.props.news.populating) validation which will try to render the post title. Change your condition to look for a truthy value rather than a falsey value and you should have more control over it.

Resources