Resetting Password with React Native, Supabase, and Auth Flow - javascript

I've been having trouble resetting the password in my React Native app that uses Supabase and an auth flow. The App function uses the useUser hook to retrieve a Boolean value called AppStacktrue. If it's true, the app is rendered, and if it's false, the auth Stack is rendered:
const AuthStack = () => {
const screenOptions = {
headerShown: false
}
const Stack = createNativeStackNavigator()
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='OpeningScreen' screenOptions={screenOptions}>
<Stack.Screen name='OpeningScreen'component={Home}/>
<Stack.Screen name='Signup' component={Signup}/>
<Stack.Screen name='Login' component={Login}/>
<Stack.Screen name='VerifyOTP' component={AnimatedExample}/>
<Stack.Screen name='Reset-Password' component={ResetPassword}/>
</Stack.Navigator>
</NavigationContainer>
)
}
const AppStack = () => {
const screenOptions = {
headerShown: false
}
const Stack = createNativeStackNavigator()
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='OpeningScreen' screenOptions={screenOptions}>
<Stack.Screen name='OpeningScreen'component={HomeScreen}/>
</Stack.Navigator>
</NavigationContainer>
)
}
export default function App() {
const {AppStacktrue} = useUser()
return (
AppStacktrue?<AppStack/>:<AuthStack/>
);
}
The issue with password reset is that the user must be logged in to change their password. To resolve this, I created the AppStacktrue value in my user.js file. This hook allows the user to log in, log out, sign up, and verify their OTP, but I also want the user to be able to reset their password.
To solve the problem, I created a useEffect that controls the AppStacktrue state. In the verify function (which is used for password reset and sign up), I set a state called Reset to true, but it returns to its initial value automatically. This might be a bug with the onAuthStateChange function. My goal was for the AppStacktrue to be false when the function is active.
I'm not sure what the best solution is to handle the password reset in my case. Any suggestions or ideas would be greatly appreciated!
import { useEffect, useState } from "react";
import { Alert } from "react-native";
import { supabase } from "./supabase";
const useUser = () => {
const [session, setSession] = useState();
const [loading, setLoading] = useState();
const [AppStacktrue, setAppStacktrue] = useState()
const [reset, setReset] = useState()
useEffect(() => {
const getUserProfile = async () => {
const session = await getSession();
if (session) setSession(session.data.session);
}
getUserProfile();
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, []);
useEffect(()=>{ //UseEffect to set the app Stack
console.log(reset)
if(session){
if(reset === true){
setAppStacktrue(false)
}else{
setAppStacktrue(true)
}
}else{
setAppStacktrue(false)
}
}, [session, reset])
async function getSession() {
const session = await supabase.auth.getSession();
return session;
};
const verify = async (email, token, forgotPassword, navigation) => { //here is the problem
setLoading(true)
setReset(true) //returns to inital value
const { error } = await supabase.auth.verifyOtp({
email,
token,
type: forgotPassword ? "recovery" : "signup",
});
setLoading(false)
if (error) {
if (error.status === 400)
return Alert.alert("Bitte gebe volständig Daten an.");
else if (error.status === 401)
return Alert.alert(
"Ungültiger Code",
"Dieser Code ist entweder Abgelaufen, falsch oder du hast versucht einen bereits vorhanden Account zu registrieren. Logge dich in diesem Fall ein.",
[
{
text: "Einloggen",
onPress: () => {
navigation.push("Login");
},
},
{
text: "Schließen",
},
]
);
else return Alert.alert("", error.message);
}
};
async function logout() {
await supabase.auth.signOut()
};
async function login(email, password) {
if (!email || !password)
return Alert.alert("Leere Felder", "Bitte fülle etwas aus.");
setLoading(true)
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
setLoading(false)
if (error) {
if (error.status === 400)
return Alert.alert(
"Falsche Anmeldedaten",
"Deine Email-Adresse oder dein Passwort ist falsch"
);
else Alert.alert(error.message);
}
};
async function signUp(email, password, confirmPassword, Name, navigation) {
if (!Name || !email || !password || !confirmPassword)
return Alert.alert("Bitte gebe vollständige Daten an");
if (!/^[A-Za-z]+ [A-Za-z]+$/.test(Name))
return Alert.alert(
"Ungültiger Name",
"Dein Name muss aus Vor und Nachname bestehen."
);
if (password !== confirmPassword)
return Alert.alert("Deine Passwörter stimmen nicht überein");
if (password.length < 6)
return Alert.alert("Dein Passswort muss mindestens 6 Zeichen lang sein");
setLoading(true);
const { data: profile, error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
full_name: Name,
},
},
});
setLoading(false);
if (error) {
if(error.status === 422){
return Alert.alert('Email adresse kann nicht vearbeitet werden', 'Deine Email-Adresse ist in ungültigem Format')
}
}
else {
if (!profile.user.email) return Alert.alert("Keine Email erhalten");
navigation.push("VerifyOTP", {
email: profile.user.email,
});
}
};
async function resetPassword(email, navigation) {
if (!email) return Alert.alert("Leeres Feld", "Bitte gebe Etwas ein.");
setLoading(true)
const { error } = await supabase.auth.resetPasswordForEmail(email);
setLoading(false)
if (error) {
if (error.status === 400)
return Alert.alert(
"Falsche Anmeldedaten",
"Deine Email-Adresse oder dein Passwort ist falsch"
);
else if (error.status === 429)
return Alert.alert(
"Bitte warte einen Moment",
"Aus sicherheitsgründen kann dies nur alle 60 Sekunden ausgeführt werden."
);
else Alert.alert(error.message);
} else {
navigation.navigate("VerifyOTP", {
email,
forgotPassword: true,
});
}
};
return { AppStacktrue, login, signUp, resetPassword, verify, logout, loading};
};
export default useUser;

