Socket.io emitting undefined variable React - html

I've currently got a very simple app that sends and receives data to a server via a websocket connection. Essentially, the app contains two text boxes and each time a user clicks on either of the textboxes to focus or click away, a notification is sent to the server and then the latter broadcasts that message to all connected instances.
This is my code below:
App.js (client)
import React, { useState, useEffect } from 'react'
import io from 'socket.io-client'
import TextField from '#material-ui/core/TextField'
import './App.css'
import 'carbon-components/css/carbon-components.min.css';
import { TextArea } from 'carbon-components-react';
const socket = io.connect('http://localhost:4000')
function App() {
const [myUser, setMyUser] = useState(null)
const handleButtonClick = (e) => {
e.preventDefault();
setMyUser(e.target.value)
}
useEffect(() => {
socket.on("message", ({ user, id, focus }) => {
console.log(user, "clicked on", id, "with focus", focus)
})
}, [])
function setFocusTrue(id) {
const focus = true
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
function setFocusFalse(id) {
const focus = false
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
const Main = () => {
return (
<div>
<h1>Hello {myUser}</h1>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text1"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text1')}
onBlur={() => setFocusFalse('text1')}
/>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text2"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text2')}
onBlur={() => setFocusFalse('text2')}
/>
</div>
)
}
return (
<div>
{myUser === null ?
[
<div>
<button onClick={handleButtonClick} value="user1">User 1</button>
<button onClick={handleButtonClick} value="user2">User 2</button>
</div>
]
: <Main />
}
</div>
)
}
export default App
index.js (server)
const app = require('express')()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
io.on('connection', socket => {
socket.on('message', ({ user, id, focus }) => {
console.log('received user', user, 'id', id, 'focus', focus)
io.emit('message', { user, id, focus })
})
})
http.listen(4000, function() {
console.log('listening on port 4000')
})
One thing to note is that I'm testing this with two different users (running on two incognito browsers), hence the conditional rendering for each user. For this example, when either user clicks on or away from a textbox, I can see that the textbox id and focus are both valid, but for some reason, the user is undefined. I can see that the instance emits the variables as expected, but they are not being received in the server correctly.
In this particular case, user 1 clicks on the first textbox (thus rendering the focus to true). Each of the variables to emit to the server are set (as can be seen by the console log). The server receives the focus and id variables, but for an odd reason, the user is undefined, even though it exists. Finally, the broadcasted message is sent to both instances but as mentioned, id and focus exist, but user is undefined. See below screenshots.
Client side
Server side console

Instead of emitting the object like socket.emit('message', { myUser, id, focus }), I instead provided the user object as such socket.emit('message', { user: myUser, id, focus }) which seemed to work fine.

Related

Chrome extension messaging error: Could not establish connection. Receiving end does not exist

I'm building an extension where when the extension first starts (browser is started/extension is updated) a window is opened with a html file containing a form asking for a master password. When this master password does not match a certain string, a message is sent through chrome.runtime.sendMessage. A message is also sent the same way when the modal window is closed through the chrome.windows.onRemoved listener.
Here is my service worker:
/// <reference types="chrome-types"/>
(async () => {
console.log('Extension started. Modal opened...');
const window = await chrome.windows.create({
url: chrome.runtime.getURL("html/index.html"),
type: "popup",
width: 400,
height: 600,
});
chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === window?.id) chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'tab closed' }).catch(console.log);
});
chrome.runtime.onMessage.addListener((message) => {
if (Object.hasOwn(message, 'monitoringEnabled')) {
console.log(`Monitoring ${message.monitoringEnabled ? 'enabled' : 'disabled'}. ${message.reason ? `Reason: ${message.reason}` : ''}`)
chrome.storage.local.set({ monitoringEnabled: message.monitoringEnabled });
if (window?.id) chrome.windows.remove(window.id);
}
return true;
});
})();
The html file just has a form with a button which when clicked triggers a script:
const MASTER_PASSWORD = 'some_thing_here';
document.getElementById('submit-button').addEventListener("click", (e) => {
const password = document.getElementById('master-password-text-field').value;
if (password !== MASTER_PASSWORD) return chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'invalid password' })
return chrome.runtime.sendMessage({ monitoringEnabled: false })
})
These are some logs:
The first error is when the modal tab is closed, notice that nothing happens after this (i.e onMessage listener is not triggered). However, in the second case, when a message is sent from the modal script, the onMessage listener is triggered, but the connection error still appears after the code in the listener has processed.
Not sure why this happens. I have checked multiple other threads on the same topic but none of them seem to help me. If you have a better idea on what I can do to achieve what I want right now, please suggest.
In my code, I'm sending a message to the service worker in the server worker itself. I've re-wrote my code by just making a function which is called when the windows.onRemoved event is triggered and also when a message is sent from the modal tab. The seems to have fixed my issue. This is my service worker code for reference:
/// <reference types="chrome-types"/>
console.log('Extension started. Modal opened...');
let windowId: number | null = null;
chrome.windows
.create({
url: chrome.runtime.getURL('html/index.html'),
type: 'popup',
width: 400,
height: 600
})
.then((created) => (windowId = created?.id ?? null));
chrome.windows.onRemoved.addListener((id) => {
if (id === windowId) enableMonitoring('window closed')
});
chrome.runtime.onMessage.addListener((message) => {
if (message.monitoringEnabled) {
enableMonitoring(message.reason);
}
return undefined
})
function enableMonitoring(reason: any) {
console.log('monitoring enabled', reason);
}

