I'm trying to build a React app with Hooks, with a node.js express server and postgreSQL database.
The first step is user registration but I'm experiencing some weirdness with axios (and also a proxy error which may or may not be related?)
Desired behaviour: User completes all fields and clicks submit, data is sent to backend, stored in a database and a user id assigned, response returned to front end and all fields clear.
If the user submits incomplete information, it is not stored and the response from the backend triggers an error message to the user.
Situation 1: The user completes all fields.
Outcome 1: Behaviour is as expected EXCEPT
(a) the user data also appears in the search bar, including the password in plain text, and persists after the data is saved e.g
http://localhost:3000/?first=Lucy&last=Who&email=who%40example.com&password=something#/
(b) The following error:
Proxy error: Could not proxy request /register from localhost:3000 to http://localhost:5000/.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNRESET).
[note: I am using create-react-app, 3000 is the port used by the dev server, I'm using 5000 for my server]
Situation 2: The user enters incomplete information and clicks submit.
Outcome 2: The data appears in the search bar as above and is sent to the backend, the input fields clear but apparently no response is returned and the error message is not triggered.
Situation 2.1: user sends the same incomplete information again
Outcome 2.1: the error message is triggered.
Situation 2.2: user sends different incomplete information
Outcome 2.2: the error message clears.
Code (apologies if this is too much/not enough, not being sure where the problem lies makes it a bit tricky to work out what someone else might need to know)
register.js
import React, { useState } from "react";
import axios from "./axios";
import { Link } from "react-router-dom";
export default function Register() {
const [error, setError] = useState(false);
const [first, setFirst] = useState("");
const [last, setLast] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const formDeets = {
first: first,
last: last,
email: email,
password: password,
};
function submitReg() {
console.log("formDeets", formDeets);
axios
.post("/register", formDeets)
.then((response) => {
console.log("response.data: ", response.data);
clearAll();
if (response.data.success) {
console.log("success");
} else {
setError("true");
}
})
.catch((err) => {
console.log("register.js error in post register", err);
});
}
function clearAll() {
console.log("clear all called");
setFirst("");
setLast("");
setPassword("");
setEmail("");
}
return (
<section className="register container">
<div className="register-component">
{error && (
<div className="error">
Registration failed. Please complete all fields and try
again.
</div>
)}
<form>
<label htmlFor="first">first name</label>
<input
onChange={(event) => setFirst(event.target.value)}
type="text"
name="first"
placeholder="first name..."
value={first}
/>
<label htmlFor="first">last name</label>
<input
onChange={(event) => setLast(event.target.value)}
type="text"
name="last"
placeholder="last name..."
value={last}
/>
<label htmlFor="email">email address</label>
<input
onChange={(event) => setEmail(event.target.value)}
name="email"
type="email"
placeholder="email address..."
value={email}
/>
<label htmlFor="password">choose a password</label>
<input
onChange={(event) => setPassword(event.target.value)}
name="password"
type="password"
placeholder="choose a password..."
value={password}
/>
submit
<input
type="submit"
value="click to accept cookies and register"
onClick={() => submitReg()}
/>
</form>
</div>
</section>
);}
server.js (just the relevant part, I think)
app.post("/register", (req, res) => {
console.log("/register route hit");
console.log("req body", req.body);
const first_name = req.body.first;
const last_name = req.body.last;
const email = req.body.email;
const password = req.body.password;
let user_id;
if (!first_name || !last_name || !email || !password) {
res.json({
success: false,
});
return;
}
hash(password).then((hashpass) => {
db.addUser(first_name, last_name, email, hashpass)
.then((results) => {
console.log("results", results.rows[0]);
user_id = results.rows[0].id;
req.session.userId = user_id;
res.json({ success: true });
})
.catch((err) => {
console.log("err in addUser: ", err);
res.json({ success: false });
});
return;
});
}); //end of register route
server.listen(port, () => console.log(`listening on port ${port}`));
and finally, I'm calling axios from axios.js:
import axios from "axios";
var instance = axios.create({
xsrfCookieName: "mytoken",
xsrfHeaderName: "csrf-token"
});
export default instance;
Browsers have default behaviour for when you submit a form.
It is causing the browser to navigate to a new URL after running your JS.
You need to prevent the default behaviour of that submit event.
onClick={() => submitReg()}
There doesn't seem to be any reason to use an arrow function here. submitReg doesn't use this so binding this with an arrow function is pointless.
onClick={submitReg}
Now your function will be passed an event object. Use it to stop the default behaviour of a form submission.
function submitReg(evt) {
evt.preventDefault();
console.log("formDeets", formDeets);
Related
I am using express js as backend and also have added proxy of backend in the package.json of react js. Before it used to throw an error with fetch method and moved to the axios method to prevent that. The data is in json format and completely working after copy pasting on postman to check backend.
import React ,{useState} from 'react'
import './contact.css'
import axios from "axios"
const Contact = (e) => {
const [email, setEmail] = useState('')
const [description,setDescription]=useState('');
const[message,setMessage]=useState('')
const [name ,setName] = useState('')
const url='localhost:5000/api/contact'
const contactClick=async (e)=>{
const subject="contacting"
e.preventDefault();
const formData={
name:name,
email:email,
subject:subject,
description:description
}
console.log(JSON.stringify(formData));
axios.post(url, JSON.stringify(formData))
.then(res => setMessage('email sent'))
.catch(err => {setMessage('Unable to sent email ')
return console.log(err)})
};
return (
<>
<div className='form__container' >
<form onSubmit={contactClick} className='contact__form'>
{message}
<input type="email" placeholder='Email' value={email} required onChange={(e)=>setEmail(e.target.value)
} />
<input type="text" placeholder='name' value={name} required onChange={e=>setName(e.target.value)} />
<input type="textarea" placeholder='Description' className='text-area' value={description} onChange={(e)=>setDescription(e.target.value)}/>
<input type="submit" title="Submit" />
</form>
</div>
</>
)
}
export default Contact
Unsupported protocol: localhost
Because the URL you're using has localhost as a protocol:
const url='localhost:5000/api/contact'
Compare this to a "complete" URL:
const url='http://localhost:5000/api/contact'
The URL starts with a protocol and a colon. Whatever is parsing that URL isn't going to intuitively know what you meant, it's just going to parse the string you provided based on standards.
Either specify the protocol:
const url='http://localhost:5000/api/contact'
Or omit it but keep the // root to use whatever protocol the current page is using:
const url='//localhost:5000/api/contact'
I think axios.post() takes data as object not string as mentioned here in the docs.
replace it with axios.post(url, formData) without stringifying it.
https://axios-http.com/docs/post_example
But the resulted data from JSON.stringify(formData) is '{the data}'
Is it may be cors policy blocking to be sent to backend from localhost:3000 to localhost:5000
Hi I'm new to Node/React, and I'm creating a learning project. It's platform that connects freelancers with nonprofit companies. Users (freelancers) view a list of companies, and click a button to connect to a company. Once this is clicked, the user will have that company added as a relationship in the database. This is working correctly.
Now I'm trying to have a page where the user can view all their connections (the companies they connected with). The solution below works but only if the user has at least one connection. Otherwise, I get the error Cannot read property 'length' of undefined.
To figure out which JSX to render, I'm using a conditional to see if the user has connections. If not, I wanna show "You have no connections". I'm doing this by checking if (!companies.length) then show "you have no connections". companies is set as in empty array in the state. I don't understand why it's undefined. Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error? How can I improve this code to avoid this problem?
function UserConnections() {
const { currentUser } = useContext(UserContext);
const connections = currentUser.connections;
const [companies, setCompanies] = useState([]);
useEffect(() => {
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then((comps => setCompanies(comps)));
}, [connections]);
if (!companies.length) {
return (
<div>
<p>You have no connections</p>
</div>
)
} else {
return (
<div>
{companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
country={c.country}
numEmployees={c.numEmployees}
shortDescription={c.shortDescription}
/>
))}
</div>
);
}
};
Edit: Sorry, I should've included that the error is being thrown from a different component (UserLoginForm). This error is thrown when the user who has no connections logs in. But in the UserConnections component (code above), if I change if (!companies.length) to if (!companies), the user can login fine, but UserConnections will not render anything at all. That's why I was sure the error is refering to the companies.length in the UserConnections component.
The UserLoginForm component has been working fine whether the user has connections or not, so I don't think the error is coming from here.
UserLoginForm
function UserLoginForm({ loginUser }) {
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [formErrors, setFormErrors] = useState([]);
const history = useHistory();
// Handle form submission
async function handleSubmit(evt) {
evt.preventDefault();
let results = await loginUser(formData);
if (results.success) {
history.push("/companies");
} else {
setFormErrors(results.errors);
}
}
// Handle change function
function handleChange(evt) {
const { name, value } = evt.target;
setFormData(d => ({ ...d, [name]: value }));
}
return (
<div>
<div>
<h1>User Login Form</h1>
<div>
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
className="form-control"
placeholder="Username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div>
<input
name="password"
className="form-control"
placeholder="Password"
type="password"
value={formData.password}
onChange={handleChange}
required
/>
</div>
{formErrors.length
? <Alert type="danger" message={formErrors} />
: null
}
<button className="btn btn-lg btn-primary my-3">
Submit
</button>
</form>
</div>
</div>
</div>
);
};
Edit 2: The solutions provided in this thread actually solved the problem. The error message kept persisting due to a different problem coming from another component.
Change this:
{companies.map(c => (...)}
to this:
{companies && companies.map(c => (...)}
and this:
if (!companies.length) {
to this:
if (!companies || companies.length === 0) {
This will then check for a nullish value, before running the map operation or checking length.
Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error?
Your assumption that companies is an empty array is incorrect. Somehow it is set to undefined. You can either protect against this by doing
if (!companies || companies.length == 0) {
or by making sure companies is always set to an array.
First off, I don't know if I worded this correctly. I'm new to react and trying to learn how to use hooks properly.
When I submit, I get an "invalid email" error whether it is or isn't valid.
I want to be able to show an invalid email error if it is an invalid email and prefer it to go away upon successful submission of a valid email.
I'll eventually be adding some conditions to the password too.
import React, {useState} from 'react';
import {Link, useHistory} from 'react-router-dom';
const SignUp = () => {
const history = useHistory();
const [name, setName] = useState("")
const [password, setPassword] = useState("")
const [email, setEmail] = useState("")
const [error, setError] = useState("")
const [message, setMessage] = useState("")
const PostData = ()=> {
// adding regex to validate proper email
if(!/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]#!$&'()*+,;=.]+$/.test(email)){
setError("Invalid email.")
// added this to close out the whole process if there is an error
return
}
fetch("/signup",{
method:"post",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
name,
password,
email,
})
}).then(res=>res.json())
.then(data=>{
if(data.error){
setError(data.error)
}
else{
setMessage(data.message)
history.push('/signin')
}
}).catch(err=>{
console.log(err)
})
}
return(
<div className="mycard">
<div className="auth-card">
<h2>Test</h2>
<input
type="text"
placeholder="name"
value={name}
onChange={(e)=>setName(e.target.value)}
/>
<input
type="text"
placeholder="email"
value={email}
onChange={(e)=>setEmail(e.target.value)}
/>
<input
type="text"
placeholder="password"
value={password}
onChange={(e)=>setPassword(e.target.value)}
/>
<button className="btn" onClick={()=>PostData()} >
Sign Up
</button>
{/* Show Error or Message */}
{ error && <div style={{color: "red"}}> {error}</div> }
{ message && <div style={{color: "green"}}> {message}</div> }
<h5>
<Link to="/signup">Already have an account?</Link>
</h5>
</div>
</div>
)
}
export default SignUp
The regex you have does not validate an email address, but some sort of hyperlink. Use a different regular expression, or, even better, use type="email" on the input (inside a <form>) so that the browser validates it.
Another problem is that you're never clearing error, so if the email is invalid once on submission, the error won't go away. Clear error when the email field changes:
onChange={(e) => { setError(''); setEmail(e.target.value); }}
In addition to the answer above, once you have your regex expression. you can validate the PostData function using the following.
const PostData = ()=> {
regex="regex expression"
//the regex expression for email validation goes here
if(regex.test(email){
setTimeOut(()=>{
setError("Invalid email.")
}, 3000) //using this will ensure the error message gets cleared after 3 seconds
}
else {
fetch(...)
}
}
I'm getting the following error when trying to login. The email address exists on Firebase Auth and I'm able to login, but the error weirdly only happens when event.preventDefault() and event.stopPropagation(). Those two lines are listed with the comment "(UNCOMMENT AND ISSUE GOES AWAY)".
Possible Issues:
Is there something else I am missing in my code or did I make a mistake somewhere else?
Error:
Error: A network error (such as timeout, interrupted connection or unreachable host) has occurred.
Login.js
// Imports: Dependencies
import React, { useState } from 'react';
import { Button, Container, Form, Row, Col } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
// Imports: Redux Actions
import { loginRequest } from '../../../src/redux/actions/authActions';
// Page: Admin Login
const Login = () => {
// React Hooks: State
const [ email, setEmail ] = useState('');
const [ password, setPassword ] = useState('');
// React Hooks: Redux
const dispatch = useDispatch();
// React Hooks: Bootstrap
const [ validated, setValidated ] = useState(false);
// React Hooks: React Router DOM
let history = useHistory();
// Login To Account
const loginToAccount = (event) => {
// Form Validation Target
const form = event.currentTarget;
// Check Form Validity
if (form.checkValidity() === false) {
// Cancels Event
event.preventDefault();
// Prevents Bubbling Of Event To Parent Elements
event.stopPropagation();
}
else {
// Validate Form
setValidated(true);
// Check If Fields Are Empty
if (
email !== ''
&& password !== ''
&& email !== null
&& password !== null
&& email !== undefined
&& password !== undefined
) {
// Credentials
const credentials = {
email: email,
password: password,
};
// Redux: Login Request
dispatch(loginRequest(credentials, history));
// // Cancels Event (UNCOMMENT AND ISSUE GOES AWAY)
// event.preventDefault();
// // Prevents Bubbling Of Event To Parent Elements (UNCOMMENT AND ISSUE GOES AWAY)
// event.stopPropagation();
}
}
};
return (
<div>
{/* <NavigationBar /> */}
<Container id="login-container">
<div id="login-inner-container">
<div id="login-logo-container">
<p id="login-title">Login</p>
</div>
<Form validated={validated} onSubmit={(event) => loginToAccount(event)}>
<Form.Group controlId="login-email">
<Form.Label className="form-field-title">Email</Form.Label>
<Form.Control
type={'email'}
placeholder={'Email'}
pattern={'[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,}$'}
onChange={(event) => setEmail((event.target.value).toLowerCase())}
value={email}
maxLength={50}
required
/>
<Form.Control.Feedback type="invalid">Invalid email</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="login-password">
<Form.Label className="form-field-title">Password</Form.Label>
<Form.Control
type={'password'}
placeholder={'Password'}
onChange={(event) => setPassword(event.target.value)}
value={password}
maxLength={40}
required
/>
<Form.Control.Feedback type="invalid">Required</Form.Control.Feedback>
</Form.Group>
<Button
variant="primary"
type="submit"
id="login-button"
onClick={(event) => loginToAccount(event)}
>Login</Button>
</Form>
</div>
</Container>
</div>
)
};
// Exports
export default Login;
You are registering your `` function as the submit handler for a form:
<Form validated={validated} onSubmit={(event) => loginToAccount(event)}>
When a HTML form is submitted, its default behavior is to send the data to the server as a request that navigates away from the current page. The logic here is that the server handles the request, and send a response to the client that it then renders. That's pretty much how the web worked 20+ years ago, hence it being the default behavior for HTML forms.
So with the commented out preventDefault, your code starts signing in to Firebase and then immediately navigates away (most likely just reloading the same page). This interrupts the sign-in, which is why you see the error message.
By calling event.preventDefault() you indicate that you want to prevent the default behavior (the submitting of the form to the server), since your code is handling that itself (by calling loginRequest).
Calling stopPropagation stops the browser from giving parent HTML elements the chance to act on the event. It typically shouldn't be needed to prevent the form submission, but depends a bit on the HTML that is generated.
thank you in advance for helping.
I am working on a crawler which I want to work in the following steps:
The user enters the seed url (front-end)
The user press the submit button (front-end)
The seed url will be processed at the express backend which will return a response in json (backend)
Once the seed url has been processed at the backend, json response will be sent back to the same component form and populate the input with the state object name (front-end)
I am currently having trouble with step no.4 where I managed to return the retrieved post json response from express backend but I'm not sure how to make it display back in the component after the handleSubmit function.
Here is my form:
import React from 'react';
import axios from 'axios';
class Crawler extends React.Component {
constructor(props) {
super(props);
this.state = {
seedURL: '',
};
}
handleInputChange = (e) => {
e.preventDefault();
let name = e.target.name;
let value = e.target.value;
// Do a set state
this.setState({
[name]: value
});
};
handleSubmit = (e) => {
e.preventDefault();
// The seed url taken from the input
const seedURL = this.state;
console.log("SEED URL: ", seedURL);
// Take the seedURL and send to API using axios
const url = "/api";
// Send data using axios
axios.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded';
axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
try {
axios
// url - represents the backend url from express
// seedURL - is the seed url that we want to send to the backend
.post(url, seedURL)
.then((res) => {
console.log(res);
console.log(res.data);
})
.catch((err) => {
console.log(err);
console.log(err.data);
});
} catch (e) {
console.log(e);
}
};
state = {
loading: true,
retail: null
}
render() {
return(
// Start of crawler form
<div className="crawler-form">
<form onSubmit={this.handleSubmit}>
<h2>Web Crawler</h2>
<div className="url">
<input
type="text"
placeholder="SEED URL"
name="seedURL"
onChange={this.handleInputChange}
/>
</div>
<input type="submit" value="Crawl" />
</form>
<h3>Business Information</h3>
<div className="retail-style">
<div className="name">
<input type="text" placeholder="Business Name" name="name" value={/* how do i retrieve the value */} />
</div>
</div>
</div>
// End of Crawler form
);
}
}
export default Crawler;
React will re-render your component any time the state changes. So the easy way to do this would be to add a property to the state that will hold the response:
this.state = {
seedURL: '',
response: null,
error: null,
};
Then, when you get the response from the request, you update the state:
axios
.post(url, seedURL)
.then((res) => {
this.setState({response: res, error: null});
})
.catch((err) => {
this.setState({error: err, response: null});
});
And then in your render method, you'll need to do something with the response:
render(){
const bizName = this.state.response ? this.state.response.data.name : "";
return(
//... other jsx
<div className="name">
<input type="text" placeholder="Business Name" name="name" value={bizName} />
</div>
);
}
This is how to update the input value based on the response:
...
.then((res) => {
document.querySelector('input[name="name"]')[0].value = response.data;
})
...
I assume that response.data is a text value, if its not, then you have to provide more information about response.data. like providing console.log(response.data) in your post.
You can use async/await for the promise.
First, make the handleSubmit method async:
handleSubmit = async (e) => {...
Then make sure you wait for the response from post request:
await response = axios.post(url, seedURL);
and set the data in the response to the component state.
this.setState({ value: response.data.value });
Finally, make sure use use the value in the state:
<input type="text" placeholder="Business Name" name="name" value={this.state.value} />