Related

handleRegister not returning value, undefined

I'm trying to return a value from my handleRegister function, but it gives me undefined, I checked before returning the value and it exists but as soon as I return, it's undefined. Can someone please help me? The object exists:
{
"signup": {
"name": "riced",
"id": 7135,
"email": "riced#gmail.com",
"password": "U24jg2xwSbF4R6k",
"error": null,
"__typename": "User"
}
}
The following part fails:
handleRegister(this.state).then(
(data) => {
console.log(data);
return;
});
import * as React from "react"
import { navigate } from "gatsby"
import {isLoggedIn } from "../services/auth"
import fetch from 'isomorphic-fetch';
import {ApolloClient, HttpLink, InMemoryCache, gql} from '#apollo/client';
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: 'http://10.0.0.212:9000/',
fetch
})
});
export const REGISTER_USER = gql
mutation Mutation($signupInput: SignUpInput) {
signup(signupInput: $signupInput) {
name
id
email
password
error
}
}
;
export async function handleRegister ({ username, email, password }) {
/*
if (username === `john` && password === `pass`) {
return setUser({
username: `john`,
name: `Johnny`,
email: `johnny#example.org`,
})
*/
/*
client.query({ query: ALL_USERS }).then(result => console.log(result));
*/
//let [errors, setErrors] = React.useState([]);
var gottenToken = "";
//let gottenName;
await client.mutate({ mutation: REGISTER_USER, variables: {
"signupInput": {
"name": username,
"password": password,
"email": email,
}
} }).then(result => {
console.log(result);
console.log(result.data!.signup);
//let signup = JSON.stringify(result.data!.signup);
return result.data!.signup
//Promise.resolve(result.data!.signup);
}).catch(err => {
console.log(err);
//setUser({})
//React.useEffect(() => {
//localStorage.setItem('signupError', JSON.stringify(err));
//}, [errors]);
//return haveErrorOccured("Signing up error"); // send error to browser
});
//return haveErrorOccured("Signing up error"); // send error to browser
}
class Register extends React.Component {
state = {
username: ``,
email: ``,
password: ``,
}
handleUpdate = async event => {
await this.setState({
[event.target.name]: event.target.value,
})
}
handleSubmit = async event => {
event.preventDefault()
//handleRegister(this.state)
/*
handleRegister(this.state).then(r => {
console.log(r);
}).catch(err => {
console.log(err);
});
*/
handleRegister(this.state).then(
(data) =>{
console.log(data);
return;
//return 43;
});
//console.log("signed up: " + signup);
//console.table(signup);
}
render() {
if (isLoggedIn()) {
navigate(`/app/profile`)
}
//let errorSignup;
//if(window.localStorage.getItem("signupError")){
// errorSignup = JSON.stringify(JSON.parse(window.localStorage.getItem("signupError")));
//}
//navigate("/app/register?registered", { state: { foo: "bar" }});
//<p>{errorSignup}</p>
return (
<>
<h1>Register</h1>
<form
method="post"
onSubmit={event => {
this.handleSubmit(event);
}}
>
<label>
Username
<input type="text" name="username" onChange={this.handleUpdate} />
</label>
<label>
Email
<input type="text" name="email" onChange={this.handleUpdate} />
</label>
<label>
Password
<input
type="password"
name="password"
onChange={this.handleUpdate}
/>
</label>
<input type="submit" value="Log In" />
</form>
</>
)
}
}
export default Register

