How to emit inputs on change using socket.io? - javascript

I'm working on a chat style app using React and socket.io, and I want to implement something where a client can see the input being typed by the other client in realtime. I'm using onChange on the input to update the state and emit the message at the same time, but that only sends messages one letter at a time to the server, and deletions don't work at all.
Here's the frontend with extra code omitted:
this.state = {
text: '',
name: '',
messages: []
}
componentDidMount() {
socket.on('RECIEVE_MESSAGE', function(msg) {
this.setState({
messages: [...this.state.messages, msg]
})
})
}
onInputChange(event) {
this.setState({
text: event.target.value
})
socket.emit('example_message', this.state.text);
this.setState({
messages: [...this.state.messages, this.state.text]
})
}
return (
<div>
<form type="text" onSubmit={this.handleSubmit}>
<input
type="text"
className="text-input"
id="name-input"
name="text-input"
required="required"
placeholder="text"
//used to save end result
value={this.state.text}
onChange={this.onInputChange}></input>
{/* <button className="next-btn"> NEXT </button> */}
<button onClick={this.sendSocketIO}>Send Socket.io</button>
</form>
<p>{this.state.text}</p>
<p>{this.state.messages}. </p>
</div>
)
And the backend:
io.on('connection', client => {
console.log('Socket connected: ', client.id);
//recieves the message from the client, and then re-emits to everyone
client.on('SEND_MESSAGE', data => {
data.message = validate.blacklist(data.message, ['<','>','&','"','/']);
io.emit('RECEIVE_MESSAGE', data);
});
I'd like to render to the DOM a live feed of what the other client is typing, and update it with every character and character deletion.

You are emitting with:
socket.emit('example_message', this.state.text)
and handling with:
client.on('SEND_MESSAGE', data...
Use the same name for the message to emit and then handle on the server.

Related

Email.js Works Locally, But not Once Deployed With React

I have set up Email.js to make a contact page for a website built with Next.js. It works completely fine when run locally, but does not work when hosted. The form does not even reset when the submit button is clicked. I do this in the sendEmail function. The error handler does not trigger either in the .then block. I get this error in the browser console:
Uncaught The user ID is required. Visit https://dashboard.emailjs.com/admin/integration
Here is how I send the emails:
export default function Book(props) {
const form = useRef();
const [sentMessage, setSentMessage] = useState();
const sendEmail = (e) => {
e.preventDefault();
emailjs
.sendForm(
props.SERVICE_ID,
props.EMAIL_TEMPLATE_ID,
form.current,
props.USER_ID
)
.then(
function (response) {
setSentMessage("Message sent successfully!");
},
function (error) {
setSentMessage("Message failed please email directly.");
}
);
document.getElementById("form").reset();
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<form
className={styles.form}
ref={form}
onSubmit={sendEmail}
id="form"
>
<h3>Name (required):</h3>
<input type="text" required={true} name="user_name"></input>
<h3>Email (required):</h3>
<input type="email" required={true} name="user_email"></input>
<h3>Phone number (required):</h3>
<input type="number" required={true} name="phone_number"></input>
<h3>Message (optional):</h3>
<textarea name="message"></textarea>
<button type="submit" value="Send">
Submit
</button>
{sentMessage ? <p>{sentMessage}</p> : <p></p>}
</form>
</div>
</div>
);
}
export async function getServerSideProps() {
return {
props: {
USER_ID: process.env.USER_ID,
EMAIL_TEMPLATE_ID: process.env.EMAIL_TEMPLATE_ID,
SERVICE_ID: process.env.SERVICE_ID,
},
};
}
I have a .env.local file with the template id, user id and service id that all work fine locally. I use next-env and dotenv-load in the next.config.js file like so:
dotenvLoad();
const withNextEnv = nextEnv();
module.exports = withNextEnv({
reactStrictMode: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ["#svgr/webpack"],
});
return config;
},
});
I saw some problems online that people had with Gmail and remote email servers, so I switched the account to have no 2 factor authentication and use less secure apps as well. That had no effect.
All you need to do is set up the environment variables in the next.js dashboard then rebuild the site so they take effect.

Is there a way to convert a javascript object to a string?