Edit form that is pre-filled with API data - React

I am building a form that is pre-populated by the results of an axios get request to a nodejs API that returns an array (stored in MySQL). I asked this question yesterday, implemented a solution that was provided and it worked. The next day I tested it thoroughly and it turns out that on submission only the edited fields were passed but not the values from the unedited fields.
I can get the data to map onto the form, but i cannot edit the form. The idea is for it to be an "edit user" form. I suspect the issue is in the onChange portion of the input field.
The form is accessed from a table that is also mapped with the results of a get request. Upon clicking the edit button, the userID from the table row is passed to the edit form through useNavigate and useLocation (I can add this portion of code if needed).
Backend
Here is the API controller that queries the MySQL database and then sends to the frontend:
export const edit = async (req, res) => {
db.query(
"SELECT * FROM userIndex WHERE userID = ?",
[req.params.userID],
(err, rows) => {
if (!err) {
res.send(rows);
} else {
console.log(err).res.send({ alert: err });
}
}
);
};
Here's an example result of the query above:
[
{
"userID": 143,
"firstName": "Kenan",
"lastName": "Erasmus",
"role": "student",
"email": "kenan#gmail.com",
"password": "$2b$12$v3s0D6cNkGwM3/IWXPdv..TRfRZLmDNuZBfrWlUCt4vKnyRi75jWe",
"createdAt": "06/10/2022, 13:56:51",
"updatedAt": "07/10/2022, 19:45:46",
"lastLogin": null
}
]
Frontend
Here is the portion of code that performs the request to the API:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(rows.firstName)
};
fetchData();
}, [setRows, userID, token]);
State for "rows" (set on API response):
const [rows, setRows] = useState([]);
const [firstName, setFirstName] = useState("");
And finally, an example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
I have only included the "firstName" input as an example. In total, this form has about 6 fields.
I would greatly appreciate any assistance with this. Let me know if I can supply any more code.
Found a workaround that seems quite logical to me. I initialised new blank states for each of the input fields
const [firstName, setFirstName] = useState("");
Then mapped the form with "rows" and set each field value to its correspond state (as seen in the useEffect below)
Frontend useEffect:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(results.data[0].firstName);
setLastName(results.data[0].lastName);
setEmail(results.data[0].email);
setPassword(results.data[0].password);
setRole(results.data[0].role);
};
fetchData();
}, [setRows, userID, token]);
Example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
By doing it this way, the form maps through "rows", and then each input field immediately gets a new state once typing occurs.
I know it's ugly, but it is effective. I'm new to coding so I don't know all the ins and outs of React yet - but this solution works for me. Hope this helps anyone looking for a solution to the same issue!

Can not store json obj in state when i use useEffect to get an object from an API

Im trying to show an object's properties on a modal, but nothing seems to happen after i fetch it. I've tried without using the useEffect hook, and it does store the item but then i cant access the properties, i asked about it, and a user told me to use use Effect. But now, nothing seems to be stored...
This is my code:
import React, {useState, useEffect } from 'react';
const Modal = ({ handleClose, show, id }) => {
const showHideClassName = show ? "mod displayBlock" : "mod displayNone";
const [peliSeleccionada, setPeli] = useState([]);
useEffect(() => {
fetch(`http://localhost/APIpeliculas/api/pelicula/read_single.php/?ID=${id}`)
.then(res => res.json())
.then(
result => {
alert(result); //the alerts dont even pop up
setPeli(result);
alert(peliSeleccionada);
});
}, []);
return (
<div className={showHideClassName}>
<section className="mod-main">
<h5>EDITAR: </h5>
<label>
{ peliSeleccionada.Nombre }
</label>
<div className="btn-grupo">
<button type="button" className="btn btn-success btn-lg btn-block">Guardar cambios</button>
<button onClick={handleClose} type="button" className="btn btn-secondary btn-lg btn-block">Cerrar</button>
</div>
</section>
</div>
);
};
export default Modal;
The alerts i put inside my useEffect function dont even pop up, and i also get this error on the console as soon as i enter the page:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
Also I want to access my object's properties, which are: ID, Nombre, Categoria, and Director. Is this the correct way to do it? { peliSeleccionada.Nombre }
useEffect is run after the component renders, similarly to how componentDidMount works.
What this means, putting it very simply, is that the component will return and then fire the fetch.
There is an issue with your peliSeleccionada state, you declare it as an array but call peliSeleccionada.Nombre like if it was an object. This means that on first render it will print undefined for the peliSeleccionada.Nombre.
An approach to have this is to pair it with a loading state.
const Modal = () => {
const [loaded, setLoading] = useState(true);
const [peliSeleccionada, setPeli] = useState({});
useEffect( () => {
fetch()
.then(res => {
setPeli(res) // parse this accordingly
}) // or chain of thens if needed
.catch(err => {
setLoading(false);
console.log(err) // something failed in the fetch. You could have another state to mark that the fetching failed; close the modal, show an error, etc.
})
}, [])
if(!loaded) return 'Loading...'
return (<div>Data goes here</div>) // be careful if the fetch failed, it won't show data!
}
Last but not least, the error is happening in the fetch and the message states exactly that, that you are not catching it. If it is in the fetch call, the above code works. If it is in a parsing, it might require a try/catch around the useEffect