console error :Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'data') at handleClick

In this page the user can login, but if the untilDate is bigger than the current date it should log out the user. The code runs fine 1/2 times, the other giving me the error on the title.
I am working with createContext for user login. This is the AuthContext file
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
When the user clicks the login button, it runs the handleClick function:
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
let date = new Date().toJSON();
let userdate = date;
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
userdate = user.activeUntil;
//do if date is <=current datem dispatch logout
} catch (err) {
if (userdate > date) {
console.log("undefined data");
} else {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
}
if (userdate > date) {
dispatch({ type: "LOGOUT" });
console.log("If you are seeing this your contract has expired");
} else {
// navigate("/myinfo");
}
};
The console error happens from this line dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
Is there a way I can bypass this error or a different way I can write my code to make it work?
This is the full code of login page
import React from "react";
import axios from "axios";
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { AuthContext } from "../../context/AuthContext";
import {
Container,
FormWrap,
FormContent,
Form,
FormInput,
FormButton,
Icon,
FormH1,
SpanText,
IconWrapper,
IconL,
} from "./signinElements";
import Image from "../../images/Cover.png";
const Login = () => {
const [credentials, setCredentials] = useState({
namekey: undefined,
password: undefined,
});
/* */
// to view current user in console
const { user, loading, error, dispatch } = useContext(AuthContext);
let msg;
const navigate = useNavigate();
const handleChange = (e) => {
setCredentials((prev) => ({ ...prev, [e.target.id]: e.target.value }));
};
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
let date = new Date().toJSON();
let userdate = date;
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
userdate = user.activeUntil;
//do if date is <=current datem dispatch logout
} catch (err) {
if (userdate > date) {
console.log("undefined data");
} else {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
}
if (userdate > date) {
dispatch({ type: "LOGOUT" });
console.log("If you are seeing this your contract has expired");
} else {
// navigate("/myinfo");
}
};
// console.log(user.activeUntil); //type to view current user in console
return (
<>
<Container>
<IconWrapper>
<IconL to="/">
<Icon src={Image}></Icon>
</IconL>
</IconWrapper>
<FormWrap>
<FormContent>
<Form action="#">
<FormH1>
Sign in with the namekey and password written to you on your
contract.
</FormH1>
<FormInput
type="namekey"
placeholder="Namekey"
id="namekey"
onChange={handleChange}
required
/>
<FormInput
type="password"
placeholder="Password"
id="password"
onChange={handleChange}
/>
<FormButton disabled={loading} onClick={handleClick}>
Login
</FormButton>
<SpanText>{msg}</SpanText>
{error && <SpanText>{error.message}</SpanText>}
{error && (
<SpanText>
Forgot namekey or password? Contact our support team +355 69
321 5237
</SpanText>
)}
</Form>
</FormContent>
</FormWrap>
</Container>
</>
);
};
export default Login;
The problem was i was trying to call a localy stored user and 1 time it wasnt loaded and the other it was. Simply fixed it by changing the if statement to check directly in result details without having to look in local storage.
const [expired, setExpired] = useState(false);
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let date = new Date().toJSON();
if (res.data.details.activeUntil < date) {
dispatch({ type: "LOGOUT" });
console.log("Users contract has expired");
setExpired(!expired);
} else {
navigate("/myinfo");
}
} catch (err) {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
};

Login React + Express + mongoDB doesn't work properly

