Show message as sent/received in other users window with react - javascript

I have a prototype chat app in react that I can currently show sent messages in both viewers windows (Agent and User). However, I'm having trouble figuring out how to only show a sent message as sent in one window and received in the other and vice versa. So if the Agent sends a message, it will show with their name and the message in both windows. I'm thinking I need to pass the "author" into each "User messages" and "Agent messages" within App.js, but this doesn't work.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
messages: []
}
this.handleNewMessage = this.handleNewMessage.bind(this);
}
static propTypes = {
messages: PropTypes.arrayOf(PropTypes.object)
}
handleNewMessage = (text) => {
this.setState({
messages: [...this.state.messages, { me: true, author: "Me", body: text},{ me: true, author: "Agent", body: text}]
})
}
render() {
return (
<div className="App">
<div className="agentWindow">
<Agent messages={this.state.messages} handleNewMessage={this.handleNewMessage} />
</div>
<div className="userWindow">
<User messages={this.state.messages} handleNewMessage={this.handleNewMessage} />
</div>
</div>
);
}
}
Agent.js(User.js is the same)
class Agent extends Component {
render() {
return (
<div className="Agent">
<header>
<p>Agent</p>
</header>
<MessageList messages={this.props.messages} />
<MessageForm onMessageSend={this.props.handleNewMessage} />
</div>
);
}
}
Message.js
class Message extends Component {
static propTypes = {
author: PropTypes.string,
body: PropTypes.string.isRequired,
me: PropTypes.bool
}
render() {
const classes = classNames('Message', {
log: !this.props.author,
me: this.props.me
})
return (
<div className={classes}>
{this.props.author && (
<span className="author">{this.props.author}:</span>
)}
{this.props.body}
</div>
)
}
}
MessageList.js
class MessageList extends Component {
static propTypes = {
messages: PropTypes.arrayOf(PropTypes.object)
}
static defaultProps = {
messages: [],
}
componentDidUpdate = () => {
this.node.scrollTop = this.node.scrollHeight
}
render() {
return (
<div className="MessageList" ref={(node) => (this.node = node)}>
{this.props.messages && this.props.messages.map((message, i) => (
<Message key={i} {...message} />
))}
</div>
)
}
}
MessageForm.js
class MessageForm extends Component {
static propTypes = {
onMessageSend: PropTypes.func.isRequired,
}
componentDidMount = () => {
this.input.focus()
}
handleFormSubmit = (event) => {
event.preventDefault()
this.props.onMessageSend(this.input.value)
this.input.value = ""
}
render() {
return (
<form className="MessageForm" onSubmit={this.handleFormSubmit}>
<div className="input-container">
<input
type="text"
ref={(node) => (this.input = node)}
placeholder="Enter Message..."
/>
</div>
<div className="button-container">
<button type="submit">
Send
</button>
</div>
</form>
)
}
}

I didn't understand your explanation...revising my answer. Please h/o.
OK. You are on the right track. Here is the solution I think you are looking for:
https://codesandbox.io/s/jz6o9y5n7v
Only a few changes. On the form, I added the "source" prop and pass that through to your handler in the App component. That correctly sets the Author prop to the source sent in from the form. I removed the second data element from the messages array. All this gives you the output I think you are after.
Thanks for the opportunity to help with this.

Related

Display the content of the clicked item in a modal popUp

