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>
)
}
Related
this is a component that retrieve data from API and generate a table. One button in a column for removing the row opens a Modal.
For avoiding in first component render to trigger api.delete request, an useRef set as false in second useEffect
Modal's Delete button return row info in deleteHandler function which successfully trigger api.delete request on confirmation, remove user in backend however table is not reloading.
Once row is removed expected result is triggering api.get request and display table now updated and without row removed.
In order to get that result I tried with const [reload, setReload] = useState(false); state which introduce another dependency to both userEffect
reload state effectively reload table data updated however it cause also that api.delete request trigger with const ida undefined. Below component script it can find useEffect with my tried.
Any hint or possible solution is appreciated
import React, { Fragment, useEffect, useState, useRef } from "react";
... other imports ...
export default function AllUsers() {
const api = useApi();
const [userData, setUserData] = useState();
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const [modalMessage, setModalMessage] = useState();
const [removeUserId, setRemoveUserId] = useState();
const [ida, setIda] = useState();
let effectRan = useRef(false);
const [reload, setReload] = useState(false);
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida]);
const handleEditClick = (e, rowIndex) => {
// console.log("user/" + rowIndex.username);
navigate("/user/" + rowIndex.username, [navigate]);
};
const handleRemoveClick = (e, rowIndex) => {
// console.log([rowIndex]);
setShowModal(true);
setModalMessage({
title: `Remove ${rowIndex.username}`,
message: `User's email to remove ${rowIndex.email}`,
});
setRemoveUserId(rowIndex.id);
};
const closeHandler = () => {
setShowModal(false);
};
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setShowModal(false);
};
// console.log(ida, idb);
return (
<Fragment>
{showModal && (
<BtModal
show={showModal}
title={modalMessage.title}
message={modalMessage.message}
handleClose={closeHandler}
onConfirm={deleteHandler}
/>
)}
<Body>
<h1>User Table</h1>
{userData === undefined ? (
<Spinner animation="border" />
) : (
<>
{userData === null ? (
<p>Could not retrieve users.</p>
) : userData.length === 0 ? (
<p>There are not users in system</p>
) : (
<UserListTable
newData={userData}
handleEditClick={handleEditClick}
handleRemoveClick={handleRemoveClick}
/>
)}
</>
)}
</Body>
</Fragment>
);
}
useEffect updated with reload state:
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
effectRan.current = false;
} else {
setUserData(null);
}
})();
}, [api, reload]);
useEffect(() => {
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => (effectRan.current = true);
}, [api, ida, reload]);
Here you modify your last edit
const deleteHandler = () => {
// console.log(removeUserId);
setIda(removeUserId);
setReload(!reload)
setShowModal(false);
};
useEffect(() => {
(async () => {
const response = await api.get("/admin/users");
if (response.ok) {
setUserData(response.body);
} else {
setUserData(null);
}
})();
}, [api]);
useEffect(() => {
effectRan.current = reload;
if (effectRan.current) {
// console.log("effect run");
(async () => {
const response = await api.delete("/admin/users", {
ida: ida,
});
if (response.ok && response.status === 204) {
console.log(response);
setReload(!reload);
} else {
console.log(response.body.errors);
}
})();
}
return () => {
effectRan.current = false;
};
}, [api, ida, reload]);
It looks like you're trying to delete an item in the table and confirm it via a prompt. I can see that you've used a lot of useEffects, which might be making things a bit complicated.
Don't worry though, I have a solution that should be much simpler. Let me show you what I mean!
const Page = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState([]);
const [deleting, setDeleting] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const deleteHandler = async() => {
setDeleting(true);
await api.delete(selectedItem);
// Next you can either update the state itself
let tD = [...data];
const index = tD.findIndex((i) => i.id == selectedItem);
tD.splice(index, 1);
setSelectedItem(tD);
// or refetch data from the backend and update the state
const d = await api.getData();
setData(d);
setDeleting(false);
setSelectedItem(null);
};
useEffect(() => {
const fetchData = async() => {
setLoading(true);
const d = await api.getData();
setData(d);
setLoading(false);
}
fetchData();
}, [])
return loading ? <div>Loading...</div> : <>
{/*Other JSX elements*/}
{selectedItem && <div>
{/*prompt body*/}
<button onClick={() => {setSelectedItem(null)}}>Cancel</button>
<button disabled={deleting} onClick={() => {deleteHandler()}}>Delete</button>
{/*Button will be disabled when the DELETE request is sent*/}
</div>}
{data.map((row) => {
return <tr key={row.id}>
<td>...</td>
<td>...</td>
<td>
<button onClick={setSelectedItem(row.id)}>Delete</button>
</td>
</tr>
})}
</>
}
I'm working on implementing a braintree payment method in my react/mui app. I've found a way that works, but it's in a class component. How can I convert this info a proper functional component?
const BraintreeDropInPaymentMethod = () => {
class Store extends React.Component {
instance;
state = {
clientToken: '<BRAIN TREE KEY>'
};
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
async buy() {
const { nonce } = await this.instance.requestPaymentMethod();
await fetch(`server.test/purchase/${nonce}`);
}
render() {
if (!this.state.clientToken) {
return (
<div>
<h1>Loading...</h1>
</div>
);
} else {
return (
<div>
<DropIn
options={{ authorization: this.state.clientToken }}
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant='contained'
onClick={this.buy.bind(this)}
>
Create Account
</Button>
<Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
Cancel
</Button>
</div>
);
}
}
}
const [user, setUser] = useState({})
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
return (
<Store/>
)
}
this is my attempt, but I'm coming up short on how I should handle componentDidMount(). I know how to handle useState in some situations, except for this one. Also, how can I handle the 'instance' section in a functional format? thanks.
const BraintreeDropInPaymentMethod = () => {
const [token, setToken] = useState('<BRAIN TREE KEY>')
const [user, setUser] = useState({})
const contactServer = async () => {
const res = await fetch('server.test/client_token')
const clientToken = await res.json()
console.log(clientToken)
setToken(token)
}
const buy = async () => {
const { nonce } = await this.instance.requestPaymentMethod()
await fetch(`server.test/purchase/${nonce}`)
}
const handleCancelAccountCreation = (event) => {
setUser({})
document.getElementById('signInBtn').hidden = false
}
const createAccountOptions = () => {
if (!token) {
return (
<div>
<h1>Loading...</h1>
</div>
) else {
return (
<div>
<DropIn
options={ authorization: {setToken})
onInstance={(instance) => (this.instance = instance)}
/>
<Button
variant="contained'
onClick={buy}
>
Create Account
</Button
variant='outlined'
sx={{ marginLeft: 3 }}
color='warning'
onClick={(e) => handleCancelAccountCreation(e)}
href='/store-front'
>
<Button>
Cancel
</Button>
</div>
)
}
}
}
return(
<>
<createAccountOptions/>
</>
)
}
The functional equivalent of componentDidMount() is the useEffect hook.
In this case you would change this:
async componentDidMount() {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
this.setState({
clientToken,
});
}
Into something like this:
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const response = await fetch("server.test/client_token");
const clientToken = await response.json();
setState((old) => clientToken);
};
Using the useEffect hook with an empty array as a dependency makes the function in it only run once as the component mounts.
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
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)
}
}
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)} />
)}
</>