I have an issue with the login authentication in my project. I connected my react front end with my express back end, but when I try to login with valid credentials, it gets stuck in an error seems like it can't read properly the value I'm passing in my form input.
Here's my LogIn page:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { signin } from '../actions/authAction';
import { clearErrors } from '../actions/errAction';
import { Button, Form, FormGroup, Input, Alert } from 'reactstrap'
import TopCont from '../components/TopCont'
class Signin extends Component {
state = {
email: '',
password: '',
msg: null
};
static propTypes = {
isAuth: PropTypes.bool,
signin: PropTypes.func.isRequired,
err: PropTypes.object.isRequired,
clearErrors: PropTypes.object.isRequired
};
componentDidMount(){
this.props.clearErrors();
}
componentDidUpdate(prevProps){
const { err } = this.props;
if(err !== prevProps.err) {
if(err.id === 'LOGIN_FAIL'){
this.setState({ msg: err.msg.msg });
} else {
this.setState({ msg: null });
}
}
};
onChange = e => {
this.setState({
[e.target.email]: e.target.value,
[e.target.password]: e.target.value
});
};
onSubmit = e => {
e.preventDefault();
const { email, password } = this.state;
const user = {
email,
password
};
this.props.signin(user);
/* this.props.push('/dashboard'); */
};
render() {
return (
<>
<TopCont>
<div className="signin-cont">
<h1>Accedi</h1>
{this.state.msg ? <Alert color="danger">{this.state.msg}</Alert> : null }
<Form className="signin-form-cont" onSubmit={this.onSubmit}>
<FormGroup>
<Input className="signin-form" type="email" name="email" id="email" placeholder="mario.rossi#prova.it" onChange={this.onChange}/>
</FormGroup>
<FormGroup>
<Input className="signin-form" type="password" name="password" id="password" placeholder="Password" onChange={this.onChange}/>
</FormGroup>
<Button className="sign-btn">Accedi</Button>
</Form>
<p>Non hai ancora un account? <Link to="/signup">Registrati</Link></p>
</div>
</TopCont>
</>
)
}
}
const mapStateToProps = state => ({
isAuth: state.auth.isAuth,
err: state.err
});
export default connect(mapStateToProps, { signin, clearErrors })(Signin);
Here is my Action:
import axios from 'axios';
import { returnErrors } from './errAction';
import { AUTH_ERROR, LOGIN_FAIL } from '../actions/types';
export const signin = ({ email, password }) => dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
axios.post('/api/auth', body, config)
.then(res => dispatch({
type: LOGIN_SUCCESS,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status, 'LOGIN_FAIL'));
dispatch({
type: LOGIN_FAIL
});
});
};
And here is my Auth API:
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const config = require('config');
const jwt = require('jsonwebtoken');
const auth = require('../../middleware/auth');
const User = require('../../models/User');
//#action POST api/auth
//#descr auth user
//#access Public
router.post('/', (req, res) => {
const { email, password } = req.body
if( !email || !password ) {
return res.status(400).json({ msg: "Enter all fields."});
}
User.findOne({ email })
.then(user => {
if(!user) return res.status(400).json({ msg: "Nessun profilo trovato con questa email"});
bcrypt.compare( password, user.password )
.then(isMatch => {
if(!isMatch) return res.status(400).json({ msg: "Password errata!"});
jwt.sign(
{ id: user.id },
config.get('jwtSecret'),
{ expiresIn: 10800 },
(err, token) => {
if(err) throw err;
res.json({
token,
user: {
id: user.id,
name: user.name,
surname: user.surname,
email: user.email,
userPlus: user.userPlus
}
})
}
)
})
})
});
//#action GET api/auth/user
//#descr GET user data
//#access Private
router.get('/user', auth, (req, res) => {
User.findById(req.user.id)
.select('-password')
.then(user => res.json(user));
});
module.exports = router;
If I try to get rid of all the errors controls my server return this error:
TypeError: Cannot read property 'password' of null
The strange thing is that if I try to register a new user (with an almost identical component and server-side method) there are no problems and it authenticates too with no problems.
Someone knows how can I fix this issue?
Change your onChange Function from this:
onChange = e => {
this.setState({
[e.target.email]: e.target.value,
[e.target.password]: e.target.value
});
};
to this:
onChange = e => this.setState(prevState => ({
...prevState,
[e.target.name]: e.target.value,
}));

Change password reducer in react app(axios + redux + jwt + bcrypt)