I am building a simple chat app and want to greet new users with a welcome mesage, but all i see is [object Object]. The connection is working. What to do?
This is the messages object that i am using to model the messages
const generateMessage = (text) => {
return {
text,
createdAt: new Date().getTime()
}
}
module.exports = {
generateMessage
}
Here is my connection
io.on("connection", (socket) => {
console.log("New socket connection")
socket.emit("message", generateMessage("Welcome to the chat"))
socket.broadcast.emit("message", "A new user has joined",)
socket.on("sendMessage", (message, callback) => {
io.emit("message", message)
callback()
})
socket.on("disconnect", () => {
io.emit("message", "A user has left")
})
})
And here is the html where i render the messages to the browser
<body>
Chat app
<div id="messages"></div>
<form id="message-form">
<input name="message" placeholder="Message">
<button>Send</button>
</form>
<template id="message-template">
<p></p>
</template>
<script src="/socket.io/socket.io.js"></script>
<script src="../chat/chat2.js"></script>
</body>
okay first thing that generateMessage function is not returning proper key value
return {
text: text,
createdAt: new Date().getTime()
}
and the second thing you are trying to render the whole object but you should render
socket.emit("message", generateMessage("Welcome to the chat").text)
In your client (the html file) you should create the message accessing text property of object:
const htmlMessageText = messageObjReceivedFromSocket.text
Not pass the object itself.
It'll easier help you if post how you handling the socket events on client.

Nuxt.js: Contact form submission

I am using server middleware to handle the POST from a contact form.
The problem is, it doesn't seem like the middleware file I created runs. I added breakpoints using VS Code, and they don't fire in the contact.js, but they do fire in the contact.vue (in VS Code I made sure to add the breakpoints in the appropriate client/server side environments). Even the console log info I have in the contact.js doesn't appear. It is like the file is being missed.
What happens is the POST call is made and stays open, with no response. The POST'd attributes and values are all there.
I suspect that the createTransport({sendmail: true}) to fail as I don't have sendmail on my development system, but no error is thrown.
Can you see if there is anything I am missing? All I want to do is simply send an email when the form is submitted, but it seems so difficult!
I was guided by https://blog.lichter.io/posts/emails-through-nuxtjs/
All packages are installed.
This is how I have it set up...
nuxt.config.js
export default {
.
.
.
serverMiddleware: [
'~/api/contact'
],
.
.
.
}
pages/contact.vue
<template>
.
.
.
<form class="form-email row mx-0"
data-success="Thanks for your enquiry, we'll be in touch shortly."
data-error="Please fill in all fields correctly."
#submit.prevent="submitForm">
<div class="col-md-6 col-12 text-xl"> <label>Your Name:</label> <input v-model="name" type="text" name="name"
class="validate-required"> </div>
<div class="col-md-6 col-12 text-xl"> <label>Email Address:</label> <input v-model="email" type="email" name="email"
class="validate-required validate-email"> </div>
<div class="col-md-12 col-12 text-xl"> <label>Message:</label> <textarea v-model="message" rows="4" name="message"
class="validate-required"></textarea> </div>
<div class="col-md-5 col-lg-4 col-6"> <button type="submit" class="btn btn--primary type--uppercase">Submit</button> </div>
</form>
.
.
.
</template>
<script>
export default {
data () {
return {
name: '',
email: '',
message: ''
}
},
mounted() {
mr.documentReady($);
mr.windowLoad($);
},
methods: {
async submitForm () {
try {
await this.$axios.$post('', {
name: this.name,
email: this.email,
msg: this.message
});
await new Promise(resolve => setTimeout(resolve, 2500));
} catch (e) {
console.error(e);
}
}
}
}
</script>
api/contact.js
const express = require('express')
const nodemailer = require('nodemailer')
const validator = require('validator')
const xssFilters = require('xss-filters')
const app = express()
app.use(express.json())
app.get('/', function (req, res) {
res.status(405).json({ error: 'sorry!' })
})
app.post('/', function (req, res) {
const attributes = ['name', 'email', 'message'] // Our three form fields, all required
// Map each attribute name to the validated and sanitized equivalent (false if validation failed)
const sanitizedAttributes = attributes.map(n => validateAndSanitize(n, req.body[n]))
// True if some of the attributes new values are false -> validation failed
const someInvalid = sanitizedAttributes.some(r => !r)
if (someInvalid) {
// Throw a 422 with a neat error message if validation failed
return res.status(422).json({ 'error': 'Ugh.. That looks unprocessable!' })
}
sendMail(...sanitizedAttributes)
res.status(200).json({ 'message': 'OH YEAH' })
})
module.exports = {
path: '/api/contact',
handler: app
}
const validateAndSanitize = (key, value) => {
const rejectFunctions = {
name: v => v.length < 4,
email: v => !validator.isEmail(v),
message: v => v.length < 25
}
// If object has key and function returns false, return sanitized input. Else, return false
return rejectFunctions.hasOwnProperty(key) && !rejectFunctions[key](value) && xssFilters.inHTMLData(value)
}
const sendMail = (name, email, message) => {
console.info('Info: 🙂'
+'\n Contact page submission.'
+'\n Date: '+ new Date().toLocaleTimeString() +' '+ new Date().toLocaleDateString()
+'\n Name: '+ name
+'\n Email: '+ email
+'\n Message: '
+'\n'
+'\n'+ message
+'\n'
+'\n End of submission.'
+'\n');
let transporter = nodemailer.createTransport({
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail'
})
transporter.sendMail({
from: email,
to: 'mail#foobar.com',
subject: 'Foo Bar: Contact Form',
text: message
}, (err, info) => {
console.log(info.envelope);
console.log(info.messageId);
})
}
I recently published a Nuxt.js module that allows to send emails via a route and an injected variable. You can use it like this:
$ npm install nuxt-mail #nuxtjs/axios
In your nuxt.config.js file:
export default {
modules: [
'#nuxtjs/axios',
['nuxt-mail', {
smtp: {
host: "smtp.example.com",
port: 587,
},
}],
],
}
Use it in your component:
this.$mail.send({
from: 'John Doe',
subject: 'Incredible',
text: 'This is an incredible test message',
to: 'johndoe#gmail.com',
})
Yours doesn't work because you are not calling the correct path, in your serverMiddleware you said that /api/contact points to the contact.js file.
This means that /api/contact equals at the routes of that contact.js, thus, you should call:
this.$axios.post('/api/contact')
#manniL kindly pointed out to me on a call that in my nuxt.config.js I had set up the environment to load on 0.0.0.0 so that I could access it from a different machine.
export default {
server: {
port: 3000,
host: '0.0.0.0' // default: localhost
},
...
}
I needed to also setup the axios config with the same IP address, which is why it could not find the api/contact.js.
I have since learnt that the default scaffold for Nuxt2, when including axios, is it adds this to the nuxt.config.js to cover changes in IP:
export default {
...
axios: {
baseURL: '/'
},
...
}
Apologies, I should have posted my full nuxt.config.js in the question as it was different from a default setup.

