Can't upload file to firebase storage - javascript

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)

Related

React setState sets to default

I am building a simple login form page in React. The <form> has 2 inputs (email and password), an error message, and a submit button.
The submit button can be set to the Loading state during the authentication request.
So I have the following component:
function LoginForm() {
const [state, setState] = useState({ loading: false, error: "", x: 1 }); // x value only for visualising...
const auth = useAuth();
const navigate = useNavigate();
const login = async ({ email, password }) => {
try {
if (!email || !password) {
return { errors: "Invalid fields" };
}
const { errors } = await auth.authenticate(email, password);
if (!errors) {
return {};
}
console.log("LoginButton", "Error login. Not Redirecting", errors);
return {
errors: "Por favor verifique seu email e/ou senha e tente novamente.",
};
} catch (error) {
return { errors: "Unexpected error. Please, try again later." };
}
};
const inputs = [
{
name: "email",
},
{
name: "password",
type: "password",
},
];
const handleSubmit = (values) => {
setState({ ...state, loading: true, error: "", x: 2 }); // First call
login(values).then(({ errors: error }) => {
if (!error) navigate("/profile");
const newState = { loading: false, error: "Error while login", x: 3 }; // Second call
setState(newState);
});
};
useEffect(() => {
console.log(state); // Only for debugin
});
return (
<Form
inputs={inputs}
onSubmit={handleSubmit}
>
<ErrorMessage text={state.error} />
<div>
<Submit loading={state.loading}>Entrar</Submit>
<Link
to="/forgot-password"
>
Esqueceu sua senha?
</Link>
</div>
</Form>
);
}
The <Form/> component only gets the inputs array and creates the list of inputs...
The login function was called, and it set the state successfully on the first setState call (x: 2), but on the second call, the state was reset to the default value (x: 1).
Why did the second setState reset the default value? How can I fix this?
I think I've solved... But I don't Understand how...
function LoginForm() {
const [state, setState] = useState({ loading: false, error: "", x: 1 });
const auth = useAuth();
const navigate = useNavigate();
const inputs = [
{
name: "email",
},
{
name: "password",
type: "password",
},
];
const handleSubmit = async (values) => {
const { email, password } = values;
setState({ ...state, loading: true, error: "", x: 2 });
auth.authenticate(email, password).then(({ errors }) => {
if (!errors) navigate("/profile");
const newState = { loading: false, error: errors, x: 3 };
setState(newState);
});
};
useEffect(() => {
console.log(state);
});
return (
<Form
inputs={inputs}
onSubmit={handleSubmit}
>
<ErrorMessage text={state.error} />
<div>
<Submit loading={state.loading}>Entrar</Submit>
<Link
css={`
color: white;
`}
to="/forgot-password"
>
Esqueceu sua senha?
</Link>
</div>
</Form>
);
}
export default LoginForm;
This worked...

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 });
}
};

I have problem "TypeError: Cannot read property 'then' of undefined" of dispatch when using formik