I want to change password, while I'm logged in.
Here's my function:
authActions.js(without catch because that will be implemented if anything start to works)
// Change password
export const changePassword = (newPassword) => (dispatch, getState) => {
// Headers
const config = {
headers: {
'Content-Type': 'application/json'
}
}
axios.post(`/api/auth/user/changePassword/`, newPassword, tokenConfig(getState))
.then(res => dispatch({
type: CHANGE_PASSWORD,
payload: res.data
}))
}
// Setup config/headers and token
export const tokenConfig = getState => {
// Get token from localstorage
const token = getState().auth.token;
// Headers
const config = {
headers: {
// "Accept": "application/json, multipart/form-data"
"Content-type": "application/json"
}
}
// If token, add to headers
if (token) {
config.headers['x-auth-token'] = token;
}
return config;
}
and authReducer.js:
...
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: false,
user: null
};
export default function (state = initialState, action) {
switch (action.type) {
...
case CHANGE_PASSWORD:
return {
...state,
token: null,
user: action.payload,
isAuthenticated: false,
isLoading: false
};
default:
return state;
}
}
and routes/api/auth.js
router.post('/user/changePassword/', (req, res) => {
console.log(req.body);
const { email, oldPassword, newPassword } = req.body
// find if old password is valid
User.findOne({ email })
.then(user => {
bcrypt.compare(oldPassword, user.password)
.then(isMatch => {
if (isMatch) {
// change to new password
user.password = newPassword
user
.save()
.then(newUser => {
res.status(200).send(newUser)
})
.catch(err => {
const message = err.message
res.status(500).json({
status: "change password failed",
msg: message
})
})
} else {
return res.status(401).send("Invalid old password")
}
})
})
.catch(err => {
res.status(500).send(err)
})
});
I have console.log(req.body); in routes just to check if anything works, but it don't works(didn't give me any message).
And component in the end(but it's not the source of problem):
import React, { useState, useEffect } from 'react';
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input,
NavLink
} from 'reactstrap';
import { connect } from 'react-redux';
import { changePassword } from '../../actions/authActions';
import PropTypes from 'prop-types';
const ChangePassword = ({ auth }) => {
const [modal, setModal] = useState(false);
const [enterPassword, setEnterPassword] = useState({
oldPassword: '',
newPassword: ''
});
const [takeEmail, setTakeEmail] = useState(null);
useEffect(() => {
const createArray = () => {
const { user } = auth;
setTakeEmail({ email: user.email });
};
createArray();
}, [auth.user]);
const toggle = () => {
setModal(!modal);
};
const onChange = e => {
setEnterPassword({
...enterPassword,
[e.target.name]: e.target.value
});
};
const onSubmit = (event) => {
event.preventDefault();
const { email } = takeEmail;
const { oldPassword, newPassword } = enterPassword;
console.log(enterPassword);
console.log(takeEmail);
const newUser = {
email,
oldPassword,
newPassword
}
// Add content via changePassword action
changePassword(newUser);
toggle();
}
return (
<div>
<NavLink onClick={toggle} href="#">
Change Password
</NavLink>
<Modal
isOpen={modal}
toggle={toggle}
className="open-modal"
>
<ModalHeader toggle={toggle}>Dodaj do listy ogłoszeń</ModalHeader>
<ModalBody>
<Form onSubmit={onSubmit}>
<FormGroup>
<Label for="oldPassword">Nagłówek</Label>
<Input
type="password"
name="oldPassword"
id="oldPassword"
placeholder="Wprowadź stare hasło..."
onChange={onChange}
/>
<Label for="newPassword">Nagłówek</Label>
<Input
type="password"
name="newPassword"
id="newPassword"
placeholder="Wprowadź stare hasło..."
onChange={onChange}
/>
<Button
color="dark"
style={{ marginTop: '2rem' }}
block>
Zmień hasło
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
ChangePassword.propTypes = {
isAuthenticated: PropTypes.bool,
changePassword: PropTypes.func.isRequired
}
const mapStateToProps = state => ({
auth: state.auth,
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps, { changePassword })(ChangePassword);

Can't upload file to firebase storage

I'am trying to upload files to firebase in react, But the file upload progress reaches 100% then suddenly it shows me an unknown error like
{
"error": {
"code": 400,
"message": "Bad Request. Could not create object",
"status": "CREATE_OBJECT"
}
}
this is the code I'm using to upload the file, This is the actual component where file uploading is done, The user opens a modal to select a file and then after selecting and pressing send in the modal the file uploading starts in the below component.
import React, { Component } from "react";
import { Segment, Button, Input, ButtonGroup } from "semantic-ui-react";
import firebase from "../../firebase";
import FileModal from "./FileModal";
import uuidv4 from "uuid/v4";
class MessageForm extends Component {
state = {
storageRef: firebase.storage().ref(),
message: "",
channel: this.props.currentChannel,
user: this.props.currentUser,
loading: false,
errors: [],
modal: false,
uploadState: "",
uploadTask: null,
percentUploaded: 0
};
uploadFile = (file, metadata) => {
const pathToUpload = this.state.channel.id;
const ref = this.props.messagesRef;
const filePath = `chat/public/${uuidv4}.jpg`;
this.setState(
{
uploadState: "uploading",
uploadTask: this.state.storageRef.child(filePath).put(file, metadata)
},
() => {
this.state.uploadTask.on(
"state_changed",
snap => {
const percentUploaded = Math.round(
(snap.bytesTransferred / snap.totalBytes) * 100
);
this.setState({ percentUploaded });
},
err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
uploadTask: null
});
},
() => {
console.log(this.state.uploadTask);
this.state.uploadTask.snapshot.ref
.getDownloadURL()
.then(downloadUrl => {
this.sendFileMessage(downloadUrl, ref, pathToUpload);
})
.catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
uploadTask: null
});
});
}
);
}
);
};
sendFileMessage = (fileUrl, ref, pathToUpload) => {
ref
.child(pathToUpload)
.push()
.set(this.createMessage(fileUrl))
.then(() => {
this.setState({
uploadState: "done"
}).catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err]
});
});
});
};
openModal = () => {
this.setState({
modal: true
});
};
closeModal = () => {
this.setState({
modal: false
});
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
createMessage = (fileUrl = null) => {
const message = {
timestamp: firebase.database.ServerValue.TIMESTAMP,
user: {
id: this.state.user.uid,
name: this.state.user.displayName,
avatar: this.state.user.photoURL
}
};
if (fileUrl != null) {
message["image"] = fileUrl;
} else {
message["content"] = this.state.message.trim();
}
return message;
};
sendMessage = () => {
const { messagesRef } = this.props;
const { message, channel } = this.state;
if (message) {
this.setState({
loading: true
});
messagesRef
.child(channel.id)
.push()
.set(this.createMessage())
.then(() => {
this.setState({
loading: false,
message: "",
errors: []
});
})
.catch(err => {
console.error(err);
this.setState({
loading: false,
errors: [...this.state.errors, err]
});
});
} else {
this.setState({
errors: [...this.state.errors, { message: "Add a message" }]
});
}
};
render() {
const { errors, message, loading, modal } = this.state;
return (
<Segment className="message__form">
<Input
fluid
name="message"
style={{ marginBottom: "0.7em" }}
icon="add"
iconPosition="left"
placeholder="Write your message"
onChange={this.handleChange}
className={
errors.some(error => error.message.includes("message"))
? "error"
: ""
}
value={message}
/>
<ButtonGroup icon widths="2">
<Button
onClick={this.sendMessage}
disabled={loading}
color="orange"
content="Add reply"
labelPosition="left"
icon="edit"
/>
<Button
color="violet"
content="Upload Media"
labelPosition="right"
icon="cloud upload"
onClick={this.openModal}
/>
<FileModal
modal={modal}
closeModal={this.closeModal}
uploadFile={this.uploadFile}
/>
</ButtonGroup>
</Segment>
);
}
}
export default MessageForm;
Just a guess, but I suspect that your error might be related to the way you are storing the uploadTask in the component's state... and it makes me pretty uncomfortable - it seems to violate one of the core principles of using component state in React.
As you've probably heard already state should only be mutated via the setState command... and the problem with your approach is that the uploadTask portion of the state will be mutated during the upload execution. In fact, your code counts on it - you've written it so that as the uploadTask is updated, its percentage gets displayed on screen.
Overall, you've got the right idea - just take that uploadTask: this.state.storageRef.child(filePath).put(file, metadata) assignment out of your state... something like this:
uploadFile = (file, metadata) => {
const pathToUpload = this.state.channel.id;
const ref = this.props.messagesRef;
const filePath = `chat/public/${uuidv4}.jpg`;
this.setState(
{
uploadState: "uploading",
},
() => {
let uploadTask = this.state.storageRef.child(filePath).put(file, metadata);
uploadTask.on(
"state_changed",
snap => {
const percentUploaded = Math.round(
(snap.bytesTransferred / snap.totalBytes) * 100
);
this.setState({ percentUploaded });
},
err => {
console.error(err);
this.setState({
errors: [...errors, err],
uploadState: "error",
});
},
() => {
console.log(uploadTask);
uploadTask.snapshot.ref
.getDownloadURL()
.then(downloadUrl => {
this.sendFileMessage(downloadUrl, ref, pathToUpload);
})
.catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
});
});
}
);
}
);
};
(Untested code, conceptual only)

Categories