i'm using React to create an App where i can see the flags and various infos about every country. I'm using an API to fetch the data and i've already mapped them all with a grid. That's my code so far:
class App extends React.Component{
constructor (props){
super (props);
this.state={
countries : [],
info: ""
}
}
componentDidMount(){
axios.get(`https://restcountries.eu/rest/v2/all`)
.then(res => {
const data = res.data;
console.log(data);
this.setState({
countries : data
})
this.showInfo = this.showInfo.bind(this)
})
}
showInfo (e) {
console.log(e.target.key);
}
render() {
return (
<div className="container">
{this.state.countries.map(country=>
<Country name={country.name}
key={country.name}
population ={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
showInfo={this.showInfo}
/>
)}
</div>
)
}
}
export default App;
And this is my Country-item component:
const Country = ({name, population, region, capital, flag, showInfo})=>{
return (
<div onClick={showInfo} className="country-item">
<div className="img">
<img src={flag}/>
</div>
<p>{name}</p>
<p>population: {population}</p>
<p>Region: {region}</p>
<p>Capital: {capital}</p>
</div>
)
}
export default Country
So far for now i have something like this:
enter image description here
Now i would like to click on each country box item and display that clicked data inside a modal popUp. If i create a modal and i will map it, of course on click i will have all of them displayed in once. how can i pass the props of that box i clicked on the modal component? i created a function trying to capture for example the key props, but i didn't suceed. What's the best strategy? thank you very much for the help
Attach an onClick handler to each country. When it's clicked, save the country name to the state of the containing component. Render the modal with content only from the country clicked:
class App extends React.Component{
constructor (props){
super (props);
this.state={
countries : [],
info: "",
clicked: ''
}
this.countryClickHandler = e => {
this.setState({clicked: country.name}, () => {
window.addEventListener('click', this.closeCountryPopup)
})
}
this.closeCountryPopup = e => {
this.setState({clicked: ''}, () => {
window.removeEventListener('click', this.closeCountryPopup)
})
}
}
componentDidMount(){
axios.get(`https://restcountries.eu/rest/v2/all`)
.then(res => {
this.setState({
countries : res.data
})
})
}
renderPopup() {
// if the clicked flag is falsy, null, or an empty string, don't render anything
if(!this.state.clicked || this.state.clicked === null || !this.state.clicked.length) return null
// otherwise, render the only clicked country by filtering it by matching it with the string in the state
const clickedCountry = this.state.countries.find(country => country.name === this.state.clicked)
return (
<div className="popup_container">
<Country
name={clickedCountry.name}
key={clickedCountry.name}
population ={clickedCountry.population}
region={clickedCountry.region}
capital={clickedCountry.capital}
flag={clickedCountry.flag}
/>
</div>
)
}
render() {
return (
<div className="container">
{this.state.countries.map(country =>
<div onClick={this.countryClickHandler}>
<Country
name={country.name}
key={country.name}
population ={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
/>
</div>
)}
{ this.renderPopup() }
</div>
)
}
}
export default App;
Your App component should maintain the state of which country should be displayed in a modal. More specifically App component will save in it's state if a modal should be displayed and which country to be displayed in the modal.
The showInfo prop that you pass to the Country component, should notify App component when a country is clicked.
I have created a representative example on CodePen.
class App extends React.Component {
constructor () {
super();
this.state = {
showModal: false,
selectedCountry: {},
countries: [
{name: "Germany", continent: "Europe"},
{name: "South Korea", continent: "Asia"},
{name: "New Zealnd", continent: "Australia"}
]
};
}
handleCloseModal = () => {
this.setState({
showModal: false
});
}
showInfo = (name) => {
this.setState({
selectedCountry: this.state.countries.find(it => it.name===name),
showModal: true
});
}
render () {
return (
<div>
{
this.state.countries.map((country) => <Country
name={country.name}
continent={country.continent}
showInfo={this.showInfo}
/>
)
}
<ReactModal
isOpen={this.state.showModal}
contentLabel={this.state.selectedCountry.name}
>
<div className="modal">
<div>{this.state.selectedCountry.name}</div>
<div>{this.state.selectedCountry.continent}</div>
</div>
<button onClick={this.handleCloseModal}>Close Modal</button>
</ReactModal>
</div>
);
}
}
const Country = (props) => {
return (
<div className="country" onClick={() => props.showInfo(props.name)}>
<div>{props.name}</div>
<span>-</span>
<div>{props.continent}</div>
</div>
)
};

React - Mapped array not passing props correctly to child component

I am making a dashboard component which displays rendered previews and code for HTML snippets. Inside of the dashboard component I am mapping the array of snippets using .map. Each mapped snippet is going to have a delete function (already built) and an update function.
For the update function to work each snippet has it's own child modal component. I need to pass the ID of the snippet to the modal component where I can combine the ID with the new content before updating the database and state.
However, I'm making a mistake somewhere as I pass the ID as props to the modal.
.map used inside of my Dashboard.js Dashboard class component.
{this.state.snippets.map(snippet => (
<>
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p> //THIS WORKS
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button type="button" onClick={this.showModal}>
Open
</button>
</div>
</div>
</div>
<Modal
sid={snippet._id} //PASS ID HERE
show={this.state.show}
handleClose={this.hideModal}
></Modal>
</>
))}
This renders the snippets below (3 snippet pods, with their database ID included).
The open button opens the modal (Modal.js) below.
import React, { Component } from 'react'
import api from '../api'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
handleInputChange = event => {
this.setState({
[event.target.name]: event.target.value,
})
}
handleClick = id => event => {
event.preventDefault()
console.log(id)
}
render() {
const { sid, show, handleClose } = this.props
console.log(sid)
const showHideClassName = show ? 'modal display-flex' : 'modal display-none'
return (
<div id="Modal" className={showHideClassName}>
<div id="modal-main">
<h4>Edit snippet {sid}</h4>
<form>
Name:{' '}
<input
type="text"
value={this.state.name}
name="name"
onChange={this.handleInputChange}
/>{' '}
<br />
Details:{' '}
<input
type="text"
value={this.state.details}
name="details"
onChange={this.handleInputChange}
/>{' '}
<br />
Content:{' '}
<textarea
value={this.state.content}
name="content"
cols="30"
rows="10"
onChange={this.handleInputChange}
/>{' '}
<br />
<button onClick={this.handleClick(sid)}>TEST ME</button>
</form>
<button onClick={handleClose}>Close</button>
{this.state.message && (
<div className="info">{this.state.message}</div>
)}
</div>
</div>
)
}
}
The console.log just under the render actually pastes the correct 3 ID's the console.
However, calling the ID (sid) within the Modal.js return will only show the last snippet ID, no matter which Modal I open. The same goes for pushing that ID to the handleClick function where I intend to combine the ID with an update package.
Solution below as initiated by HMR in the comments.
The problem was all the modals were showing and just the last one was visible.
Fixed by moving the modal out of the .map and instead updating the ID from within the .map to the state and passing the state ID to a new nested component within the modal.
Also switched to using dynamic CSS to show and hide the modal based on the state.
Dashboard.jsx
export default class Snippets extends Component {
constructor(props) {
super(props)
this.showModal = React.createRef()
this.state = {
snippets: [],
show: false,
sid: '',
}
}
handleDelete = id => event => {
event.preventDefault()
api
.deleteSnippet(id)
.then(result => {
console.log('DATA DELETED')
api.getSnippets().then(result => {
this.setState({ snippets: result })
console.log('CLIENT UPDATED')
})
})
.catch(err => this.setState({ message: err.toString() }))
}
handleModal = id => {
this.setState({ sid: id })
this.showModal.current.showModal()
}
//<div id="preview">{ReactHtmlParser(snippet.content)}</div>
render() {
return (
<>
<Modal ref={this.showModal} handleClose={this.hideModal}>
<ModalUpdate sid={this.state.sid} />
</Modal>
<div className="Dashboard">
<div className="wrapper">
<div className="container">
<div className="holder">
<div className="content">
<div className="content__body">
<h3>Dashboard</h3>
</div>
</div>
</div>
<div className="break"></div>
{this.state.snippets.map(snippet => (
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p>
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button
type="button"
onClick={() => this.handleModal(snippet._id)}
>
Open
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</>
)
}
Modal.jsx
import React, { Component } from 'react'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
}
}
showModal = () => {
this.setState({ show: true })
}
hideModal = () => {
this.setState({ show: false })
}
render() {
return (
<div
id="Modal"
style={{ display: this.state.show === true ? 'flex' : 'none' }}
>
<div id="modal-main">
<h4>Edit snippet </h4>
{this.props.children}
<button onClick={() => this.hideModal()}>Close</button>
</div>
</div>
)
}
}
ModalUpdate.jsx
import React, { Component } from 'react'
export default class ModalUpdate extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
// handleInputChange = event => {
// this.setState({
// [event.target.name]: event.target.value,
// })
// }
// handleClick = id => event => {
// event.preventDefault()
// console.log(id)
// }
render() {
return <h4>ID = {this.props.sid}</h4>
}
}
I am not sure about the handleDelete function,. but replacing the line should solve the issue probably
<button onClick={() => this.handleDelete(snippet._id)}>
One potential issue is the this.handleDelete(snippet._id) will fire immediately rather than onClick, so you will need to add an anonymous function in the event listener:
() => this.handleDelete(snippet._id)
instead of
this.handleDelete(snippet._id)

Stop Relay: Query Renderer in reloading data for certain setStates

I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}

