So I have this blog application where I have to sort the blogs depending on the number of likes it has. This works fine when I first log in the app to see the blogs and also whenever I refresh the page. More specifically, when I first log into the app and when I refresh the page everything is/gets sorted. Is there anyway to update/sort the blog list on the fronted without refreshing the page?
import { useState, useEffect } from 'react'
import Blog from './components/Blog'
import Login from './components/Login'
import CreateBlog from './components/CreateBlog'
import blogService from './services/blogs'
import loginService from './services/login'
import './app.css';
const App = () => {
const [blogs, setBlogs] = useState([]) // Unsorted array
const [user, setUser] = useState(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [errorMsg, setErrorMsg] = useState('')
const [msg, setMsg] = useState('')
const [sortedArray, setSortedArray] = useState(null) //This sorts the original blog array
const [visibility, setVisibility] = useState(false)
useEffect( () => {
if(user != null){
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
}
console.log("blogs is", blogs)
}, [user,sortedArray])
useEffect(() => {
const loggedInUser = window.localStorage.getItem('loggedBlogUser')
console.log("loggedInUser is", loggedInUser)
if(loggedInUser){
const user = JSON.parse(loggedInUser)
blogService.setToken(user.token)
console.log("user is", user)
setUser(user)
}
else{
console.log("Error here,", JSON.parse(loggedInUser))
}
},[])
const handleLogin = async (event) => {
event.preventDefault()
console.log("Logging in,", username, password)
try {
const user = await loginService({username, password})
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
blogService.setToken(user.token)
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setUser(user)
setUsername('')
setPassword('')
}
catch(error){
setErrorMsg('Wrong username or password')
setTimeout(() => {
setErrorMsg('')
},3000)
}
}
const handleLogout = () => {
window.localStorage.removeItem('loggedBlogUser')
setUser(null)
setBlogs([])
setSortedArray([])
}
const addNewBlog = async (blogObject) => {
try {
const result = await blogService.createBlog(blogObject)
const result2 = await blogService.getUserBlogs(result.blog.slice(-1)[0])
user.blog = user.blog.concat(result2)
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
console.log("USER USER USER IS", user)
setVisibility(false)
setMsg(`a new blog ${blogObject.title} by ${user.name} added`)
setTimeout(() => {
setMsg('')
},3000)
}
catch(error){
console.log("error adding new blog", error)
}
}
return (
<div>
<h2>blogs</h2>
{msg !== '' && <h1 className='successMsg-container'>{msg}</h1>}
{errorMsg !== '' && <h1 className='errorMsg-container'>{errorMsg}</h1>}
{user == null &&
<Login
handleLogin={handleLogin}
setUsername={setUsername}
setPassword={setPassword}
username={username}
password={password}>
</Login>
}
{user != null && <div className="notes">
<p>{user.name} logged in <button onClick={handleLogout}>logout</button></p>
</div>}
{user != null &&
<CreateBlog
addNewBlog={addNewBlog}
visibility={visibility}
setVisibility={setVisibility}>
</CreateBlog>
}
{sortedArray !== null && sortedArray.map(blog =>
<Blog key={blog.id} blog={blog} setBlogs={setBlogs} setSortedArray={setSortedArray} user={user} />
)}
</div>
)
}
export default App
Blog code
import blogService from "../services/blogs"
const Blog = ({blog,user,setBlogs,setSortedArray}) => {
const [view, setView] = useState(false)
const [likeCount, setLikeCount] = useState(0)
const hideWhenVisible = {display: view ? 'none' : ''}
const showWhenVisible = {display: view ? '': 'none'}
const handleLikeClick = async (blog) => {
const id = blog.id
const actualBlog = await blogService.getUserBlogs(id)
try {
const updatedBlog = {
user: [blog.user[0]],
likes: actualBlog.likes + 1,
author: blog.author,
id: actualBlog.id,
title: blog.title,
url: blog.url
}
setLikeCount(updatedBlog.likes)
var elementPos = user.blog.map(ranBlog => {return ranBlog.id}).indexOf(blog.id)
user.blog[elementPos] = updatedBlog
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
const result = await blogService.updateBlog(updatedBlog,id)
}
catch(error){
console.log("error liking blog")
}
}
return (
<div className="blog-container">
{blog.title}
<button onClick={() => setView(true)} style={hideWhenVisible}>view</button>
<button onClick={() => setView(false)} style={showWhenVisible}>hide</button>
{view && <div className="expanded-view-container">
<li className="extra-blog-info">
<ul>{blog.url}</ul>
{likeCount === 0 && <ul>likes: {blog.likes} <button onClick={() => handleLikeClick(blog)}>like</button></ul>}
{likeCount !== 0 && <ul>likes: {likeCount} <button onClick={() => handleLikeClick(blog)}>like</button></ul>}
<ul>{blog.author}</ul>
<ul>{blog.id}</ul>
</li>
</div>}
</div>
);
}
export default Blog;
From your code of Blog component, I have seen that you haven't called setBlogs and setSortedArray which update blog states from App.
I'd propose you should have another callback function called afterLikeClicked in Blog to update blogs state which will make UI re-rendered.
import blogService from "../services/blogs"
const Blog = ({blog, user, afterLikeClicked}) => {
const [view, setView] = useState(false)
const [likeCount, setLikeCount] = useState(0)
const hideWhenVisible = {display: view ? 'none' : ''}
const showWhenVisible = {display: view ? '': 'none'}
const handleLikeClick = async (blog) => {
const id = blog.id
const actualBlog = await blogService.getUserBlogs(id)
try {
const updatedBlog = {
user: [blog.user[0]],
likes: actualBlog.likes + 1,
author: blog.author,
id: actualBlog.id,
title: blog.title,
url: blog.url
}
setLikeCount(updatedBlog.likes)
//The change is here
afterLikeClicked(updatedBlog)
var elementPos = user.blog.map(ranBlog => {return ranBlog.id}).indexOf(blog.id)
user.blog[elementPos] = updatedBlog
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
const result = await blogService.updateBlog(updatedBlog,id)
}
catch(error){
console.log("error liking blog")
}
}
return (
<div className="blog-container">
{blog.title}
<button onClick={() => setView(true)} style={hideWhenVisible}>view</button>
<button onClick={() => setView(false)} style={showWhenVisible}>hide</button>
{view && <div className="expanded-view-container">
<li className="extra-blog-info">
<ul>{blog.url}</ul>
{likeCount === 0 && <ul>likes: {blog.likes} <button onClick={() => handleLikeClick(blog)}>like</button></ul>}
{likeCount !== 0 && <ul>likes: {likeCount} <button onClick={() => handleLikeClick(blog)}>like</button></ul>}
<ul>{blog.author}</ul>
<ul>{blog.id}</ul>
</li>
</div>}
</div>
);
}
export default Blog;
And then update App accordingly
import { useState, useEffect } from 'react'
import Blog from './components/Blog'
import Login from './components/Login'
import CreateBlog from './components/CreateBlog'
import blogService from './services/blogs'
import loginService from './services/login'
import './app.css';
const App = () => {
const [blogs, setBlogs] = useState([]) // Unsorted array
const [user, setUser] = useState(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [errorMsg, setErrorMsg] = useState('')
const [msg, setMsg] = useState('')
const [sortedArray, setSortedArray] = useState(null) //This sorts the original blog array
const [visibility, setVisibility] = useState(false)
useEffect( () => {
if(user != null){
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
}
console.log("blogs is", blogs)
}, [user,sortedArray])
useEffect(() => {
const loggedInUser = window.localStorage.getItem('loggedBlogUser')
console.log("loggedInUser is", loggedInUser)
if(loggedInUser){
const user = JSON.parse(loggedInUser)
blogService.setToken(user.token)
console.log("user is", user)
setUser(user)
}
else{
console.log("Error here,", JSON.parse(loggedInUser))
}
},[])
const handleLogin = async (event) => {
event.preventDefault()
console.log("Logging in,", username, password)
try {
const user = await loginService({username, password})
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
blogService.setToken(user.token)
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setUser(user)
setUsername('')
setPassword('')
}
catch(error){
setErrorMsg('Wrong username or password')
setTimeout(() => {
setErrorMsg('')
},3000)
}
}
const handleLogout = () => {
window.localStorage.removeItem('loggedBlogUser')
setUser(null)
setBlogs([])
setSortedArray([])
}
const addNewBlog = async (blogObject) => {
try {
const result = await blogService.createBlog(blogObject)
const result2 = await blogService.getUserBlogs(result.blog.slice(-1)[0])
user.blog = user.blog.concat(result2)
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setBlogs(user.blog)
setSortedArray(user.blog.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
console.log("USER USER USER IS", user)
setVisibility(false)
setMsg(`a new blog ${blogObject.title} by ${user.name} added`)
setTimeout(() => {
setMsg('')
},3000)
}
catch(error){
console.log("error adding new blog", error)
}
}
return (
<div>
<h2>blogs</h2>
{msg !== '' && <h1 className='successMsg-container'>{msg}</h1>}
{errorMsg !== '' && <h1 className='errorMsg-container'>{errorMsg}</h1>}
{user == null &&
<Login
handleLogin={handleLogin}
setUsername={setUsername}
setPassword={setPassword}
username={username}
password={password}>
</Login>
}
{user != null && <div className="notes">
<p>{user.name} logged in <button onClick={handleLogout}>logout</button></p>
</div>}
{user != null &&
<CreateBlog
addNewBlog={addNewBlog}
visibility={visibility}
setVisibility={setVisibility}>
</CreateBlog>
}
{sortedArray !== null && sortedArray.map(blog =>
<Blog key={blog.id} blog={blog} afterLikeClicked={(updatedBlog) => {
const updatedBlogs = blogs.map((blog) => blog.id === updatedBlog.id ? updatedBlog : blog)
//update original blogs
setBlogs(updatedBlogs)
//update sorted blogs
setSortedArray(updatedBlogs.sort((a,b) => parseInt(b.likes) - parseInt(a.likes)))
}} user={user} />
)}
</div>
)
}
export default App
Related
I am learning React with Fullstack with the University of Helsinki. In one of the exercises, I need to build a frontend app and connect it with a previously built backend server.
I need to implement a login/logout function.
The issue is in my HandleLogin section. For some reason, if I use promises for the axios HTTP request directly in my frontend app.js. It has no problem connecting the server. However, if I create a new component for the login function to handle the login button. I cannot receive anything from the server. I tried console.log(user) in the HandleLogin, it shows the user with axio/http but not with async/await.
Could anyone explain to me why this is happening? Thank you very much!
My codes are here.
App.js
import blogService from './services/blogs'
import loginService from './services/login'
import Blog from './components/Blog'
import LoginForm from './components/LoginForm'
import BlogForm from './components/BlogForm'
import Togglable from './components/Togglable'
import Notification from './components/Notification'
import axios from 'axios'
const App = () => {
const [blogs, setBlogs] = useState([])
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [user, setUser] = useState(null)
const [loginVisible, setLoginVisible] = useState(false)
const [Notification, setNotification] = useState("")
const [Toggle, setToggle] = useState(false)
useEffect(() => {
const Data = async () => {
const initialBlogs = await blogService.getAll()
setBlogs( initialBlogs )
}
Data()
}, [])
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem('loggedBlogAppUser')
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON)
setUser(user)
blogService.setToken(user.token)
}
}, [])
const addBlog = async (blogObject) => {
BlogFormRef.current.toggleVisibility()
if (blogObject.title !== '' && blogObject.author !== '' && blogObject.url !== '') {
const newBlog = await blogService.create(blogObject)
setBlogs(blogs.concat(newBlog))
setNotification(`A new blog ${blogObject.title} by ${blogObject.author} is added`)
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
} else {
setNotification('You must fill all fields to create a blog')
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
}
}
const BlogFormRef = useRef()
const blogUpdate = async (blogId, blogObject) => {
await blogService.update(blogId, blogObject)
const updatedBlog = {...blogObject, blogId}
setBlogs(blogs.map((blog) => (blog.id === updatedBlog.id ? updatedBlog : blog)))
}
const blogRemove = async (blogId) => {
await blogService.remove(blogId)
setBlogs(blogs.filter((blog) => blog.id !== blogId))
}
const handleLogin = async (event) => {
event.preventDefault()
try{
const user = await axios.post(`http://localhost:3001/api/login`, {username, password})
/*const user = await loginService.login({
username,
password
})*/ //this is not working
console.log(user)
console.log(user.data.name)
window.localStorage.setItem('loggedBlogAppUser', JSON.stringify(user))
blogService.setToken(user.data.token)
setUser(user)
setUsername('')
setPassword('')
setNotification(`User ${user.data.name} is logged in`)
} catch (exception) {
setNotification('Wrong username or password')
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
}
}
//console.log(user)
const handleLogout = () => {
window.localStorage.removeItem('loggedBlogAppUser')
document.location.reload()
}
const blogForm = () => (
<Togglable buttonLabel='new blog' cancelButtonLabel="Cancel" ref={BlogFormRef}>
<BlogForm createBlog={addBlog} />
</Togglable>
)
const loginForm = () => {
const hideWhenVisible = { display: loginVisible ? 'none' : ''}
const showWhenVisible = { display: loginVisible ? '' : 'none'}
return(
<div>
<div style={hideWhenVisible}>
<button onClick={() => setLoginVisible(true)}>log in</button>
</div>
<div style={showWhenVisible}>
<LoginForm
username={username}
password={password}
handleUsernameChange={({target}) => setUsername(target.value)}
handlePasswordChange={({target}) => setPassword(target.value)}
handleSubmit={handleLogin}/>
</div>
</div>
)
}
return (
<div>
<h2>Blog11</h2>
{user && (
<div>
{user.data.name} is logged in
<button onClick={handleLogout}>Logout</button>
</div>
)}
<div>
{user === null ? loginForm()
:(
<>
{blogForm()}
<div>
{blogs.map(blog =>
<Blog
key={blog.id}
blog={blog}
blogUpdate={blogUpdate}
blogRemove={blogRemove}/>
)}
</div>
</>
)
}
</div>
</div>
)
}
export default App
loginService
const baseUrl = `/api/login`
const login = async (credentials) => {
const response = await axios.post(baseUrl, credentials)
return response.data
}
export default login
This is the first time for me to ask a question on Stackoverflow. Thank you all for your patience.
I gave up using an independent module. Instead, I used await axios.post in the App.js and there is no problem.
try{
const user = await axios.post(`http://localhost:3001/api/login`, {username, password})
/*const user = await loginService.login({
username,
password
})*/ //this is not working
//console.log(user)
//console.log(user.data.name)
window.localStorage.setItem('loggedBlogAppUser', JSON.stringify(user))
blogService.setToken(user.data.token)
setUser(user)
setUsername('')
setPassword('')
setNotification(`User ${user.data.name} is logged in`)
} catch (exception) {
setNotification('Wrong username or password')
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
}
}
Why do I have undefined in selectedUser?
After all, I go through the find method through the users array and the first id of the users array should be written to selectedUser
function App() {
const [selectedUserId, setSelectedUserId] = useState(null)
const [users,setUsers] = useState([])
const selectedUser = users.find(u => u.id === selectedUserId)
console.log(users)
console.log(selectedUser)
useAsyncEffect(async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await res.json()
setUsers(data)
}, [])
const onUserClick = (userId) => {
setSelectedUserId(userId)
}
return (
<div>
{ selectedUser ? <ListUsers users={users} onUserClick={onUserClick} /> : <Profile user=
{selectedUser} />
}
</div>
)
}
You can use a loading state to determine when the async function is done and only then you can set the selected user. Otherwise, it remains set to 'null' when the 'return' is rendered.
Below is pseudo code.
function App() {
const [selectedUserId, setSelectedUserId] = useState(null)
const [users,setUsers] = useState([])
const selectedUser = users.find(u => u.id === selectedUserId)
const [loading, setLoading] = useState(false)
console.log(users)
console.log(selectedUser)
useAsyncEffect(async () => {
setLoading(true)
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await res.json()
setUsers(data)
setLoading(false)
}, [])
const onUserClick = (userId) => {
setSelectedUserId(userId)
}
if (loading){
return "Loading..."
// you can use a nicer loader component here
}
return (
<div>
{ selectedUser ? <ListUsers users={users} onUserClick={onUserClick} /> : <Profile user=
{selectedUser} />
}
</div>
)
}
I currently have a signup form. Users have an optional field where they can upload a profile picture if they want.
If they don't upload a profile picture, I want to use a default profile picture for them (imagine the default facebook profile picture image for example).
My image imported as:
import defaultPhoto from "../../assets/images/logo/grayscale-nut.png";
My register function:
const register = async () => {
setLoading("loading");
let newUserID;
auth
.createUserWithEmailAndPassword(email, confirmedPassword)
.then((auth) => {
if (auth) {
newUserID = auth.user.uid;
const collection = db.collection("users");
collection.doc(auth.user.uid).set({
username: username.toLowerCase().trim(),
name: firstName,
surname: lastName,
bio: "",
addressBook: [],
medals: [],
medalShowcase: [],
boughtItems: [],
soldItems: [],
});
}
})
.then(async () => {
let photo;
if (signUpPhoto.length === 1) {
photo = signUpPhoto[0].file;
} else {
//GET THE DEFAULT PHOTO HERE
}
const userProfileRef = firebase
.storage()
.ref()
.child(`users/` + newUserID + `/profilePicture/profilePicture`);
userProfileRef.put(photo).then(async (snapshot) => {
const downloadURL = await snapshot.ref.getDownloadURL();
const userData = db.collection("users").doc(newUserID);
userData
.set({ profilePicture: downloadURL }, { merge: true })
.then(async () => {
setLoading("complete");
const newData = await getUserByUserId(newUserID);
setData(newData);
history.replace("/");
});
});
})
.catch((error) => {
setLoading("error");
});
};
Edit 1: The full and updated Signup page
//#####################################################################
import { useState, useEffect } from "react";
import { Link, useHistory } from "react-router-dom";
//#####################################################################
import firebase from "firebase/app";
//#####################################################################
import defaultPhoto from "../../assets/images/logo/grayscale-nut.png";
//#####################################################################
import "./Signup.scss";
import "../ui/buttons/buttons.scss";
import classNames from "classnames/bind";
import Logo from "../../assets/images/logo/seekio-logo-purple.png";
import PasswordStrengthMeter from "./components/PasswordStrengthMeter/PasswordStrengthMeter";
import PasswordMatchChecker from "./components/PasswordMatchChecker/PasswordMatchChecker";
import zxcvbn from "zxcvbn";
import ElipsisSpinner from "../ui/loading/ElipsisLoading/ElipsisSpinner";
import validator from "validator";
//#####################################################################
import { auth, db } from "../../firebase";
//#####################################################################
import { FilePond, registerPlugin } from "react-filepond";
import "filepond/dist/filepond.min.css";
import FilePondPluginFileValidateSize from "filepond-plugin-file-validate-size";
import FilePondPluginImageExifOrientation from "filepond-plugin-image-exif-orientation";
import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css";
//#####################################################################
import { getUserByUserId } from "../../services/firebase";
//#####################################################################
import userDataStore from "../../Global/UserState/UserStore";
//#####################################################################
registerPlugin(
FilePondPluginImageExifOrientation,
FilePondPluginImagePreview,
FilePondPluginFileValidateType,
FilePondPluginFileValidateSize
);
function Signup() {
//-------------------------------------------------------
//STATES
//-------------------------------------------------------
const history = useHistory();
const [email, setEmail] = useState("");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [password, setPassword] = useState("");
const [passwordStrength, setPasswordStrength] = useState(0);
const [confirmedPassword, setConfirmedPassword] = useState("");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [username, setUsername] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [inputsFilled, setInputsFilled] = useState(false);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [errorMsg, setErrorMsg] = useState(null);
const [passwordsMatch, setPasswordsMatch] = useState(null);
const [passwordNotStrongEnough, setPasswordNotStrongEnough] = useState(false);
const [usernameError, setUsernameError] = useState(null);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [signUpPhoto, setSignUpPhoto] = useState([]);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const setData = userDataStore((state) => state.setData);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const [loading, setLoading] = useState("idle");
//-------------------------------------------------------
//CLASSNAMES
//-------------------------------------------------------
const signUpButton = classNames({
animateSlideUpButton: true,
signup__buttonRegister: true,
disabled: !inputsFilled,
});
//-------------------------------------------------------
//USE EFFECTS
//-------------------------------------------------------
useEffect(() => {
if (!validator.matches(username, "^[a-zA-Z0-9_.-]*$")) {
setUsernameError("Invalid username, please try another one.");
} else {
setUsernameError(null);
}
}, [username]);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
useEffect(() => {
if (password !== "") {
getPasswordScore(password);
}
}, [password]);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
useEffect(() => {
if (password !== confirmedPassword) {
setPasswordsMatch(false);
} else {
setPasswordsMatch(true);
}
}, [password, confirmedPassword]);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
useEffect(() => {
if (
(email, username, password, firstName, lastName, confirmedPassword) !== ""
) {
let emailValid = emailIsValid(email);
if (
emailValid &&
passwordStrength >= 2 &&
password === confirmedPassword &&
username.length >= 3
) {
setInputsFilled(true);
} else {
setInputsFilled(false);
}
} else {
setInputsFilled(false);
}
}, [
email,
password,
username,
firstName,
lastName,
confirmedPassword,
signUpPhoto,
]);
//-------------------------------------------------------
//METHODS
//-------------------------------------------------------
async function getPasswordScore() {
const score = zxcvbn(password).score;
setPasswordStrength(score);
if (score <= 2) {
setPasswordNotStrongEnough(true);
} else {
setPasswordNotStrongEnough(false);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function emailIsValid(email) {
return /^[^\s#]+#[^\s#]+\.[^\s#]+$/.test(email);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const preRegister = async (e) => {
e.preventDefault();
if (
firstName.trim() === "" ||
lastName.trim() === "" ||
username.trim() === "" ||
email.trim() === "" ||
password.trim() === "" ||
confirmedPassword.trim() === "" ||
usernameError !== null
) {
window.alert("Invalid data!\nAll data must be filled in!");
} else {
const usernameRef = db
.collection("users")
.where("username", "==", username);
usernameRef.get().then((docs) => {
if (docs.size === 1) {
window.alert("Username taken");
} else {
register();
}
});
}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const register = async () => {
setLoading("loading");
let newUserID;
auth
.createUserWithEmailAndPassword(email, confirmedPassword)
.then((auth) => {
if (auth) {
newUserID = auth.user.uid;
const collection = db.collection("users");
collection.doc(auth.user.uid).set({
username: username.toLowerCase().trim(),
name: firstName,
surname: lastName,
bio: "",
addressBook: [],
medals: [],
medalShowcase: [],
boughtItems: [],
soldItems: [],
});
}
})
.then(async () => {
const userProfileRef = firebase
.storage()
.ref()
.child(`users/` + newUserID + `/profilePicture/profilePicture`);
const userData = db.collection("users").doc(newUserID);
if (signUpPhoto.length === 1) {
console.log("profile picture selected");
const photo = signUpPhoto[0].file;
userProfileRef.put(photo).then(async (snapshot) => {
const downloadURL = await snapshot.ref.getDownloadURL();
userData
.set({ profilePicture: downloadURL }, { merge: true })
.then(async () => {
setLoading("complete");
const newData = await getUserByUserId(newUserID);
setData(newData);
history.replace("/");
});
});
} else {
console.log(
"no profile picture was selected, uploading the default photo instead"
);
userProfileRef
.putString(defaultPhoto.toString(64).split(",")[1], "base64", {
contentType: "image/png",
})
.then((s) => {
//This console log never happens
console.log(s);
})
.catch((e) => console.log(e));
}
})
.catch((error) => {
setLoading(error);
});
};
//Filepond
function onDrop(uploadedPhotos) {
setSignUpPhoto(uploadedPhotos);
}
//-------------------------------------------------------
//END
//-------------------------------------------------------
return (
<div className="signup">
{loading === "loading" ? (
<div className="signup__loading">
<ElipsisSpinner />
<h4>Creating Account</h4>
</div>
) : loading === "complete" ? (
<div className="signup__loading">
<h4>Success!</h4>
</div>
) : loading === "error" ? (
<div className="signup__loading">
<h4>Error! Please try again.</h4>
</div>
) : (
<div className="signup__container">
<Link to="/">
<img src={Logo} alt="logo" className="signup__logo" />
</Link>
<h2>Sign-up</h2>
<form>
<h5>E-mail</h5>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="inputText"
placeholder="youremail#email.com"
/>
<h5> Username </h5>{" "}
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
className="inputText"
placeholder="PokeTrainer"
/>
{usernameError && (
<span className="errorSpan">
<h4 className="">{usernameError}</h4>
</span>
)}
<h5> Name </h5>{" "}
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
required
className="inputText"
placeholder="Ash"
/>
<h5> Surname </h5>{" "}
<input
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
required
className="inputText"
placeholder="Ketchum"
/>
<h5>Password</h5>
<input
type="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
required
className="inputText"
placeholder="●●●●●●●●●●"
/>
<PasswordStrengthMeter
passwordStrength={passwordStrength}
password={password}
/>
{password.length > 0 && passwordNotStrongEnough && (
<span className="errorSpan">
<h4>Password score of 4 or higher is required.</h4>
</span>
)}
<h5> Confirm Password </h5>
<input
type="password"
value={confirmedPassword}
onChange={(e) => setConfirmedPassword(e.target.value)}
required
className="inputText"
placeholder="●●●●●●●●●●"
/>
<PasswordMatchChecker
password={password}
confirmPassword={confirmedPassword}
/>
</form>
{!passwordsMatch && (
<span className="errorSpan">
<h4>Passwords must match!</h4>
</span>
)}
<div className="signup__container__filePondWrapper">
<h4>Choose a profile picture:</h4>
<div className="signup__container__filePondWrapper__wrapper">
<FilePond
files={signUpPhoto}
onupdatefiles={onDrop}
allowMultiple={false}
maxFiles={1}
instantUpload={false}
name="files"
itemInsertLocation={"after"}
maxFileSize={"50MB"}
acceptedFileTypes={["image/*"]}
labelIdle={"<span class='filepond--label-action'>Browse</span>"}
credits={null}
stylePanelLayout={"compact circle"}
allowImagePreview={true}
imageCropAspectRatio={"1:1"}
imageResizeTargetWidth={"200"}
imageResizeTargetHeight={"200"}
/>
</div>
</div>
<button type="submit" className={signUpButton} onClick={preRegister}>
Sign Up
</button>
{errorMsg && <h4 className="errorMsg">{errorMsg}</h4>}
<p>
Our alpha is currently UK only, by signing-up you acknowledge this
and agree to Seekio's Conditions of Use & Sale. Please see our
Privacy Notice, our Cookies Notice and our Interest-Based Ads
Notice.
</p>
<Link to="/login">
<button className="signup__buttonSignIn">
Already Have an Account ? Sign In
</button>
</Link>
</div>
)}
</div>
);
}
export default Signup;
Using base64 string and uploading worked out for me:
import image from "../path/to/image.png"
const base64String = ""
storage.ref("imageName.png")
.putString(base64String, "base64", {contentType: "image/png"})
.then((s) => {
console.log(s);
}).catch(e => console.log(e));
Make sure you have the extension name in the file name and contentType set to correct mimetype.
Your function is async so try to use await as shown:
const register = async () => {
setLoading("loading");
const userCred = await auth.createUserWithEmailAndPassword(
email,
confirmedPassword
);
const newUserID = userCred.user.uid;
const collection = db.collection("users");
await collection.doc(auth.user.uid).set({
username: username.toLowerCase().trim(),
name: firstName,
surname: lastName,
bio: "",
addressBook: [],
medals: [],
medalShowcase: [],
boughtItems: [],
soldItems: [],
});
const userProfileRef = firebase
.storage()
.ref()
.child(`users/` + newUserID + `/profilePicture/profilePicture`);
let photo;
if (signUpPhoto.length === 1) {
photo = signUpPhoto[0].file;
await userProfileRef.put(photo);
} else {
//GET THE DEFAULT PHOTO HERE
await userProfileRef.putString(base64String, "base64", {
contentType: "image/png",
});
}
const downloadURL = await userProfileRef.getDownloadURL();
const userData = db.collection("users").doc(newUserID);
await userData.set({ profilePicture: downloadURL }, { merge: true });
setLoading("complete");
const newData = await getUserByUserId(newUserID);
setData(newData);
history.replace("/");
};
Error:
Warning: Encountered two children with the same key, 5e0611d77833da1668feade1. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
Here on this picture https://prnt.sc/qgfymk I have created 2 blogs. Delete button is working fine. I'm sending via axios to HTTP delete request using mongoose and MongoDB as my database.
But when I start to click on like button check what happens. https://prnt.sc/qgg32o
It removes my other blog post and copies one with the same name and id. The issue here is that I have different IDs but somehow when I press LIKE button it gives me another ID.
I'll give you code for both PUT request in backend and frontend for incrementLikes, I really don't know what is going on.
controllers/blogs.js (backend)
blogsRouter.put('/:id', async (request, response, next) => {
const body = request.body
const blogs = {
title:body.title,
author: body.author,
url:body.url,
likes: body.likes
}
try {
const updatedBlog = await Blog.findOneAndUpdate(request.params.id, blogs, {
new: true
})
response.json(updatedBlog.toJSON())
} catch (exception) {
next(exception)
}
})
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import Blog from './components/Blog';
import LoginForm from './components/LoginForm'
import BlogForm from './components/BlogForm'
import Notification from './components/Notification'
import loginService from './services/login';
import blogService from './services/blogs';
const App = () => {
const [blogs, setBlogs] = useState([])
const [user, setUser] = useState(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [errorMessage, setErrorMessage] = useState(null)
// states for blog creation
const [title, setTitle] = useState('')
const [author, setAuthor] = useState('')
const [url, setUrl] = useState('')
useEffect(() => {
console.log('effect')
blogService
.getAll()
.then(response => {
console.log('promise fulfiled')
setBlogs(response.data)
})
.catch(error => {
console.log('response', error.response)
console.log('error')
})
}, [])
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem('loggedBlogUser')
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON)
setUser(user)
blogService.setToken(user.token)
}
}, [])
//put request
***const incrementLike = id => {
const blog = blogs.find(b => b.id === id)
console.log('blog id', blog)
const voteLike = {...blog, likes: blog.likes + 1}
blogService
.update(id, voteLike)
.then(returnedBlog => {
setBlogs(blogs.map(blog => blog.id !== id ? blog : returnedBlog))
})
.catch(error => {
setErrorMessage(
`Blog was already removed from server`
)
setTimeout(() => {
setErrorMessage(null)
}, 5000)
})
}***
//login
const handleLogin = async (e) => {
e.preventDefault()
try {
const user = await loginService.login({username, password})
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setUser(user)
setUsername('')
setPassword('')
console.log('success')
} catch (exception) {
setErrorMessage('wrong credentials')
setTimeout(() => {
setErrorMessage(null)
}, 5000)
console.log('baaad')
}
}
const deleteBlogId = (id) => {
console.log('deleted blog')
blogService
.del(id)
.then(response => {
setBlogs(blogs.filter(blog => blog.id !== id))
})
.catch(error => {
console.log(error.response);
})
}
const handleCreateBlog = async (e) => {
e.preventDefault()
const newBlogs = {
title: title,
author: author,
url: url,
date: new Date()
}
blogService
.create(newBlogs)
.then(returnedBlog => {
setBlogs(blogs.concat(returnedBlog))
setTitle('')
setAuthor('')
setUrl('')
setErrorMessage(`${author} created new blog with name ${title}`)
setTimeout(() => {
setErrorMessage(null)
}, 5000)
})
}
const loginForm = () => {
return (
<div>
<Notification message={errorMessage}/>
<div>
<LoginForm
username={username}
password={password}
handleUsernameChange={({target}) => setUsername(target.value)}
handlePasswordChange={({target}) => setPassword(target.value)}
handleSubmit={handleLogin}
/>
</div>
</div>
)
}
const handleTitleChange = (event) => {
console.log(event.target.value)
setTitle(event.target.value)
}
const blogForm = () => {
return (
<div>
<BlogForm
title={title}
author={author}
url={url}
handleTitleChange={handleTitleChange}
handleAuthorChange={({target}) => setAuthor(target.value)}
handleUrlChange={({target}) => setUrl(target.value)}
onSubmit={handleCreateBlog}
/>
</div>
)
}
const handleLogout = async () => {
window.localStorage.clear()
setUser(null)
}
const logout = () => {
return (
<div><button type="reset" onClick={handleLogout}>Logout</button></div>
)}
const blogList = () => {
return (
<div>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
{blogs.map(blog =>
<Blog
key={blog.id}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</div>
)
}
return (
<div className="App">
{user === null ?
loginForm() :
<div>
<Notification message={errorMessage}/>
{blogForm()}
{blogList()}
</div>
}
</div>
);
}
export default App;
Check the incrementLikes function. I think there is some kind of issue. Button for likies are in component called Blog.js
Blog.js
import React from 'react';
const Blog = ({blog, increment, deleteBlog}) => (
<div>
<button onClick={deleteBlog}>Delete</button>
{blog.title}
{blog.author}
{blog.likes}
<button onClick={increment}>Like</button>
</div>
)
export default Blog
While there shouldn't be 2 blogs with the same ID you can fix the issue at hand by replacing the key from blog.id to the index of the post like this.
<div>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
//change
{blogs.map((blog,index) =>
<Blog
//change
key={index}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</div>
I added //change to the lines I changed.
You can just use something like uuid for this which will generate a unique ID.
import uuid from "uuid";
<>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
{blogs.map((blog,index) =>
<Blog
key={uuid.v4()}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</>
While my code works from the functionalities, I have to click "refresh" after each click to see the changes. For example when I click "Add note" I have to refresh the page in order to see it. While it compiles successfully, the console shows three errors:
import { API, graphqlOperation } from "aws-amplify";
import { withAuthenticator } from "aws-amplify-react";
import React, { useEffect, useState } from "react";
import { createNote, deleteNote, updateNote } from "./graphql/mutations";
import { listNotes } from "./graphql/queries";
import {
onCreateNote,
onDeleteNote,
onUpdateNote
} from "./graphql/subscriptions";
const App = () => {
const [id, setId] = useState("");
const [note, setNote] = useState("");
const [notes, setNotes] = useState([]);
useEffect(() => {
getNotes();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote)
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});
const deleteNoteListener = API.graphql(
graphqlOperation(onDeleteNote)
).subscribe({
next: noteData => {
const deletedNote = noteData.value.data.onDeleteNote;
setNotes(prevNotes => {
const updatedNotes = prevNotes.filter(
note => note.id !== deletedNote.id
);
return updatedNotes;
});
}
});
const updateNoteListener = API.graphql(
graphqlOperation(onUpdateNote)
).subscribe({
next: noteData => {
const updatedNote = noteData.value.data.onUpdateNote;
setNotes(prevNotes => {
const index = prevNotes.findIndex(note => note.id === updatedNote.id);
const updatedNotes = [
...prevNotes.slice(0, index),
updatedNote,
...prevNotes.slice(index + 1)
];
return updatedNotes;
});
setNote("");
setId("");
}
});
return () => {
createNoteListener.unsubscribe();
deleteNoteListener.unsubscribe();
updateNoteListener.unsubscribe();
};
}, []);
const getNotes = async () => {
const result = await API.graphql(graphqlOperation(listNotes));
setNotes(result.data.listNotes.items);
};
const handleChangeNote = event => setNote(event.target.value);
const hasExistingNote = () => {
if (id) {
const isNote = notes.findIndex(note => note.id === id) > -1;
return isNote;
}
return false;
};
const handleAddNote = async event => {
event.preventDefault();
// Check if we have an exisiting note. If so, then update it.
if (hasExistingNote()) {
handleUpdateNote();
} else {
const input = { note };
await API.graphql(graphqlOperation(createNote, { input }));
}
};
const handleUpdateNote = async () => {
const input = { id, note };
await API.graphql(graphqlOperation(updateNote, { input }));
};
const handleDeleteNote = async noteId => {
const input = { id: noteId };
await API.graphql(graphqlOperation(deleteNote, { input }));
};
const handleSetNote = ({ note, id }) => {
setNote(note);
setId(id);
};
return (
<div className="flex flex-column items-center justify-center pa3 bg-washed-red">
<h1 className="code f2-l">Amplify Notetake</h1>
{/* Note Form */}
<form onSubmit={handleAddNote} className="mb3">
<input
type="text"
className="pa2 f4"
placeholder="Write your note"
onChange={handleChangeNote}
value={note}
/>
<button className="pa2 f4" type="submit">
{id ? "Update note" : "Add note"}
</button>
</form>
{/* Notes list */}
<div>
{notes.map(item => (
<div key={item.id} className="flex items-center">
<li onClick={() => handleSetNote(item)} className="list pa1 f3">
{item.note}
</li>
<button
onClick={() => handleDeleteNote(item.id)}
className="bg-transparent bn f4"
>
<span>×</span>
</button>
</div>
))}
</div>
</div>
);
};
export default withAuthenticator(App, { includeGreetings: true });
If you are using CLI version 2.0 and above, owner is a required argument. This is explained more in the link below:
https://aws-amplify.github.io/docs/cli-toolchain/graphql#authorizing-subscriptions
After I added Auth in the import
import { API, graphqlOperation, Auth } from 'aws-amplify';
captured the current user and passed it into the subscription it started working for me.
useEffect(() => {
getNotes();
const owner = Auth.user.getUsername();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote, { owner })
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});