I'm struggling to get "lovedList" values as the page loads for the first time. When re-rendering, the list is correctly updated. How can I make it so it waits for the list values to render the first objects from useEffect?
import { Post, PostProps } from "./Post";
import { useEffect, useState, useCallback } from "react";
import { api } from "../services/api";
import { useSession } from "next-auth/client";
export function PostList() {
const [session, status] = useSession();
const [loading, setLoading] = useState(true);
const [data, setData] = useState<PostProps[]>([])
const [skip, setSkip] = useState(0)
const take = 3
let list = []
async function getUserPurchases() {
if (session) {
const response = await api.get(`/purchases/${session?.id}`)
response.data.map(purchase => {
list.push(purchase.postId)
})
}
return list
}
async function handleLoadMore() {
setLoading(true)
const lovedList = await getUserPurchases();
setSkip(skip + take)
const newPosts = await api.get(`/post?take=${take}&skip=${skip}`)
const formattedData = newPosts.data.map(post => {
console.log(lovedList)
return {
id: post.id,
image: post.image,
institution: post.institution,
title: post.title,
description: post.description,
createdAt: new Date(post.createdAt).toLocaleDateString('pt-BR', {
day: '2-digit',
month: 'long',
year: 'numeric'
}),
loved: lovedList.includes(post.id),
author: {
image: post.author.image,
name: post.author.name,
id: post.author.id
}
}
})
setData([...data, ...formattedData])
setLoading(false)
}
useEffect(() => {
handleLoadMore()
}, [])
if (loading) {
return <h1>Carregando</h1>
}
return (
<main className="flex flex-col md:max-w-3xl xl:max-w-6xl mx-auto text-gray-200 text-6xl ">
{data && data.map(post => (
<Post
key={post.id}
image={post.image}
institution={post.institution}
title={post.title}
description={post.description}
createdAt={post.createdAt}
loved={post.loved}
author={post.author}
id={post.id}
/>
))}
<button
type="button"
className="px-4 py-3 bg-blue-400 rounded-full outline-none flex items-center
justify-center text-sm my-16 mx-auto w-4/12 hover:bg-blue-500
transition-colors duration-200 ease-out"
onClick={handleLoadMore}
>
Carregar Mais
</button>
</main>
);
}
When I load the page, the console.log returns empty arrays. If the next few items load, they console.log the propper list. How can I make it so it load properly when accessing the page for the first time?
Thanks!
You can make use either one of these:-
A. use your loading state
only display data if loading=false
return (
<main className="flex flex-col md:max-w-3xl xl:max-w-6xl mx-auto text-gray-200 text-6xl ">
{!loading
? data.map(post => (
<Post
key={post.id}
image={post.image}
institution={post.institution}
title={post.title}
description={post.description}
createdAt={post.createdAt}
loved={post.loved}
author={post.author}
id={post.id}
/>
))
: 'Loading...'
}
<button
type="button"
className="px-4 py-3 bg-blue-400 rounded-full outline-none flex items-center
justify-center text-sm my-16 mx-auto w-4/12 hover:bg-blue-500
transition-colors duration-200 ease-out"
onClick={handleLoadMore}
>
Carregar Mais
</button>
</main>
);
B. set your data state initially to undefined
currently you set them as const [data, setData] = useState<PostProps[]>([])
change it to undefined, const [data, setData] = useState<PostProps[]>(). I'm not sure whether you can do it like this or not. I've never use this useState<Props>([]) syntax before
then your current condition data && data.map(post => (... will work as intended.
It didn't work before cause data was set to be []/empty array initially.
Huge thanks to Joseph from Rockeseat for the solution!
Basically, If I want to have the list ready when the page is loaded, I need to use server-side rendering. Meaning I had to use the ServerSideProps from Next in the page that is using this component.
To validate the data array you should validate the lenght, because "data" constant already exist:
data.lenght > 0
additional to this you can add a prevent to the objects "?":
post?.id
Final code:
import { Post, PostProps } from "./Post";
import { useEffect, useState, useCallback } from "react";
import { api } from "../services/api";
import { useSession } from "next-auth/client";
export function PostList() {
const [session, status] = useSession();
const [loading, setLoading] = useState(true);
const [data, setData] = useState<PostProps[]>([])
const [skip, setSkip] = useState(0)
const take = 3
let list = []
async function getUserPurchases() {
if (session) {
const response = await api.get(`/purchases/${session?.id}`)
response.data.map(purchase => {
list.push(purchase.postId)
})
}
return list
}
async function handleLoadMore() {
setLoading(true)
const lovedList = await getUserPurchases();
setSkip(skip + take)
const newPosts = await api.get(`/post?take=${take}&skip=${skip}`)
const formattedData = newPosts.data.map(post => {
console.log(lovedList)
return {
id: post.id,
image: post.image,
institution: post.institution,
title: post.title,
description: post.description,
createdAt: new Date(post.createdAt).toLocaleDateString('pt-BR', {
day: '2-digit',
month: 'long',
year: 'numeric'
}),
loved: lovedList.includes(post.id),
author: {
image: post.author.image,
name: post.author.name,
id: post.author.id
}
}
})
setData([...data, ...formattedData])
setLoading(false)
}
useEffect(() => {
handleLoadMore()
}, [])
if (loading || data?.lenght === 0) {
return <h1>Carregando</h1>
}
return (
<main className="flex flex-col md:max-w-3xl xl:max-w-6xl mx-auto text-gray-200 text-6xl ">
{data?.length > 0 && data?.map(post => (
<Post
key={post?.id}
image={post?.image}
institution={post?.institution}
title={post?.title}
description={post?.description}
createdAt={post?.createdAt}
loved={post?.loved}
author={post?.author}
id={post?.id}
/>
))}
<button
type="button"
className="px-4 py-3 bg-blue-400 rounded-full outline-none flex items-center
justify-center text-sm my-16 mx-auto w-4/12 hover:bg-blue-500
transition-colors duration-200 ease-out"
onClick={handleLoadMore}
>
Carregar Mais
</button>
</main>
);
}
Related
Description - I'm pulling a list of Material data from firestore and displaying it as a table. I've added a modal to add a Material to the database.
Problem - The refresh method I'm passing to the modal, refreshList() isn't refreshing the list of Materials on the Materials page. I'm quite certain the refresh method works—it's the same one I use to pull all Materials from firestore. what did I do wrong?
Here's the AddMaterialModal.
import { FC, useContext, useState } from "react";
import { ProjectContext } from "../../context/ProjectContext";
import MaterialService from "../../services/materialService";
import { Material } from "../../services/orgTypes";
import { randomString } from "../../utils/utils";
import AddEditTextModal from "./AddEditTextModal";
interface AddMaterialsModalProps {
editting?: boolean;
setOpenModal: (bool: boolean) => void;
selectedMaterial?: Material;
refreshList?: () => void;
}
const AddMaterialModal: FC<AddMaterialsModalProps> = ({
editting,
setOpenModal,
selectedMaterial,
refreshList,
}) => {
const idTemp = randomString(20);
const projectContext = useContext(ProjectContext);
const [newMaterial] = useState<Material>({
material: "material",
actualquantity: "actualquantity",
size: "size",
id: idTemp,
description: "description",
type: "type",
unit: "unit",
quantity: "quantity",
});
const materialService = new MaterialService(
projectContext.selectedProject.id
);
const createNewMaterial = () => {
materialService.updateCreateGlobalMaterial(newMaterial);
if (refreshList != undefined) {
refreshList();
setOpenModal(false);
}
};
return (
<div>
<div className="flex flex-col gap- 2 w-full h-fit ">
{//input some material data here.}
</div>
<div className="flex justify-center align-center">
<button
onClick={createNewMaterial}
className=" w-fit px-6 py-2.5 mt-6 bg-flowius-blue text-white font-medium text-lg leading-tight uppercase rounded shadow-md hover:bg-blue-400 hover:shadow-lg focus:bg-cyan-400 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-500 active:shadow-lg
transition duration-150 ease-in-out"
>
{editting ? "UPDATE" : "ADD +"}
</button>
</div>
</div>
);
};
export default AddMaterialModal;
And here's the main Materials page.
import { FC, useCallback, useContext, useEffect, useState } from "react";
import DataTable from "react-data-table-component";
import { ProjectContext } from "../context/ProjectContext";
import { Material } from "../services/orgTypes";
import Loader from "../components/Loader";
import MaterialIcon from "../components/MaterialIcon";
import { toast } from "react-toastify";
import MaterialService from "../services/materialService";
import Modal from "../components/modal/Modal";
import AddMaterialModal from "../components/modal/AddMaterialModal";
interface Selected {
allSelected: boolean;
selectedCount: number;
selectedRows: Material[];
}
export const formatDate = (date: Date) => {
const month = date.toLocaleString("en-us", { month: "long" });
const year = date.getFullYear();
const day = date.getDate();
return `${day} ${month} ${year}`;
};
const Materials: FC = () => {
const [materials, setMaterials] = useState<Material[]>([]);
const [loading, setLoading] = useState(true);
const [selected, setSelected] = useState<Selected>();
const projectContext = useContext(ProjectContext);
const { id } = projectContext.selectedProject;
const materialService = new MaterialService(id);
const pullMaterials = useCallback(async () => {
if (!id) return;
const materials = await materialService.getProjectMaterials();
setMaterials(materials);
setLoading(false);
}, [id]);
const [openAddMaterialModal, setOpenAddMaterialModal] = useState(false);
useEffect(() => {
pullMaterials();
}, [pullMaterials]);
const columns = [
{
name: "Material",
selector: (row: Material) => row.material,
sortable: true,
},
{
name: "Description",
selector: (row: Material) => row.description,
sortable: true,
},
{
name: "Size",
selector: (row: Material) => row.size,
sortable: true,
},
{
name: "Unit",
selector: (row: Material) => row.unit,
sortable: true,
},
{
name: "Actual Quantity",
selector: (row: Material) => row.actualquantity,
sortable: true,
},
];
const deleteSelected = async () => {
if (!selected) return;
const { selectedRows } = selected;
materialService.deleteGlobalMaterial(selectedRows);
toast.success(`${selectedRows.length} Items deleted`);
pullMaterials();
setSelected(undefined);
};
return (
<div className="m-2 w-full">
<div className="flex text-flowius-blue text-xl flex-row justify-between my-1">
<button
className="bg-flowius-blue text-base text-white p-2 rounded-md"
onClick={() => {
setOpenAddMaterialModal(true);
}}
>
Add Material
</button>
</div>
{(selected?.selectedCount ?? 0) > 0 && (
<MaterialIcon
className="cursor-pointer "
onClick={deleteSelected}
icon="delete"
/>
)}
{loading ? (
<Loader />
) : (
<DataTable
selectableRows={true}
paginationRowsPerPageOptions={[50, 100, 150]}
paginationPerPage={50}
onSelectedRowsChange={setSelected}
pagination={true}
columns={columns}
data={materials}
defaultSortAsc={true}
defaultSortFieldId="Material"
/>
)}
{setOpenAddMaterialModal && (
<Modal
open={openAddMaterialModal}
close={() => {
setOpenAddMaterialModal(false);
}}
title={"Add Material"}
className={""}
>
<AddMaterialModal
editting={false}
setOpenModal={setOpenAddMaterialModal}
refreshList={pullMaterials}
/>
</Modal>
)}
</div>
);
};
export default Materials;
I'm using Dropzone component of React-Dropzone with Nextjs and TypeScript.
I want to reject the upload if the file size is over 30MB (30000000 Bytes). The reject message could be whatever at this point.
Currently, when dropping a big file into the zone - this error appears:
I saw that there is a property called onDropRejected to use with Dropzone component in this documentation but how can we use this one instead of running into the error like above?
Here's how my UI looks like:
My code:
type Props = {
name?: string;
isError?: boolean;
onChange?: (id: string) => void;
};
export const FileUploader = ({ name, isError, onChange }: Props) => {
const [uploading, setUploading] = useState(false);
const [nameFile, setNameFile] = useState<string>('File format: CSV Maximum upload size: 30MB');
const [currId, setCurrId] = useState('');
useEffect(() => {
name && setNameFile(name);
}, [name]);
const handleDrop = async (acceptedFiles: File[]) => {
setUploading(true);
const file = acceptedFiles[0];
const res = await AssetApis.create(file, AssetResourceType.marketingAction);
if (res.id) {
setNameFile(file.name);
onChange?.(res.id);
currId && (await AssetApis.remove(currId));
setCurrId(res.id);
}
setUploading(false);
};
return (
<div>
<Dropzone
onDrop={handleDrop}
multiple={false}
accept={['image/*', 'text/csv']}
disabled={uploading}
maxSize={30000000}>
{({ getRootProps, getInputProps }) => (
<div
{...getRootProps({ className: 'dropzone' })}
className='flex items-center w-full h-40 text-center'>
<input {...getInputProps()} />
<div
className={classNames(
'rounded flex h-full items-center justify-center flex-col flex-1 border border-dashed text-gray-700 mr-2.5 py-6',
isError ? 'border-danger' : 'border-gray'
)}>
<Icon name='upload' size={20} />
<div className='mb-2.5 text-medium'>Drag and drop to upload</div>
<button
type='button'
className='px-2.5 border-gray-700 border rounded-full text-small px-3 py-0.5'>
Select file
</button>
<div className='mt-2 text-gray-500 text-small'>{nameFile}</div>
</div>
</div>
)}
</Dropzone>
</div>
);
};
you can add a function validator :
const fileValidator = (file) => {
if (file.size > 30000000) {
return {
code: "size-too-large",
message: `file is larger than 30MB`,
};
}
return null;
}
And then, add your function in your useDropZone :
const { getRootProps, getInputProps, isDragAccept, isDragReject } =
useDropzone({
onDrop,
validator: fileValidator,
});
I have authentication context provider to store login user info and show user's photo in header navigation.
I have profile edit page to edit user's display name and photo. After I update the user information to firebase and database via api, I set the user's updated info into context value again. But image on header navigation does not change until manually refresh the page or click the other routes. My I know what I have wrong to refresh the context value.
auth_prodiver.jsx
import React, { useState, useEffect, useContext, createContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { signIn, signUp, updateCurrentUser } from "../helpers/gql_auth_helpers";
import paths from "../routes/paths";
import { store } from "../store/configure_store";
import { saveUser } from "../store/slice/auth_slice";
import { auth } from "../helpers/init-firebase";
import {
getAuth,
sendEmailVerification,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
sendPasswordResetEmail,
onAuthStateChanged,
signInWithPopup,
GoogleAuthProvider,
FacebookAuthProvider,
signOut,
confirmPasswordReset,
updateProfile,
verifyPasswordResetCode,
applyActionCode,
checkActionCode,
updatePassword,
} from "firebase/auth";
const AuthContext = createContext(null);
let accessToken = "";
export const getAccessToken = () => accessToken;
export const setAccessToken = (token) => {
accessToken = token;
};
export const AuthProvider = ({ user: usr, children }) => {
const [user, setUser] = useState(usr);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user && user.emailVerified) {
setUser(user);
setAccessToken(user.getIdToken(true));
store.dispatch(saveUser(user));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
});
return () => {
unsubscribe();
};
}, []);
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
return await signIn(token);
});
}
);
}
async function register(name, email, password) {
return createUserWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await updateProfile(auth.currentUser, {
displayName: name,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
return await sendEmailVerification(auth.currentUser).then(
async function (res) {
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
await signUp(token);
});
}
);
}
})
.catch((error) => {
throw error;
});
}
);
}
async function resendEmailVerification() {
return sendEmailVerification(auth.currentUser);
}
async function forgotPassword(email) {
return sendPasswordResetEmail(auth, email, {
url: `https://myanmargita.com/login`,
});
}
async function confirmPassword(oobCode, newPassword) {
return confirmPasswordReset(auth, oobCode, newPassword);
}
async function updateCurrentUserPassword(newPassword) {
const auth = getAuth();
const user = auth.currentUser;
return updatePassword(user, newPassword);
}
async function updateUser(updateUserId, displayName, photoUrl) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
return await updateProfile(auth.currentUser, {
displayName: displayName,
photoUrl: photoUrl,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
updateCurrentUser(updateUserId, displayName, photoUrl)
.then(async(updatedUser) => {
if (updatedUser) {
await currentUser.reload();
setUser(currentUser);
setAccessToken(currentUser.getIdToken(true));
store.dispatch(saveUser(currentUser));
console.log("User Name : ", user.displayName)
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
})
.catch((error) => {
throw error;
});
}
})
.catch((error) => {
throw error;
});
}
async function resetPassword(actionCode) {
return verifyPasswordResetCode(auth, actionCode);
}
async function applyAction(actionCode) {
return applyActionCode(auth, actionCode);
}
async function checkAction(actionCode) {
return checkActionCode(auth, actionCode);
}
async function logout() {
return signOut(auth);
}
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
async function signInWithFacebook() {
const provider = new FacebookAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
const value = {
user,
accessToken,
login,
register,
forgotPassword,
confirmPassword,
logout,
signInWithGoogle,
signInWithFacebook,
resetPassword,
applyAction,
checkAction,
resendEmailVerification,
updateCurrentUserPassword,
updateUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
export const RequireAuth = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth?.user) {
return <Navigate to={paths.login} state={{ from: location }} />;
}
return children;
};
profile_edit_page.jsx
import React, { useEffect,useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as yup from "yup";
import PicturePicker from "../../components/picture_picker";
import ProgressBar from "../../controls/progress_bar";
import LoadingIndicator from "../../components/loading_indicator";
import {getUserWithId } from "../../gql/user";
import { useAuth } from "../../auth/auth_provider";
import { useQuery } from "#apollo/client";
import paths from '../../routes/paths';
import { storage} from "../../helpers/init-firebase";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
const formSchema = yup.object().shape({
name: yup.string().required("Name is required."),
email: yup.string().required("Email is required."),
});
const formData = {
name: "",
email: "",
photoURL: "",
};
const ProfileEditPage = (props) => {
const { id } = useParams();
const [uploadProgress, setUploadProgress] = useState(0);
const [pictureUrl, setPictureUrl] = useState();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const { data, loading: dataLoading, error } = useQuery(getUserWithId,{variables:{
"where": {
"id": id
}
}});
let auth = useAuth();
const handleUpdate = async (edit_data) => {
console.log("Id : "+ id);
const user_name = edit_data.name.trim();
const email = edit_data.email.trim();
const file = edit_data.photoURL;
if (pictureUrl === file) {
auth.updateUser(id, user_name, edit_data.photoURL);
} else {
const file = edit_data.photoURL;
const storageRef = ref(storage, "images/users/" + file.name);
const uploadTask = uploadBytesResumable(storageRef,file);
uploadTask.on(
"state_changed",
(snapshot) => {
var progress = Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Progrss: ", progress);
setUploadProgress({ progress });
},
(error) => {
console.log("File Upload Error: ", error.message);
throw error;
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then(async(url) => {
await auth.updateUser(id, user_name, url);
navigate(paths.getProfile(id));
});
}
);
}
};
const handleBack = () => {
navigate(-1);
};
useEffect(()=>{
if(!dataLoading && !error){
if(data?.user) {
setPictureUrl(data?.user?.photoURL);
formData.name = data?.user.displayName;
formData.email = data?.user.email;
formData.photoURL = data?.user.photoURL;
}
}
},[dataLoading,error]);
const EntryForm = () => {
return (
<Formik initialValues={formData} enableReinitialize={true} onSubmit={handleUpdate} validationSchema={formSchema}>
{({ dirty, values, errors, touched, handleChange, handleSubmit, handleReset, setFieldValue }) => {
return (
<Form autoComplete="off">
<div className="text-sm">
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Email</div>
<div className="p-2 m-2">
{values.email}
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Name</div>
<div className="p-2 m-2">
<Field
type="text"
className="p-2 w-96 textarea textarea-primary"
id="name"
name="name"
placeholder="Display Name"
value={values.name}
onChange={handleChange}
/>
<ErrorMessage name="name" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Profile Image</div>
<div className="flex flex-col items-left align-middle">
<div className="flex flex-col px-4 mt-8 mx-4 h-60 items-cente p-2 m-2" style={{ height: "250px", width: "200px" }}>
<PicturePicker url={pictureUrl} onChange={(file) => setFieldValue("photoURL", file)} value={values.photoURL} />
<ProgressBar className="px-2 pt-2 pb-1" percent={uploadProgress} />
<span className="text-red-600 self-center text-sm">{touched.photoURL && errors.photoURL}</span>
<ErrorMessage name="photoURL" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
</div>
<div className="flex flex-nowrap p-3 mt-5">
<button type="submit" onClick={handleSubmit} disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]">
Update
</button>
<button disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]" onClick={handleReset}>
Clear
</button>
</div>
</div>
</Form>
);
}}
</Formik>
);
};
return (
<div className="card">
<header className="px-5 py-4 border-b border-gray-100">
<h2 className="font-semibold text-gray-800">Edit Profile</h2>
</header>
<EntryForm />
<LoadingIndicator loading={loading} color="#000099" label="Uploading..." />
</div>
);
};
export default ProfileEditPage;
profile.jsx (profile component using in header.jsx)
import { useState, useRef, useEffect } from "react";
import {
ProfileIcon,
ResetPasswordIcon,
LogoutIcon,
} from "../assets/icons/svg_icons";
import { useAuth } from "../auth/auth_provider";
import { useNavigate } from "react-router-dom";
import paths from "../routes/paths";
import ProfileModal from "./profile_modal";
const Profile = () => {
const [isOpen, setIsOpen] = useState(false);
const [modal, setModal] = useState({
open: false,
id: "",
});
const wrapperRef = useRef(null);
const auth = useAuth();
const navigate = useNavigate();
const handleLogout = (e) => {
e.preventDefault();
auth.logout();
};
const handleResetPassword = (e) => {
e.preventDefault();
navigate(paths.resetpassword, { replace: true });
};
const handleProfile = (e) => {
e.preventDefault();
navigate(paths.getProfile(auth.user.uid));
};
const handleModalClose = async () => {
setModal({ ...modal, open: false });
};
const handleModalOpen = async () => {
setModal({ ...modal, id: auth.user.uid, open: true });
};
useOutsideClick(wrapperRef);
function useOutsideClick(ref) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(false);
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
return (
<div className="flex justify-end" ref={wrapperRef}>
<div className="relative">
<div className="cursor-pointer" onClick={() => setIsOpen(!isOpen)}>
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user && (
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-10 h-10" />
) : (
<img src={auth.user?.photoURL} />
)
) : (
<div></div>
)}
</div>
</div>
)}
</div>
</div>
</div>
{isOpen && (
<div
className="origin-top-right absolute right-0 mt-2 w-80 rounded-md shadow-lg
bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100
focus:outline-none cursor-pointer z-50"
>
<div className="py-5">
<div className="flex justify-center" onClick={handleModalOpen}>
<div className="relative text-gray-700">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-20 h-20" />
) : (
<img
className="object-cover w-20 h-20 rounded-full"
src={auth.user?.photoURL}
alt={auth.user?.displayName}
/>
)
) : (
<div></div>
)}
<div className="flex absolute right-0 bottom-0 w-8 h-8 bg-gray-400 rounded-full justify-center items-center">
<svg
className="w-6 h-6"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
>
<path
fill="currentColor"
d="M864 260H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 260H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V340c0-44.2-35.8-80-80-80zM512 716c-88.4 0-160-71.6-160-160s71.6-160 160-160s160 71.6 160 160s-71.6 160-160 160zm-96-160a96 96 0 1 0 192 0a96 96 0 1 0-192 0z"
/>
</svg>
</div>
</div>
</div>
<div className="pt-2" onClick={handleProfile}>
{auth.user ? (
<h1 className="text-sm text-gray-700 p-0 m-0">
{auth.user.displayName}
</h1>
) : (
<div></div>
)}
{auth.user ? (
<h2 className="text-sm text-gray-700 p-0 m-0">
{auth.user.email}
</h2>
) : (
<div></div>
)}
</div>
<div className="pt-4">
<button
type="submit"
className="btn btn-sm btn-ghost rounded-lg mx-6 my-1 px-4 w-auto text-black border border-gray-400"
onClick={handleProfile}
>
Manage your account
</button>
</div>
</div>
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleResetPassword}
>
<ResetPasswordIcon className="w-8 h-8" />
<span className="px-1">Reset Password</span>
</a>
</div>
<div className="divide-x" />
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleLogout}
>
<LogoutIcon className="w-8 h-8" />
<span className="px-1">Logout</span>
</a>
</div>
</div>
)}
</div>
{modal.open && (
<ProfileModal id={auth.user.uid} onClose={handleModalClose} />
)}
</div>
);
};
export default Profile;
If you are sure that url has changed add key to img tag
I am new to javascript and react. I am trying to figure out why blockHeight state variable is not accessible inside the loadNewBlocks function which triggers when the user scrolls.
Current value of blockHeight is 0 but I am expecting this value which is set in setBlockHeight(data[data.length - 1].Height); in side useEffect. For e.g. value set inside the setBlockHeight is 14789 so I am expecting 14789 inside loadNewBlocks function.
import { useState, useEffect } from "react";
import connect from "../../Backend/database/dbtest";
export default function test({ data }) {
const [blocks, setBlocks] = useState([]);
const [blockHeight, setBlockHeight] = useState(0);
console.log("top block height: ", blockHeight);
const loadNewBlocks = async () => {
console.log(
`Value in loadNewBlocks http://localhost:3000/api/fetchBlocks?blockHeight=${blockHeight}`
);
const res = await fetch(
`http://localhost:3000/api/fetchBlocks?blockHeight=${blockHeight}`
);
if (!res.ok) {
console.log("Error in fetching blocks");
return;
}
const newBlocks = await res.json();
setBlockHeight(newBlocks[newBlocks.length - 1].Height);
setBlocks((prevBlocks) => [...prevBlocks, ...newBlocks]);
};
// Load Data on Scroll
const handleScroll = async (e) => {
if (
e.target.documentElement.scrollTop + window.innerHeight >=
e.target.documentElement.scrollHeight
) {
loadNewBlocks();
}
};
useEffect(() => {
setBlocks(data);
setBlockHeight(data[data.length - 1].Height);
console.log("useEffect blockHeight", blockHeight);
}, [data]);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
return (
<div>
<button className="border-2 bg-red-400"> Submit </button>
{blocks.map((block) => (
<div key={block.blockHeader.blockhash}>
{block.blockHeader.blockhash}
</div>
))}
</div>
);
}
export async function getServerSideProps() {
const connection = await connect();
const res = await fetch("http://localhost:3000/api/fetchBlocks");
const data = await res.json();
return {
props: { data },
};
}
Here is the updated solution. Used useRef to maintain the value.
import Link from "next/link";
import connect from "../../Backend/database/dbtest";
import { useEffect, useState, useRef, useCallback } from "react";
// import read from "../../Backend/database/read";
export default function Blocks({ data }) {
const [blocks, setBlocks] = useState([]);
const HeightRef = useRef();
const isLoading = useRef(false);
const MINUTE_MS = 500000;
const loadNewBlocks = async () => {
if (!isLoading.current) {
isLoading.current = true;
console.log(
`http://localhost:3000/api/fetchBlocks?blockHeight=${HeightRef.current}`
);
const res = await fetch(
`http://localhost:3000/api/fetchBlocks?blockHeight=${HeightRef.current}`
);
const newBlocks = await res.json();
console.log("New Blocks: ", newBlocks);
HeightRef.current = newBlocks[newBlocks.length - 1].Height;
console.log("New Height: ", HeightRef.current);
setBlocks((prevBlocks) => [...new Set([...prevBlocks, ...newBlocks])]);
isLoading.current = false;
}
};
const handleScroll = async (e) => {
if (
e.target.documentElement.scrollTop + window.innerHeight >=
e.target.documentElement.scrollHeight
) {
await loadNewBlocks();
}
};
useEffect(() => {
setBlocks(data);
HeightRef.current = data[data.length - 1].Height;
window.addEventListener("scroll", handleScroll);
}, []);
return (
<div className="bg-black flex justify-center pt-[2em]">
<div className="w-full h-full bg-gradient-to-r from-indigo-700 to-sky-600 rounded-2xl text-white grid grid-rows-[4em_1fr] mx-[6em]">
<div className=" text-4xl font-bold pl-[1em] pt-[1em]">
Latest Blocks
</div>
<div className="pt-[2em]">
<div className="grid grid-cols-[1fr_3fr_1fr_1fr] font-bold h-[3em] text-xl border-b-2">
<div className="flex justify-center"> Block Height </div>
<div className="flex justify-center">Block Header</div>
<div className="flex justify-center"> Transactions </div>
<div className="flex justify-center"> Block Size </div>
</div>
{blocks.map((block) => (
<div
key={block.blockHeader.blockhash}
className="cursor-pointer grid grid-cols-[1fr_3fr_1fr_1fr] border-b-[1px] h-[4em] pt-[1em] hover:bg-gradient-to-r from-purple-600 to-blue-400 rounded-2xl"
>
<div className="flex justify-center"> {block.Height} </div>
<div className=" ">
<Link href={`/block?blockhash=` + block.blockHeader.blockhash}>
<div className="flex justify-start px-[2em]">
{block.blockHeader.blockhash}
</div>
</Link>
</div>
<div className="flex justify-center"> {block.TxCount} </div>
<div className="flex justify-center"> {block.BlockSize} </div>
</div>
))}
</div>
</div>
</div>
);
}
export async function getServerSideProps() {
const connection = await connect();
// const blocks = JSON.parse(JSON.stringify(await read.main(false, false, 20)));
const res = await fetch("http://localhost:3000/api/fetchBlocks");
const data = await res.json();
return {
props: { data },
};
}
To try to bring
useEffect(() => {
window.addEventListener("scroll", handleScroll);
- setBlocks(data);
- setBlockHeight(data[data.length - 1].Height);
}, []);
change to
useEffect(() => {
setBlocks(data);
setBlockHeight(data[data.length - 1].Height);
}, [data]);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
Make sure that only one thing is doing
By the way, a better approach would be to throttle your scrolling function
you can use the lodash throttle method
So, I expect you to do this to maintain application optimization
const MAX_TIMES = 300
const handleScroll = () => {}
useEffect(() => {
const throttleScroll = throttle(handleScroll, MAX_TIMES);
window.addEventListener('scroll', throttleScroll);
return () => {
window.removeEventListener('scroll', throttleScroll)
}
}, [])
Good Luck :)
Trying to pass down an array of uniqueWords.
On Charts initial mount uniqueWords comes in as undefineed,
I try to do a ( uniqueWords && uniqueWords) check to no success.
Although in Filter where I map through uniqueWords I use the same check and it works.
I know this may be a simple question but I am baffled.
Home
const Home = () => {
const [uniqueWords, setUniqueWords] = useState()
const [filter, setFilter] = useState(null)
const handleData = () => {
const categoryData = data.map(word => word["Parent Category"])
const uw = [...categoryData.reduce((map, obj) => map.set(obj, obj), new Map()).values()]
setUniqueWords(uw)
}
useEffect(() => {
handleData()
}, [])
return (
<div className={`w-screen h-screen bg-[#121212] text-xl text-gray-400 overflow-x-hidden`}>
<Filter
setFilter={setFilter}
uniqueWords={uniqueWords}
/>
<div className={`flex h-[70%]`}>
<Charts
uniqueWords={uniqueWords}
/>
</div>
<div className={`flex-grow bg-slate-900`}>
<DataTable filter={filter}/>
</div>
</div>
)
}
Charts - undefined error
const charts = ({uniqueWords}) => {
const [data, setData] = useState([])
const {uw} = uniqueWords && uniqueWords
const fdata = () => {
for (let i = 0; i <= uniqueWords[i].length; i++) {
setData(mdata.filter(items => items.name === uniqueWords[i]))
console.log('test')
}
}
useEffect(() => {
fdata()
}, [])
Filter - working check
const Filter = ({setFilter, uniqueWords}) => {
const handleClick = (item) => {
setFilter(item.item.toLowerCase())
}
const handleReset = () => {
setFilter(null)
}
return (<div className={`absolute top-4 left-4 flex-col shadow-xl z-20 h-min w-max`}>
<div className={`p-4 bg-slate-900`}>
{uniqueWords && uniqueWords.map(item =>
<div key={Math.random()} className={`flex items-center mb-2`}>
<input type={'checkbox'}/>
<div onClick={() => handleClick({item})} className={`ml-2 text-sm`}>{item}</div>
</div>
)}
<div className={`flex items-center w-full mt-4 rounded-md bg-slate-800`}>
<div onClick={() => handleReset()}
className={`text-md w-full text-center cursor-pointer p-2`}>Reset</div>
</div>
</div>
</div>
)
}
export default Filter
You cannot destructure array as objet.
Const {uw} = someArray
Is not valid syntax. Use [] instead of {}
This is the best option I have been able to come up with.
Although it seems really hacky.
declare const of uw using state.
only run function if uw exists.
Watch for updates on uw & uniqueWords
useEffect(() => {
if (uniqueWords) {
setUw(uniqueWords)
if (uw) {
fdata()
}
}
}, [uniqueWords, uw])