Asynchronous custom validation issue

I have created an asynchronous custom validation for my angular form. The validator checks an array of emails to see if the email that the user has entered is already registered or not (like you would get on a generic log in screen). I have done this asynchronously to simply mimic the retrieval of data from a server.
The issue I am having is that when an unregistered email address is entered by the user, although the error message is correctly displayed on the UI by checking whether the error message is present in my form object, an error message is also logged to the console: "TypeError: Cannot read property 'emailIsNotRegistered' of null".
It's almost as if the promise is returning the correct key value pair {'emailIsNotRegistered': true}, but is then also returning 'null'.
Here is the custom validator:
unregisteredEmail(control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(this.registeredEmails.indexOf(control.value) === -1) {
resolve({'emailIsNotRegistered': true})
} else {
resolve(null);
}
}, 1500)
});
return promise;
}
Here is where the validator is added to the email form control:
ngOnInit() {
this.logInForm = new FormGroup({
'email': new FormControl(null, [Validators.required, Validators.email], this.unregisteredEmail.bind(this)),
'password': new FormControl(null, [Validators.required])
})
}
The email input tag from html:
<input class="input" type="text" id="email" name="email" formControlName="email">
This html element that is added if the user enters an email address which isn't already registered:
<span *ngIf="logInForm.get('email').errors['emailIsNotRegistered'] && logInForm.get('email').touched">No account found with this email address. Register below!</span>
An finally, the registeredEmails array:
registeredEmails = ['52pbailey#gmail.com', 'sammie-midnight#hotmail.co.uk'];

Is it possible to populate the input bar in webchat with an onclick method

I'm attempting to display a list of popular questions to the user, when they click them I want them to populate the input bar and/or send the message to the bot via the directline connection.
I've attempted using the ReactDOM.getRootNode() and tracking down the input node and setting the .value attribute, but this does not populate the field. I assume there is some sort of form validation that prevents this.
Also, if I console log the input node then save it as a global variable in the console screen I can change the value that way, but then the message will not actually be able to be sent, hitting enter or the send arrow does nothing. While it may seem that the suggestedActions option would work well for this particular application, I CANNOT use it for this use case.
const [chosenOption, setChosenOption] = useState(null);
const getRootNode = (componentRoot) =>{
let root = ReactDom.findDOMNode(componentRoot)
let inputBar = root.lastChild.lastChild.firstChild.firstChild
console.log('Initial Console log ',inputBar)
setInputBar(inputBar)
}
//in render method
{(inputBar && chosenOption) && (inputBar.value = chosenOption)}
this is the function I tried to use to find the node, the chosen option works as intended, but I cannot change the value in a usable way.
I would like the user to click on a <p> element which changes the chosenOption value and for that choice to populate the input bar and/or send a that message to the bot over directline connection.What I'm trying to accomplish
You can use Web Chat's store to dispatch events to set the send box (WEB_CHAT/SET_SEND_BOX) or send a message (WEB_CHAT/SEND_MESSAGE) when an item gets clicked. Take a look at the code snippet below.
Simple HTML
<body>
<div class="container">
<div class="details">
<p>Hello World!</p>
<p>My name is TJ</p>
<p>I am from Denver</p>
</div>
<div class="wrapper">
<div id="webchat" class="webchat" role="main"></div>
</div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
// Initialize Web Chat store
const store = window.WebChat.createStore();
// Get all paragraph elements and add on click listener
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
paragraph.addEventListener('click', ({ target: { textContent: text }}) => {
// Dispatch set send box event
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
});
}
(async function () {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
React Version
import React, { useState, useEffect } from 'react';
import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
const WebChat = props => {
const [directLine, setDirectLine] = useState();
useEffect(() => {
const initializeDirectLine = async () => {
const res = await fetch('http://localhost:3978/directline/token', { method: 'POST' });
const { token } = await res.json();
setDirectLine(createDirectLine({ token }));
};
initializeDirectLine();
}, []);
return directLine
? <ReactWebChat directLine={directLine} {...props} />
: "Connecting..."
}
export default () => {
const [store] = useState(createStore());
const items = ["Hello World!", "My name is TJ.", "I am from Denver."]
const click = ({target: { textContent: text }}) => {
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
}
return (
<div>
<div>
{ items.map((item, index) => <p key={index} onClick={click}>{ item }</p>) }
</div>
<WebChat store={store} />
</div>
)
};
Screenshot
For more details, take a look at the Programmatic Post as Activity Web Chat sample.
Hope this helps!