react.js how to display multiple error messages

I just got started to react so please bear with me. I don't know exactly what I am doing, I'm just picking those things as I go so I'll do my best to walk you through my mental process when building this.
My intentions are to create a registration component, where the backend returns the validation errors in case there are any in form of an object which has following structure.
{
"username": [
"A user with that username already exists."
],
"email": [
"A user is already registered with this e-mail address."
]
}
The state manager that I chose to be using is redux, so this comes back every time when the register function is dispatched.
Since it has this structure I wrote a function to help me decompose it and pick up only on the actual errors (the strings).
const walkNestedObject = (obj, fn) => {
const values = Object.values(obj)
values.forEach(val =>
val && typeof val === "object" ? walkNestedObject(val, fn) : fn(val))
}
now I want to display them in the view, so I wrote another function which is supposed to do that
const writeError = (value) => {
return <Alert message={value} type="error" showIcon />
}
Down in the actual component I am calling it as this:
{(props.error) ? walkNestedObject(props.error, writeError) : null}
To my surprise if I console.log the value above return in writeError it works flawlessly, every single error gets printed, but none of them gets rendered.
To debug this I've tried multiple variations and none of them seemed to work, I even called the writeError function in the component as
{writeError('test')}
and it worked for some reason.
At this stage I'm just assuming there's some react knowledge required to fulfil this task that Im just now aware of.
EDIT:
A mock example can be found over here
Also, I've tried using the first two answers and when mapping through the errors I get this
Unhandled Rejection (TypeError): props.error.map is not a function
with other variations, it mentions the promise from so I'd include how I manage the API request
export const authSignup = (username, email, password1, password2) => dispatch => {
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
username: username,
email: email,
password1: password1,
password2: password2
})
.then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
})
.catch(err => {
dispatch(authFail(err.response.data))
})
}
Consider changing the topology of your error messages:
"errors": [
{ "type": "username", "message": "Username already in use." },
{ "type": "email", "message": "Email address already in use."}
]
That makes your implementation a bit easier:
// MyLogin.jsx
import React from 'react'
const MyLogin = () => {
/**
* Here we're using state hooks, since it's much simpler than using Redux.
* Since we don't need this data to be made globally available in our
* application, it doesn't make sense to use Redux anyway.
*/
const [errors, setErrors] = React.useState([])
const handleLogin = (event) => {
event.preventDefault()
axios.post('/api/login', formData).then(() => successAction(), (error: any) => {
setErrors(error) // Update our local state with the server errors
})
}
return (
<>
{errors.length && ( // Conditionally render our errors
errors.map((error) => (
<Alert type={error.type} message={error.message} />
)
)}
<form onSubmit={handleLogin}>
<input type='text' name='email' />
<input type='text' name='username' />
<input type='password' name='password' />
</form>
<>
)
}
export default MyLogin
Your walkNestedFunction function checks each layer of an object, and if a given layer of the object is an object itself, it then uses that object to run your function - which in this case is writeError. writeError returns an error <Alert /> as soon as an error arises. But when you stick writeError inside the circular logic of walkNestedFunction, it will hit the first return statement, render it to the page, and then stop rendering. I think this is why you're getting the complete list of errors logged to the console. Your walkNestedFunction continues cycling down through object layers until its done. But in React, only the first return statement will actually render.
A better tactic would be to modify your writeError function to record the erors to a state variable. Then you can render this state variable. Every time the state is updated, the component will rerender with the updated state.
// Define state in your component to contain an array of errors:
state = {
errors: []
}
// Add an error into state with each iteration of writeError
const writeError = (error) => {
this.setState({
errors: [
...this.state.errors,
error
]
})
}
// inside render(), render the state variable containing your errors
<div>
{ this.state.errors.map(error => <p>error</p>) }
</div>
`

Input undefined when sending post request to the nodejs api using react and axios

I'm trying to make a post request to an API that I created with mysql and node js using react and axios. But when I input in the data the response i get is 'undefined'.
When I put the data with postman, it works. I tried to log the data that I sent with my browser and I saw them.
So I think that maybe the structure
When i console.log the data in the browser, I can figure out those data
adapter: Æ’ xhrAdapter(config)
data: "{"etudiants":{"name":"Fenohasina
Andrainiony","lastname":"fenohasina","birthdate":"12-12-12"}
So i think that maybe, the structure of the data I sent is different to the structure I can receive on my API because on my API: it is just
{"name":"Fenohasina
Andrainiony","lastname":"fenohasina","birthdate":"12-12-12"}
Is this the problem?
This is the api code:
app.post('/etudiants/add', (req, res)=>{
const {name,
lastname,
birthdate,
} = req.query;
const queryInsert =`insert into etudiants (name, lastname, birthdate) values('${name}', '${lastname}', '${birthdate}')`
connection.query(queryInsert, (err, rows, fields)=>{
if(err) {
throw err
}
res.json(rows);
res.end()
})
}
}`
And the React Code:
export default class PersonList extends React.Component {
state = {
name:'',
lastname:'',
birthdate:'',
}
handleSubmit = event => {
event.preventDefault();
const etudiants = {
name: this.state.name,
lastname:this.state.lastname,
birthdate:this.state.birthdate,
};
axios.post('http://localhost:8080/etudiants/add', {etudiants})
.then(res=>{
console.log(res);
console.log(res.data)
})
}
handleName = event => {
this.setState({
name: event.target.value
})}
handleLastname = event => {
this.setState({
lastname: event.target.value,
})}
handleBirthdate = event => {
this.setState({
birthdate: event.target.value
})}
render() {
return (
<div className='App'>
<form onSubmit={this.handleSubmit}>
<label>
Person Name:
<input type="text" name="name" id='name'value={this.state.name} onChange={this.handleName} />
</label>
<label>
Person Lastname:
<input type="text" name="lastname" id='lastname'value={this.state.lastname} onChange={this.handleLastname} />
</label>
<label>
Person birthdate:
<input type="text" name="genre" id='birthdate'value={this.state.birthdate} onChange={this.handleBirthdate} />
</label>
<button type="submit">Add</button>
</form>
</div>
)
Thank you in advance!
Yes the problem is with the data you send
It should be
axios.post('http://localhost:8080/etudiants/add', {...etudiants})
(note the spread operator ... so that your etudiants constant is expanded in the object you send. Otherwise it just creates a property named etudiants which contains the contents of your constant)
Your code uses the short notation introduced in ES6
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
additionally you could re-use a single method for your input handling since they all do the same thing. So handleName, handleLastname and handleBirthdate could be replaced by just
handleInput = event => {
const {name, value} = event.target;
this.setState({
[name]: value
});
}
finally i think
<input type="text" name="genre" id='birthdate'
should really be
<input type="text" name="birthdate" id='birthdate'
I think the problem is how you are extracting the request data in the backend. In the react code, this is how you are making the request to the server:
axios.post('http://localhost:8080/etudiants/add', {etudiants})
The default behavior of Axios, when used like this, is to JSON serialize the request data(i.e {etudiants}) Refrence.
In order to receive and use this data in the backend, you need to parse the JSON request body, if you are using a body-parser middleware like this one, that should already be done for you. Then, you can extract the request body like this:
const {
name,
lastname,
birthdate,
} = req.body.etudiants;
Note how I replaced req.query with req.body and the addition of etudiants.

Categories