React.js targeting a single element with a shared onClick function

I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;

Prop not being passed to Child

This is bizarre. My console.log produces a company:
but for some reason in my child, when I try pulling it from props, it's null
CompanyDetailContainer
class CompanyDetailContainer extends Component {
async componentDidMount() {
const { fetchCompany } = this.props,
{ companyId } = this.props.match.params;
await fetchCompany(companyId);
}
render(){
const { company } = this.props;
console.log(company) // this outputs a company
return (
<CompanyDetail className="ft-company-detail" company={company} />
);
}
}
const mapStateToProps = state => ({
company: state.company.company
});
const mapDispatchToProps = {
fetchCompany: fetchCompany
};
export default connect(mapStateToProps, mapDispatchToProps)(CompanyDetailContainer);
CompanyDetail
export default class CompanyDetail extends Component {
render(){
const callToAction = 'test';
const { company } = this.props;
console.log(company) // this is null! why??? I've never had this problem before
const title = `${company.name} Details`;
return (
<Main>
<MainLayout title={title}>
<div>
<div id='ft-company-detail'>
<div className="panel vertical-space">
<CompanyHeader className="ft-company-header" company={company} />
<div className="ft-call-to-action-interview">{callToAction}</div>
<CompanyProfile className="ft-company-profile" company={company} />
<RelatedLinks className="ft-company-resources" company={company} />
</div>
</div>
</div>
</MainLayout>
</Main>
);
}
}
///// UPDATE ////
this worked:
return (
company && <CompanyDetail className="ft-company-detail" company={company} />
);
But then why does this combo work fine? it's setup pretty much the same way. This is the first route hit on my app, renders this container:
HomepageContainer
class HomePageContainer extends Component {
async componentDidMount() {
await this.props.fetchFeaturedCompanies();
await this.props.fetchCompanies();
await this.props.fetchCountries();
}
render(){
return (<HomePage
className='ft-homepage'
companies={this.props.companies}
countries={this.props.countries}
featuredCompanies={this.props.featuredCompanies}
/>);
}
}
const mapStateToProps = state => ({
countries: state.country.countries,
companies: state.company.companies,
featuredCompanies: state.company.featuredCompanies
});
const mapDispatchToProps = {
fetchCountries: fetchCountries,
fetchCompanies: fetchCompanies,
fetchFeaturedCompanies: fetchFeaturedCompanies
};
export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer);
HomePage
export default class HomePage extends Component {
render(){
return (
<Main>
<MainLayout title='Test'>
<div className="homepage panel vertical-space margin-bottom-300">
<FeaturedCompanies companies={this.props.featuredCompanies} />
<div>
<div className="column-group">
<div className="all-100 width-100 align-center fw-300 extralarge">
test
</div>
</div>
</div>
<CompanyList className="ft-company-list" companies={this.props.companies} countries={this.props.countries} />
</div>
</MainLayout>
</Main>
);
}
}
To the fella who commented on my theme, the first image above is from Chrome tools dark theme. Here is my actual theme in WebStorm which I think is even better :P:
componentDidMount is called after the render and your async call is in the componentDidMount, so for the first render the parent and the child both get null, and since you use company.name in child without a conditional check it errors out. Provide a conditional check in the child and it will work fine
const { company } = this.props;
console.log(company)
const title = company ? `${company.name} Details`: null;

Categories