Following issue:
I am trying to create an authentication system in react, which let's a user login, sign up and reset the password. The problem is, I want to let the user go certain routes such as /profile, but display the content if he is logged in. If he is not logged in, I want to simply display the navigation bar and a small login component.
So far I tried something like this: (In order to make it more readable I left out the action-blocks)
const Profile = ({ history }) => {
const [values, setValues] = useState({
username: "",
email: "",
password: ""
});
const [isShowingAuth, setIsShowingAuth] = useState(null);
const { username, email, password } = values;
useEffect(() => {
if (!isLoggedIn()) {
setIsShowingAuth("login");
} else {
loadProfileData();
}
}, []);
const loadProfileData = () => {
const userEmail = isLoggedIn().email;
const token = getCookie("token");
axios({
method: "GET",
url: `${process.env.REACT_APP_BACKEND_URL}/user`,
headers: { Authorization: `Bearer ${token}` },
data: { userEmail }
})
.then(response => {
// Do something
})
.catch(error => {
// Do something
});
};
const handleChange = name => event => {
setValues({ ...values, [name]: event.target.value });
};
const handleUpdateUserSubmit = e => {
e.preventDefault();
const token = getCookie("token");
axios({
method: "PUT",
url: `${process.env.REACT_APP_BACKEND_URL}/user/update`,
headers: { Authorization: `Bearer ${token}` },
data: { username, password, email }
})
.then(response => {
// Do something
})
.catch(error => {
// Do something
}
store.addNotification({
...defaultSettings,
type: "danger",
title: "User update error",
message: error.response.data.errorMsg
});
});
};
const deleteAccount = () => {
const token = getCookie("token");
axios({
method: "DELETE",
url: `${process.env.REACT_APP_BACKEND_URL}/user`,
headers: { Authorization: `Bearer ${token}` }
})
.then(response => {
// Do something
})
.catch(error => {
// Do something
};
const showDeleteConfirmation = () => {
// Do something
};
return (
<Layout isShowingAuth={isShowingAuth}>
{isLoggedIn() && isLoggedIn().role === "member" ? (
<>
<ReactNotifications />
<h1 className='mt-5 pl-3'>Profile</h1>
<form className='form col-md-6 mt-3'>
<div className='form-group'>
<label>Name</label>
<input
type='text'
className='form-control'
value={username}
onChange={handleChange("username")}
/>
</div>
<div className='form-group'>
<label>Email</label>
<input
type='email'
className='form-control'
value={email}
onChange={handleChange("email")}
disabled
/>
</div>
<div className='form-group'>
<label>Password</label>
<input
type='password'
className='form-control'
value={password}
onChange={handleChange("password")}
placeholder='Enter your new password'
autoComplete='on'
/>
</div>
<button type='submit' className='btn btn-outline-primary' onClick={handleUpdateUserSubmit}>
Update User
</button>
<button type='button' className='btn btn-outline-danger ml-3' onClick={showDeleteConfirmation}>
Delete account
</button>
<button type='button' className='btn btn-outline-danger ml-3 mt-2 mt-md-0'>
Want to change your email?
</button>
</form>
{showAlert}
</>
) : null}
</Layout>
);
};
Basically I am checking through the isLoggedIn() method if the user is logged in. If he is, I want to render the content, if not I am returning only the layout. The layout contains the navigation bar.
The problem now is, that once the user logs in while being on the route /profile, the component doesn't remount, which means the useEffect(()=> {},[]) is not called again and my profile data is not loaded.
I already tried to refresh the page once logged in through history.push(history.location.pathname), but this doesn't trigger a remount either.
I'm no wondering how to properly trigger a remount after login, and if the way I am setting my authentication system up is secure or if there are better solutions.
Any help / feedback appreciated thanks :)
Related
I have an api that is of the GET type and I receive the data below code and store it in fetchPlants
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchPlants = async () => {
try {
const { data } = await axios.get(
"URL",
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
setAllPlants(data.plantsList);
} catch (error) {
console.error(error.message);
}
};
fetchPlants();
}, []);
And I have an api of POST type that I send with the below code and I put loading inside it
When it is sent successfully, the page is directed and I have no problem, but I get an error when setLoading is set to false and refresh page, the input values are empty and the user has to enter the input values again.
const sendDisease = async (e) => {
e.preventDefault();
setIsLoading(true);
try {
const api = "URL";
const { data } = await axios.post(api, formData, {
headers: {
headers: { "Content-Type": "multipart/form-data" },
Authorization: `Bearer ${token}`,
},
});
if (data) {
setIsLoading(false);
navigate("/dashboard/diseases/");
toast.success(data.success, { autoClose: 15000 });
}
} catch (e) {
setIsLoading(false);
console.log(e);
toast.error(e.response.data.error, { autoClose: 15000 });
console.log(e.response.data.error);
}
};
this is my form:
<div className="form-row mt-4">
<div className="col-md-6 mb-3">
<label className="font-weight-bold" for="">
Unique Name
</label>
<input
type="text"
className="form-control"
name="unique_name"
placeholder="Unique Name"
required
onChange={(e) => setUniqueName(e.target.value)}
/>
</div>
<div className="col-md-6 mb-3">
<label className="font-weight-bold" for="">
Title
</label>
<input
type="text"
className="form-control"
name="title"
placeholder="Title"
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
</div>
<button
type="button"
className="btn btn-primary mt-4"
onClick={sendDisease}
>
Submit
</button>
</form>
I want the values not to be clear, but when it goes false, the input values are empty. how to resolve that?
I can't figure out why my form is populating my backend struct with empty strings. I can only get it to send the data correctly if I replace my onSubmit={handleSubmit} with method="POST" action="/createLink"
But even then it seems to send two form inputs.. one with empty values and one populated correctly. Backend is in Go.
const Home = () => {
//create state variables
const [media, setMedia] = React.useState([])
const [url, setURL] = React.useState(null)
const [headline, setHeadline] = React.useState(null)
const [description, setDescription] = React.useState(null)
const [type, setType] = React.useState(null)
const [duration, setDuration] = React.useState(null)
const [address, setAddress] = React.useState(null)
//HOOKS
//get media already submitted today to display on the front end
useEffect(() => {
fetch("/getMedia", {
method: "GET",
headers: {
"Content-Type" : "application/json",
},
})
.then((res) => res.json())
.then((data) => {
console.log(data)
setMedia(...media, data);
});
}, []);
//direct user back to home page
let navigate = useNavigate()
const handleHome = () => {
navigate("/")
}
//submit media
const handleSubmit = (e) => {
e.preventDefault()
fetch("/createLink", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
url: url,
headline: headline,
description: description,
type: type,
duration: duration,
address: address,
}),
})
console.log(address, "why")
handleHome()
}
//method="POST" action="/createLink"
return (
<>
<Div>
<H1>Submit External Media</H1>
<form onSubmit={handleSubmit} >
<Field>
<label htmlFor="url">URL Submission:</label>
<Input type="text" id="url" name="url" required onChange={(e) => {
setURL(e.target.value)
}}></Input>
</Field>
<Field>
<label htmlFor="headline">Headline:</label>
<Input type="text" id="headline" name="headline" required onChange={(e) => {
setHeadline(e.target.value)
}}></Input>
</Field>
<Field>
<label htmlFor="description">Description:</label>
<Input type="text" id="description" name="description" required onChange={(e) => {
setDescription(e.target.value)
}}></Input>
</Field>
<Field>
<label htmlFor="type">Type of Media:</label>
<select id="type" name="type" onChange={(e) => {
setType(e.target.value)
}}>
<option value="article" >Article</option>
<option value="tweet" >Tweet</option>
<option value="yt-video" >YouTube Video</option>
</select>
</Field>
<Field>
<label htmlFor="duration">Duration:</label>
<select id="duration" name="duration" onChange={(e) => {
setDuration(e.target.value)
}}>
<option value="15">15 Hours</option>
<option value="48" >48 Hours</option>
<option value="two-weeks" >Two Weeks</option>
</select>
</Field>
<Field>
<label htmlFor="location-lng">Street Address:</label>
<Input type="text" id="location-lng" name="location-lng" onChange={(e) => {
setAddress(e.target.value)
console.log(address)
}}></Input>
</Field>
<Button type="submit">SUBMIT</Button>
</form>
<h2>Media Submitted Today:</h2>
{ media.length >= 1 ? (
media.map((mediaItem) => {
const headline = mediaItem.headline
const description = mediaItem.description
const url = mediaItem.url
return <Media headline={headline} description={description} url={url} />
})
) : (
<>
<p>Looks like no external media was submitted yet today!</p>
</>
)}
</Div>
</>
)
}
export default Home;
One reason it might work with method="POST", but it is not working with your javascript function is that the default content type for a form is application/x-www-form-urlencoded.
In handleSubmit it sets the type to application/json, which your backend may not be expecting, and is not decoding properly.
Please consider this:
console.log everything before sending and validate input values are correct
The Input component seems to be Uncontrolled, validate if state if being set correctly
fetch is async and you're re-directing & pushing browser history state the user without waiting for the fetch call to end
Do this for advice 3:
//direct user back to home page
let navigate = useNavigate()
const handleHome = () => {
navigate("/")
}
//submit media
const handleSubmit = (e) => {
e.preventDefault()
fetch("/createLink", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
url: url,
headline: headline,
description: description,
type: type,
duration: duration,
address: address,
}),
}).then(() => handleHome()) // We are sure data was sent
}
Do this for advice 1:
//direct user back to home page
let navigate = useNavigate()
const handleHome = () => {
navigate("/")
}
//submit media
const handleSubmit = (e) => {
e.preventDefault()
const data = {
url,
headline,
description,
type,
duration,
address,
}
console.log({ data }) // Check the dev console
// JS Enhanced Object literals.
// https://www.sitepoint.com/es6-enhanced-object-literals
fetch("/createLink", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(() => handleHome()) // We are sure data was sent
}
I have a unique React Js form I am trying to validate before submission.
The issue is my onClick function is doing two other processes.
I am fetching data and redirecting. I am trying to validate the phone before any of the two processes takes action.
The phone/email/name field should not be empty, if empty the fetch or redirect must not take action.
By default, the number starts with +27.
Methods I have tried, Joi Form, React-Form-Hood, and statement
Here is the code:
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import Page from "react-page-loading";
function Checkout({ cart, setResponse }) {
const [contactPreference, setContactPreference] = useState("");
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const navigate = useNavigate();
const [spinner, setSpinner] = useState(false);
const handleSubmit = async () => {
setSpinner(true);
const token = "2c506c6b-d880-36bb-bdba-a035d1464b35";
const data = {
transactionReference: "string",
paymentMethod: "CreditCard",
checkoutOrderUrl: "http://www.test.com/",
user: {
name: name,
msisdn: "+27" + phone,
email: email,
},
payementMethodDetail: {
RedirectUrl: "http://localhost:3000/",
PurchaseEventWebhookUrl: "http://www.test.com",
},
bundle: cart.map((item) => ({
ProductCode: `${item.ProductCode}`,
Amount: item.amount,
CurrencyCode: item.currencyCode,
Quantity: item.quantity,
})),
};
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
};
await fetch(
"https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/checkout/",
requestOptions
)
.then((response) => response.json())
.then((response) => {
setResponse(response);
})
.then(() => {
setSpinner(false);
navigate("/payment");
});
};
console.log();
return (
<div className="App">
<div>
Name: <input onChange={(event) => setName(event.target.value)} />
<form className="form-inline">
<label className="my-1 mr-2" for="inlineFormCustomSelectPref">
How should we contact you ??
</label>
<select
onChange={(e) => {
setContactPreference(e.target.value);
}}
className="custom-select my-1 mr-sm-2"
id="inlineFormCustomSelectPref"
>
<option selected>Choose...</option>
<option value="phone">Phone Number</option>
<option value="email">Email</option>
</select>
{contactPreference === "phone" ? (
<input
placeholder="+27"
onChange={(event) => setPhone(event.target.value)}
/>
) : contactPreference === "email" ? (
<input
placeholder="Email"
onChange={(event) => setEmail(event.target.value)}
/>
) : (
<></>
)}
</form>
</div>
<button type="submit" onClick={handleSubmit}>
Proceed to Payment
</button>
{spinner && <Page loader={"spin"} color={"#b2fa00"} size={4}></Page>}
</div>
);
}
export default Checkout;
You can add the required attribute to input and select elements, put a ref on the form element, and in your handleSubmit function, use
const formValid = formRef.current?.reportValidity()
to get a boolean result and have the browser display hints on missing fields.
I have created an app with a simple login/register form that will give a user a token and stores it in localStorage. I also have a logout function in place which will remove the token from storage. Everything works as planned however, if I press the form button to login or to register, it will still give access to the dashboard until the page is refreshed.
Login.js
const Login = ({ setAuth }) => {
const [inputs, setInputs] = useState({
email: '',
password: '',
});
const { email, password } = inputs;
//Handle form inputs
const changeHandler = (e) => {
setInputs({
...inputs,
[e.target.name]: e.target.value,
});
};
//Form submit handler
const submitHandler = async (e) => {
e.preventDefault();
try {
const body = { email, password };
const response = await fetch('http://localhost:5000/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
//Retrive token from URL
const parseRes = await response.json();
//Save token in local storage then give user authorization
localStorage.setItem('token', parseRes.token);
setAuth(true);
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<div className='container'>
<h1 className='text-center my-5'>Login</h1>
<form onSubmit={submitHandler}>
<input
type='email'
name='email'
placeholder='Email'
className='form-control my-3'
value={email}
onChange={(e) => changeHandler(e)}
/>
<input
type='password'
name='password'
placeholder='Password'
className='form-control my-3'
value={password}
onChange={(e) => changeHandler(e)}
/>
<button className='btn btn-success w-100'>Login</button>
</form>
<Link to='/register'>Register</Link>
</div>
</Fragment>
);
};
In the console after trying to login with an empty form, I get 2 errors from my server side routes: POST http://localhost:5000/auth/login 401 (Unauthorized) and GET http://localhost:5000/dashboard/ 401 (Unauthorized). These errors are expected and working as they are supposed to.
Is there something wrong with my form or could this be in another area of my code?
My app calls a Login api and returns me a token,i stored the token in localStorage so my problem is how i validade if the user has a token to do the login. what can i do to do that?
this is my login Page where i used to add the token to the localStorage
import React, { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { login } from '../services/login.services';
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const history = useHistory();
const loginHandler = async () => {
const result = await login(email, password);
console.log(result);
localStorage.setItem('token', result?.response?.result?.token);
localStorage.getItem('token');
};
return (
<section className='layout'>
<div className='wrp-login'>
<div className='container'>
<h1 color='white'>Login Page</h1>
<div className='col-sm-6 offset-sm-3'>
<input
type='text'
placeholder='email'
onChange={(e) => setEmail(e.target.value)}
className='input-wrapper'
/>
<br />
<input
type='password'
placeholder='password'
onChange={(e) => setPassword(e.target.value)}
className='input-wrapper'
/>
<br />
<button onClick={() => loginHandler()} className='button'>
Login
</button>
</div>
</div>
</div>
</section>
);}export default Login;
this is my Login Service wher i do the api call and return data
export const login = async (email, password) => {
try {
const result = await fetch(
'teste.com',
{
method: 'Post',
body: JSON.stringify({ login: email, senha: password }),
headers: {
'Content-Type': 'application/json',
},
mode: 'cors',
cache: 'default',
},
);
return await result.json();
} catch (err) {
return err;
}};
Someone could help me?
You can validate and redirect users by creating a wrapper for protected routes with react-router-dom to the login page if they don't have the token stored this way:
const ProtectedRoute = (props) => {
const token = localStorage.getItem('token');
if (token == null) {
return <Redirect to={Routes.LOGIN} />;
}
return <>{props.children}</>;
};