I'm trying to use formik for registration/login form and catch Formik.tsx:826 Warning: An unhandled error was caught from submitForm() TypeError: Cannot read property 'then' of undefined Please check my code. I lost my .then from here...
loginForm.js
import { withFormik } from "formik";
import LoginForm from "../components/loginForm";
import store from "../../../redux/store";
import { usersActions } from "../../../redux/actions";
const LoginFormContainer = withFormik({
enableReinitialize: true,
mapPropsToValues: () => ({
email: "",
password: "",
}),
validate: (values) => {
const errors = {};
if (!values.email) {
errors.email = "Обязательное поле";
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
) {
errors.email = "Неверный email адрес";
}
if (!values.password) {
errors.password = "Введите пароль";
}
return errors;
},
handleSubmit: (values, { setSubmitting, props }) => {
store
.dispatch(usersActions.fetchUserLogin(values))
.then(({data}) => {
props.history.push('/')
})
.catch((e) => {console.log(e);});
},
displayName: "LoginForm",
})(LoginForm);
export default LoginFormContainer;
Here is my usersApi.js i'm using axios to post data on my back
import { axios } from '../../core';
export default {
signin: (postData) => axios.post('/user/signin', postData),
}
and this is my redux actions
userAction.js
import { usersApi } from "../../helpers/api";
import { notification } from "antd";
const actions = {
setUser: (data) => ({
type: "USERS:SET_DATA",
payload: data,
}),
setIsAuth: (bool) => ({
type: "USER:SET_IS_AUTH",
payload: bool,
}),
fetchUserLogin: (postData) => (dispatch) => {
usersApi
.signin(postData)
.then(({ data }) => {
console.log(data);
if (data.error) {
const openNotificationWithIcon = (type) => {
notification[type]({
message: data.error,
description: data.message,
});
};
openNotificationWithIcon("error");
} else {
const openNotificationWithIcon = (type) => {
notification[type]({
message: "Успех!",
description: "Добро пожаловать!",
});
};
openNotificationWithIcon("success");
window.axios.defaults.headers.common["token"] = data.token;
window.localStorage["token"] = data.token;
dispatch(actions.setUser(data));
dispatch(actions.setIsAuth(true));
}
return data;
})
.catch((e) => {
const openNotificationWithIcon = (type) => {
notification[type]({
message: "Что то пошло не так",
description: "Попробуйте ещё раз позже",
});
};
openNotificationWithIcon("error");
dispatch(actions.setIsAuth(false));
});
},
export default actions;
I'm looking for hours and can't find problem.

Todo-list using useReducer and MongoDB - updating DB after Action of Reducer?

I tried connecting my Todolist with MongoDB to store my data.
Since it is not recommended to fetch and update data within the reducer I'm a little confused on where to update the DB.
My thoughts are whenever I change the List with an Action, I would need to Replace the whole list on the DB, which doesnt seem efficient to me.
But when i only update only the changed element in the DB i dont see a reason to use useReducer.
Can someone help me how i should continue? :/
(This Todolist was using useStates and MongoDB before I tried exercising on useReducer, that's why the Routes and APIhelper include other functions)
App.js:
import React, { useState, useEffect, useReducer } from "react";
import APIHelper from "./APIHelper.js";
import Todo from "./components/Todo";
import "./index.css";
export const ACTIONS = {
ADD_TODO: "add-todo",
TOGGLE_TODO: "toggle-todo",
DELETE_TODO: "delete-todo",
SET_TODO: "set-todos",
};
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.SET_TODOS: {
return Object.assign({}, state.todos, {
todos: action.payload.todos,
});
}
case ACTIONS.ADD_TODO:
return [...state.todos, newTodo(action.payload.task)];
case ACTIONS.TOGGLE_TODO:
return state.todos.map((todo) => {
if (todo._id === action.payload.id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
case ACTIONS.DELETE_TODO:
return state.todos.filter((todo) => todo._id !== action.payload.id);
default:
return state.todos;
}
};
const newTodo = (task) => {
return { _id: Date.now(), task: task, completed: false };
};
export const setTodos = (todos) => {
return {
type: ACTIONS.SET_TODOS,
payload: {
todos,
},
};
};
const App = () => {
const initialState = {
todos: [],
};
const [state, dispatch] = useReducer(reducer, initialState);
const [task, setTask] = useState("");
useEffect(async () => {
const fetchTodoAndSetTodo = async () => {
const todos = await APIHelper.getAllTodos();
return todos;
};
const todos = await fetchTodoAndSetTodo();
//console.log(todos);
dispatch(setTodos(todos));
}, []);
const handleSubmit = (e) => {
e.preventDefault();
dispatch({ type: ACTIONS.ADD_TODO, payload: { task: task } });
setTask("");
};
return (
<div>
{console.log(state.todos)}
<form onSubmit={handleSubmit}>
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
/>
</form>
{state.todos &&
state.todos.map((todos) => {
return <Todo key={todos._id} todo={todos} dispatch={dispatch} />;
})}
{//APIHelper.updateTodo(state.todos)}
</div>
);
};
export default App;
Todo.js:
import React from "react";
import { ACTIONS } from "../App";
const Todo = ({ todo, dispatch }) => {
return (
<div>
<span style={{ color: todo.complete ? "#AAA" : "#000" }}>
{todo.task}
</span>
<button
onClick={() =>
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })
}
>
Toggle
</button>
<button
onClick={() =>
dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })
}
>
Delete
</button>
</div>
);
};
export default Todo;
APIHelper.js:
import axios from "axios";
const API_URL = "http://localhost:8080/todos/";
const createTodo = async (task) => {
const { data: newTodo } = await axios.post(API_URL, {
task,
});
return newTodo;
};
const deleteTodo = async (id) => {
const message = await axios.delete(`${API_URL}${id}`);
return message;
};
const updateTodo = async (payload) => {
const { data: newTodo } = await axios.put(`${API_URL}`, payload);
return newTodo;
};
const getAllTodos = async () => {
const { data: todos } = await axios.get(API_URL);
return todos;
};
export default { createTodo, deleteTodo, updateTodo, getAllTodos };
routes.js:
const db = require("./db.js");
const routes = express.Router();
const success = (res, payload) => {
return res.status(200).json(payload);
};
routes.get("/", async (req, res, next) => {
try {
const todos = await db.Todo.find({}, "_id task completed");
return success(res, todos);
} catch (err) {
next({ status: 400, message: "failed to get todos" });
}
});
routes.post("/", async (req, res, next) => {
try {
const todo = await db.Todo.create(req.body);
return success(res, todo);
} catch (err) {
next({ status: 400, message: "failes to create todo" });
}
});
routes.put("/", async (req, res, next) => {
try {
const todo = await db.Todo.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
return success(res, todo);
} catch (err) {
next({ status: 400, message: "failed to update todo" });
}
});
routes.delete("/:id", async (req, res, next) => {
try {
await db.Todo.findByIdAndRemove(req.params.id);
return success(res, "todo deleted");
} catch (err) {
next({ status: 400, message: "failed to delete todo" });
}
});
routes.use((err, req, res, next) => {
return res.status(err.status || 400).json({
status: err.status || 400,
message: err.message || "there was an error processing request",
});
});
module.exports = routes;
```

How to manually remove Firebase firestore listeners?

I understand from this SO answer, that we must manually remove Firebase listeners. How can I do that in the following use case? My successful attempt is shown in the below code.
I tried to use some of the ideas from this answer too. But unsuccessfully.
What am I doing wrong?
import React, { Component } from 'react';
// redacted for brevity
import firebase from '#firebase/app';
import '#firebase/firestore';
class CRUDContainer extends Component {
state = {
items: [],
path: null,
isError: false,
isLoading: true,
};
componentWillUnmount () {
// cancel subscriptions and async tasks to stop memory leaks
this.unsubscribe(this.path);
}
unsubscribe = path => path && firebase.firestore().collection(path).onSnapshot(() => {})
getItems = path => {
const out = [];
const db = firebase.firestore();
if(!db) return;
db.collection(path)
.orderBy('timestamp', 'desc')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
out.push(doc.data());
});
return out;
})
.then(result => {
const newState = {
path,
items: result,
isError: false,
isLoading: false,
};
this.setState(newState);
return result;
})
.then(() => {
this.unsubscribe(path);
return path;
})
.catch(error => {
console.error('Error getting documents: \n', error);
const newState = {
isError: true,
isLoading: false,
};
this.setState(newState);
});
};
Child = ({ match: { params: { id }}}) => {
// redacted for brevity
getItems(path);
return (
this.state.isLoading
?
<Loading/>
:
(
this.state.isError
?
<ErrorMessage/>
:
(items && (
<CRUDView items={items} />
)))
)
};
render() {
return <Route path="/:id" component={this.Child} />
}
}
export default CRUDContainer;

Categories