I don't know if I need to use global state like useContext for this ( I am not using redux in this project) but what I want to do is, once I have uploaded a new image it sends the image data back from the server and I set that state. I want to then replace the existing image on the screen with the newly uploaded one.
So, here is my file input component:
import React, { useState } from "react";
import ProgressBar from "../../../shared/components/progressBar/ProgressBar";
const UploadForm = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const types = ["image/png", "image/jpg", "image/jpeg"];
const changeHandler = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError("");
} else {
setFile(null);
setError("Please select an image file(png or jpg");
}
};
return (
<form>
<input type="file" onChange={changeHandler} name="image"></input>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</form>
);
};
export default UploadForm;
My progress bar component:
import React, { useEffect } from "react";
import useStorage from "../../hooks/use-storage";
import { motion } from "framer-motion";
import "./ProgressBar.css";
const ProgressBar = ({ file, setFile }) => {
const { url, progress } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<motion.div
className="upload-progress"
initial={{ width: 0 }}
animate={{ width: progress + "%" }}
></motion.div>
);
};
export default ProgressBar;
And, my upload custom hook. You can see here I am setting the state of the image url here but I don't know how to then update my Avatar component once the upload is complete.
import React, { useState, useEffect, useContext } from "react";
import Axios from "axios";
import { AuthContext } from "../context/auth-context";
const useStorage = (file) => {
const auth = useContext(AuthContext);
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
const formData = new FormData();
formData.append("image", file);
try {
const sendImage = async () => {
const response = await Axios.post(
"http://localhost:8000/api/v1/users/update-avatar",
formData,
{
headers: {
"Content-type": "multipart/form-data",
Authorization: "Bearer " + auth.token,
},
onUploadProgress: (progressEvent) => {
setProgress(
parseInt(
Math.round((progressEvent.loaded * 100) / progressEvent.total)
)
);
},
}
);
// get the new file name from the server so you can show it right away after upload
const { filename, path } = response.data.file;
setUrl({ filename, path });
};
sendImage();
} catch (err) {
setError(err);
console.log(err.response);
}
}, [file]);
return { progress, url, error };
};
export default useStorage;
Avatar component
import React, { useContext, useEffect, useState } from "react";
import Axios from "axios";
import { AuthContext } from "../../../shared/context/auth-context";
const Avatar = () => {
const auth = useContext(AuthContext);
const [avatar, setAvatar] = useState(null);
useEffect(() => {
const getAvatarImage = async () => {
const response = await Axios.get(
"http://localhost:8000/api/v1/users/get-avatar",
{ headers: { Authorization: "Bearer " + auth.token } }
);
setAvatar(response.data.avatar);
};
if (auth.token) getAvatarImage();
}, [auth.token]);
return (
<div>
{avatar && (
<img
src={`http://localhost:8000/uploads/images/${avatar}`}
width="200"
alt="avatar image"
/>
)}
</div>
);
};
export default Avatar;
Auth Context
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
token: null,
email: null,
firstName: null,
login: () => {},
logout: () => {},
});
Related
I have custom hook which is catching data from dummyjson API. When I render products, it works fine and perfectly. When I try to catch only one product with this hook via parameter passed via url with useParams in the end it catch this one product, but it cannot render. It seems that a single product didn't manage to load with the help of the hook before it renders. So what is difference when all products are catched are rendered correctly
import axios, { Canceler } from 'axios';
import { useEffect, useState } from 'react';
import { dummyProductType } from '../types/types';
export const useFetch = ({ limit, id }: any) => {
const [products, setProducts] = useState<dummyProductType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
let cancel: Canceler;
const config =
id === null || id === undefined
? {
method: 'GET',
url: `https://dummyjson.com/products/`,
params: { limit: limit },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
}
: {
method: 'GET',
url: `https://dummyjson.com/products/${id}`,
cancelToken: new axios.CancelToken((c) => (cancel = c)),
};
async function fetchData() {
setIsLoading(true);
{
await axios(config)
.then((response) => {
if (Object.hasOwn(config, 'params')) {
setProducts((prev) => {
return [...prev, ...response.data.products];
});
} else {
setProducts({ ...response.data });
}
if (products.length < response.data.total) setHasMore(true);
setIsLoading(false);
})
.catch((err) => {
if (axios.isCancel(err)) return;
setError(true);
});
}
}
fetchData();
return () => cancel();
}, [limit, id]);
return { products, isLoading, error, hasMore };
};
import React, { useCallback, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
import { CardProduct } from '../CardProduct';
import styles from './Cards.module.scss';
const { wrapperContainer } = styles;
const Cards = () => {
const [limit, setLimit] = useState(10);
const { products, isLoading, hasMore } = useFetch({ limit: limit });
const observer = useRef<IntersectionObserver | null>(null);
const lastProduct = useCallback(
(node: Element) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setLimit((prev) => prev + 10);
}
});
if (node) observer.current.observe(node);
},
[isLoading, hasMore]
);
console.log(products);
return (
<div className={wrapperContainer}>
{products.map((product, index) => {
if (products.length === index + 1) {
return (
<Link to={`books/${index + 1}`}>
<CardProduct
key={`${index} ${product.title}`}
{...product}
innerRef={lastProduct}
/>
</Link>
);
} else {
return (
<Link to={`books/${index + 1}`}>
<CardProduct key={`${index} ${product.title}`} {...product} />
</Link>
);
}
})}
</div>
);
};
export default Cards;
import {
Button,
CardContent,
Card,
CardHeader,
CardMedia,
dividerClasses,
} from '#mui/material';
import { useParams } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
export const CardDetail = () => {
const { id } = useParams();
console.log(id);
const { products, isLoading, hasMore } = useFetch({
id: Number.parseInt(id),
});
console.log(products, isLoading, hasMore);
return (
<Card key={id}>
<CardHeader title={products[0].title}></CardHeader>
<CardMedia
component='img'
image={products[0].thumbnail}
sx={{ height: '150px' }}></CardMedia>
</Card>
);
};
What am I doing wrong? Or maybe it should be done different?
im calling an object from the pokeapi, exactly the name property and on first render after saving the file i get the name but i dont know why, re render and then the propertie is null and i get an error
this is my component card
import {
EditOutlined,
EllipsisOutlined,
SettingOutlined,
} from "#ant-design/icons";
import { Avatar, Card, Col, Row } from "antd";
function Pokecard(values: any) {
const { response} = values;
const { Meta } = Card;
return (
<Row gutter={[10, 10]}>
<Col>
<Card
style={{ width: 300 }}
cover={
<img
alt={"" }
src={response && response['sprites']['front_default']}
/>
}
actions={[
<SettingOutlined key="setting" />,
<EditOutlined key="edit" />,
<EllipsisOutlined key="ellipsis" />,
]}
>
<Meta
avatar={<Avatar src="https://joeschmoe.io/api/v1/random" />}
title={response.name}
description=""
/>
</Card>
</Col>
</Row>
);
}
export default Pokecard;
this is my view
import { Methods } from "../interfaces/request";
import { useEffect, useState } from "react";
import Pokecard from "../components/pokecard/Pokecard";
import useAxios from "../plugins/Useaxios";
function App2() {
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const [data, setData] = useState([]);
useEffect(() => {
if (response !== null) {
setData(response);
}
}, [response]);
let args: any = {
response,
};
return (
<>
<Pokecard {...args} />;
</>
);
}
export default App2;
and this is my plugin axios
import axios from "axios";
import Request from "../interfaces/request";
import { useState, useEffect } from "react";
enum Methods {
get = "get",
post = "post",
default = "get",
}
const useAxios = ({ url, method, body, headers }: Request) => {
axios.defaults.baseURL = "https://pokeapi.co/api/v2/pokemon";
const [response, setResponse] = useState(null);
const [error, setError] = useState("");
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](url, JSON.parse(headers), JSON.parse(body))
.then((res: any) => {
setResponse(res.data);
})
.catch((err: any) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
im learning to destructuring objects
im tried saving the object in the store but i got an Undifined
sorry for my english
you can try something like this
title={response?.name || ''}
Try using the resonse directly
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const name = response?.name;
const src = response?.sprites?.?front_default;
// use the properties directly inside the child
return (
<>
<Pokecard name={name} src={src}/>
</>
);
You can check examples of how when useEffect is not needed
//Newtask.js
import React, { useCallback } from "react";
import { useState } from "react";
import useHttp from "../hookes/useHttp";
import Taskform from "./taskform";
const newTask = (props) => {
const createTask = (tastk, taskdata) => {
const data = { id: taskdata.name, text: tastk };
props.onAddTask(data);
};
const { isloading, error, sendRequest: fetchRequest } = useHttp();
const enterTaskHandler = async (tasktext) => {
fetchRequest(
{
url: "https://react-prep-d4d1d-default-rtdb.firebaseio.com/comments.json",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: { text: tasktext },
},
createTask.bind(null, tasktext)
);
};
return (
<section>
<Taskform onEnterTask={enterTaskHandler} loading={isloading} />
{error && <p>{error}</p>}
</section>
);
};
export default newTask;
Here above i am calling TaskForm compoenet and passing arguments ( props)
//taskform.js
import React from "react";
import { useState } from "react";
import "./taskforms.css";
function Taskform(props) {
const [text, settext] = useState();
const Inputchangehandler = (event) => {
settext(event.target.value);
};
console.log(props.onEnterTask);
const submithandler = (text) => {
props.onEnterTask(text);
settext("");
};
return (
<div>
<input
type="text"
value={text}
onChange={Inputchangehandler}
className="asit"
/>
<button className="asit" onClick={submithandler}></button>
</div>
);
}
export default Taskform;
//Here in taskform i am calling the props.onEnterTask(text)....but i am getting error
Error : Uncaught TypeError: props.onEnterTask is not a function
//useHttp.js ( hook)
import { useState, useCallback } from "react";
const useHttp = () => {
const [isLoading, setisLoading] = useState();
const [error, setError] = useState(null);
const sendRequest = useCallback(async (requestConfig, applydata) => {
setisLoading(true);
setError(null);
try {
const response = await fetch(requestConfig.url, {
method: requestConfig.method ? requestConfig.method : "GET",
headers: requestConfig.headers ? requestConfig.headers : {},
body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
});
if (!response.ok) {
throw new Error("Request failed!");
}
const data = await response.json();
applydata(data);
} catch (err) {
setError(err.message || "Something went wrong!");
}
setisLoading(false);
}, []);
return {
isLoading,
error,
sendRequest,
};
};
export default useHttp;
Please kindly go through this above files and let me know the issue.
Sorry the issue is not with props , some other component issue was there in app file , i was giving some other componet as start file //////
I am trying to create gallary app and when i try to upload image in fire storage there is only one image but in fire store there are two entries for same image so i think it is happening because it uploads image two times i try to figure out but nothing is working
Here is my use storage hook
import React, { useState, useEffect } from 'react'
import { fireStorage, fireStore } from '../firebase-config';
import { collection, addDoc, serverTimestamp } from "firebase/firestore";
import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage";
export default function useStorage(file) {
const [progresspercent, setProgresspercent] = useState(0);
const [error, setError] = useState(null);
const [url, setImgUrl] = useState(null);
useEffect(() => {
const storageRef = ref(fireStorage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on("state_changed",
(snapshot) => {
const progress =
Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgresspercent(progress);
},
(error) => {
setError(error);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setImgUrl(downloadURL)
addDoc(collection(fireStore, 'images'),{
url: downloadURL,
createdAt: serverTimestamp()
})
});
}
);
},[file]);
return { progresspercent, url, error};
}
Here is my upload form
import { useState } from "react";
import ProgressBar from './ProgressBar'
function UploadForm() {
const [file, setFile] = useState(null);
const [error, setError] = useState("");
const allowedType = ["image/png", "image/jpg", "image/jpeg"];
const changeHandler = (e) => {
e.preventDefault()
let selectedFile = e.target.files[0]
if (selectedFile && allowedType.includes(selectedFile.type)){
setFile(selectedFile);
setError('')
}else{
setFile(null);
setError("Please select an image file");
}
};
return (
<form>
<label>
<input type="file" onChange={changeHandler} />
<span>+</span>
</label>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</form>
);
}
export default UploadForm;
and Here is my progress bar
import React, { useEffect } from "react";
import useStorage from "..//../src/Hooks/useStorage";
export default function ProgressBar({ file, setFile }) {
const { url, progresspercent } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<div
className="progress-bar"
style={{ width: progresspercent + "%" }}
></div>
);
}
I don't have any other component, i try to fix it but i don't know why it is happening at first place.
Can't figure out an efficient way to set a user's profile and then pass that data onwards to other components as needed.
Below is an example of my current logic, and although the app works, the rendering is not efficient. When I click on various parts of my app, the data coming from my UserProfile component is re-rendering every time causing the text to change from the initial text to the rendered data text.
The main issue, I believe, is the communication between the UserProfile and Dashboard Home snippets below. I'm new to the useEffect logic, so I would imagine the inefficient setup is with that hook.
Any help or nudge in the right direction is appreciated!
Thanks
AuthContext file => Setting the current user
import React, { useContext, useState, useEffect } from 'react';
import firebase from 'firebase/app';
import {
auth,
signInWithGoogle,
createUserProfileDocument,
firestore,
} from '../firebase.utils';
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password);
};
const logout = () => {
setCurrentUser(null);
return auth.signOut();
};
const resetPassword = email => {
return auth.sendPasswordResetEmail(email);
};
const updateEmail = email => {
return currentUser.updateEmail(email);
};
const updatePassword = password => {
return currentUser.updatePassword(password);
};
const deleteProfile = () => {
currentUser.delete();
firestore.doc(`users/${currentUser.uid}`).delete();
};
const updateName = displayName => {
return currentUser.updateProfile({
displayName: displayName,
});
};
const setName = displayName => {
return auth.currentUser.updateProfile({
displayName: displayName,
});
};
const googleSignIn = () => {
const google = signInWithGoogle();
setCurrentUser(google);
return google;
};
const updatePersonalSettings = data => {
createUserProfileDocument(currentUser, data);
};
const updateAccountSettings = data => {
createUserProfileDocument(currentUser, data);
};
console.log(currentUser);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
updateName,
setName,
googleSignIn,
updatePersonalSettings,
updateAccountSettings,
deleteProfile,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
UserProfile file => Setting the userInfo
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
const setUserData = async () => {
if (currentUser) {
const userRef = await createUserProfileDocument(currentUser);
userRef.onSnapshot(doc => {
setUserInfo({
id: doc.id,
...doc.data(),
});
});
}
};
useEffect(() => {
setUserData();
}, []);
return { userInfo };
};
export default UserProfile;
Dashboard home file => Example of rendering data from the UserProfile component
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import sprite from '../../../../assets/sprite.svg';
import UserProfile from '../../../../user-profile/user-profile';
import './home-dashboard.styles.scss';
const HomeDashboard = () => {
const { userInfo } = UserProfile();
const handleCurrentLevel = () => {
return !userInfo || userInfo.currentLevel === undefined ? (
<h1>Welcome! Start your eval to see your level</h1>
) : (
<h1>Current Level: {userInfo.currentLevel}</h1>
);
};
const handleCurrentLevelCard = () => {
return !userInfo || userInfo.currentLevel === undefined
? 'Start a new eval to see your level'
: `You scored a ${userInfo.currentLevel} in your last eval`;
};
return (
<div className="home-dash">
<div className="home-dash__title">{handleCurrentLevel()}</div>
<div className="home-dash__cards">
<div className="home-dash__card-1">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-success'}></use>
</svg>
<h3 className="home-dash__card-title">
Latest Eval Results
</h3>
<div className="home-dash__card-result-text">
{handleCurrentLevelCard()}
</div>
<Link to="/eval-quiz">
<button className="home-dash__card-btn--purple">
Start New Eval
</button>
</Link>
</div>
{/* TODO Add resutls to firestore */}
{
<div className="home-dash__card-2">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-lightbulb'}></use>
</svg>
<h3 className="home-dash__card-title">
Areas to practice
</h3>
<div className="home-dash__card-result-text">
We recommend working on reading skills
</div>
{/*<button className="home-dash__card-btn--blue">
Practice
</button>*/}
<div className="home-dash__coming-soon">
Coming soon!
</div>
</div>
}
</div>
</div>
);
};
export default HomeDashboard;
Firestore/Firebase setup
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
});
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = await userRef.get();
const { displayName, email, photoURL } = userAuth;
const createdAt = new Date();
if (!snapShot.exists) {
console.log(displayName);
try {
await userRef.set({
displayName,
photoURL,
email,
createdAt,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
if (snapShot.exists) {
try {
await userRef.update({
displayName,
email,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
return userRef;
};
export const firestore = firebase.firestore();
const googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.setCustomParameters({ prompt: 'select_account' });
export const signInWithGoogle = () => auth.signInWithPopup(googleProvider);
export const auth = app.auth();
export default app;
Consider this answer a draft, I've made it public in case you can piece it together but I'll update it with some documentation in the morning.
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!currentUser) {
// user is signed out
setUserInfo(null);
setLoading(false);
return;
}
const userRef = /* ... */;
setUserInfo(undefined); // clear stale data
setLoading(true); // update loading status
return userRef.onSnapshot({
next(snapshot) {
if (!snapshot.exists) {
userRef
.set({
/* new data */
})
.catch((err) => {
// TODO: Handle errors
});
return;
}
setUserInfo({
id: snapshot.id,
...snapshot.data(),
});
setLoading(false);
},
error(err) {
// TODO: Handle errors
}
});
}, [currentUser]);
return { userInfo }; // <-- this seems odd? missing loading?
};
export default UserProfile;