Skip to content
Advertisement

I am trying to make my program list blog posts in order by the number of likes each blog has

I am currently working on a blog website and I want to list out the blogs in ascending order based on the amount of likes each blog has. However, whenever I run my current code, whenever I click “view” for one post, all of the post expand when I only want one of the post to expand, furthermore, whenever I hit “like” a like is left on all posts rather than just the one I left i like on.

Here is my code in App.js:

import { useState, useEffect } from 'react'
import Blog from './components/Blog'
import blogService from './services/blogs'
import loginService from './services/login'
import BlogForm from './components/BlogForm'
import LoginForm from './components/LoginForm'
import Togglable from './components/Toggable'

const App = () => {
  const [blogs, setBlogs] = useState([])
  const [user, setUser] = useState('')
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [errorMessage, setErrorMessage] = useState('')

  useEffect(() => {
    blogService.getAll().then(blogs =>
      setBlogs( blogs )
    )  
  }, [errorMessage])

  useEffect(() => {
    const loggedUserJSON = window.localStorage.getItem('user')
    if (loggedUserJSON) {
      const user = JSON.parse(loggedUserJSON)
      setUser(user)
      blogService.setToken(user.token)
    }
  }, [])

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      const user = await loginService.login({
        username, password
      })

      window.localStorage.setItem('user', JSON.stringify(user))

      blogService.setToken(user.token)
      setUser(user)
      setUsername('')
      setPassword('')
    } catch (exception) {
      setErrorMessage('Wrong credentials')
      setTimeout(() => {
        setErrorMessage(null)
      }, 5000)
    }
  }

  const handleLogout = (e) => {
    e.preventDefault();
    window.localStorage.clear()
    setUser('')
  }

    if (user === '') { 
      return (
        <Togglable buttonLabel='login'>
              <LoginForm
              username={username}
              password={password}
              handleUsernameChange={({ target }) => setUsername(target.value)}
              handlePasswordChange={({ target }) => setPassword(target.value)}
              handleSubmit={handleLogin}
              errorMessage={errorMessage}
            />
        </Togglable>
      )} else {
          return (
            <div>
                <h2>blogs</h2>
                <h3>{errorMessage}</h3>
                <nobr>{user.name} logged in</nobr> <button onClick={handleLogout}>logout</button>
                <h2>create new</h2>
                <Togglable buttonLabel='create new'>
                  <BlogForm errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
                </Togglable>
                  <Blog blogs={blogs} setErrorMessage={setErrorMessage}/>
                </div>
              )
        }
}

export default App

Finally, here is my code that deals specifically with each blog:

import { useEffect, useState, useRef } from "react"
import blogService from '../services/blogs'
const baseUrl = '/api/blogs'

const Blog = ({blogs, setErrorMessage}) => {
  const [checker, setChecker] = useState(false)
  const [blogLikes, setBlogLikes] = useState(0)
  const buttonText = checker  ? 'hide' : 'view'

  const blogStyle = {
    paddingTop: 10,
    paddingLeft: 2,
    border: 'solid',
    borderWidth: 1,
    marginBottom: 5
  }

  return (
    <>
  {blogs.map(blog => {
    return (
      <>
  {buttonText === "view" ?   
  <div style={blogStyle}>
    {blog.title} {blog.author} <button onClick={() => setChecker(!checker)}>{buttonText}</button>
  </div>
  : <div style={blogStyle}>
      {blog?.title} {blog.author} <button onClick={() => setChecker(!checker)}>{buttonText}</button>
      <p>{blog.url}</p>
      likes {blogLikes} <button onClick={(e, blog) => {
        e.preventDefault()
        setBlogLikes(blogLikes + 1)
        blogService.update(blog?.id, {
          user: blog.user?.id,
          likes: blogLikes,
          author: blog.author,
          title: blog.title,
          url: blog.url})
        setErrorMessage(`You liked ${blog.author}s post`)
      }}>like</button>
      <p>{blog.user?.username}</p>
    </div>
    }</>)})}
    </>
  )
}
export default Blog

Advertisement

Answer

The mistake you did here is you created Blog.js(or whatever name you gave to the source file), you are treating it as an individual blog, and expecting ‘blogLikes` to work for an individual blog.

What structure needs to be. list-item


Create separate components for list and item and include list component into the app. The quick fix will be,

App.js

import { useState, useEffect } from 'react'
import BlogList from './components/Blog'
import blogService from './services/blogs'
import loginService from './services/login'
import BlogForm from './components/BlogForm'
import LoginForm from './components/LoginForm'
import Togglable from './components/Toggable'

const App = () => {
  const [blogs, setBlogs] = useState([])
  const [user, setUser] = useState('')
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [errorMessage, setErrorMessage] = useState('')

  useEffect(() => {
    blogService.getAll().then(blogs =>
      setBlogs( blogs )
    )  
  }, [errorMessage])

  useEffect(() => {
    const loggedUserJSON = window.localStorage.getItem('user')
    if (loggedUserJSON) {
      const user = JSON.parse(loggedUserJSON)
      setUser(user)
      blogService.setToken(user.token)
    }
  }, [])

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      const user = await loginService.login({
        username, password
      })

      window.localStorage.setItem('user', JSON.stringify(user))

      blogService.setToken(user.token)
      setUser(user)
      setUsername('')
      setPassword('')
    } catch (exception) {
      setErrorMessage('Wrong credentials')
      setTimeout(() => {
        setErrorMessage(null)
      }, 5000)
    }
  }

  const handleLogout = (e) => {
    e.preventDefault();
    window.localStorage.clear()
    setUser('')
  }

    if (user === '') { 
      return (
        <Togglable buttonLabel='login'>
              <LoginForm
              username={username}
              password={password}
              handleUsernameChange={({ target }) => setUsername(target.value)}
              handlePasswordChange={({ target }) => setPassword(target.value)}
              handleSubmit={handleLogin}
              errorMessage={errorMessage}
            />
        </Togglable>
      )} else {
          return (
            <div>
                <h2>blogs</h2>
                <h3>{errorMessage}</h3>
                <nobr>{user.name} logged in</nobr> <button onClick={handleLogout}>logout</button>
                <h2>create new</h2>
                <Togglable buttonLabel='create new'>
                  <BlogForm errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
                </Togglable>
                  <BlogList blogs={blogs} setErrorMessage={setErrorMessage}/>
                </div>
              )
        }
}

export default App

BlogList.js

To sort blogs by likes you apply Array.sort() before mapping.

import Blog from './Blog';

const BlogList = ({blogs, setErrorMessage}) => {
    return (
        <>
        {
            blogs.sort((a, b)=> a.likes > b.likes ? 1 : -1 ).map((blog, i) => <Blog key={i} blog={blog} setErrorMessage={setErrorMessage} />
        }
        </>
    )
}
export default BlogList;

Blog.js

import blogService from '../services/blogs';
import { useEffect, useState, useRef } from 'react';

const Blog = ({blog, setErrorMessage}) => {
    const [checker, setChecker] = useState(false)
    const [blogLikes, setBlogLikes] = useState(blog.likes)
    const buttonText = checker  ? 'hide' : 'view'
    
    const blogStyle = {
        paddingTop: 10,
        paddingLeft: 2,
        border: 'solid',
        borderWidth: 1,
        marginBottom: 5
    }
    
    return (
        <>
        {buttonText === "view" ?
        <div style={blogStyle}>
        {blog.title} {blog.author} <button onClick={() => setChecker(!checker)}>{buttonText}</button>
        </div>
        : <div style={blogStyle}>
        {blog?.title} {blog.author} <button onClick={() => setChecker(!checker)}>{buttonText}</button>
        <p>{blog.url}</p>
        likes {blogLikes} <button onClick={(e, blog) => {
            e.preventDefault()
            setBlogLikes(blogLikes + 1)
            blogService.update(blog?.id, {
                user: blog.user?.id,
                likes: blogLikes,
                author: blog.author,
                title: blog.title,
                url: blog.url})
                setErrorMessage(`You liked ${blog.author}s post`)
            }}>like</button>
            <p>{blog.user?.username}</p>
            </div>
        }
        </>
    )
}
export default Blog

Since you have a separate state for likes here in the Blog component its action or data won’t affect other blog components.


[update] If you are familiar with context then try it, for the beginner you move like action to App.js and pass it as a prop just like you did for setErrorMessage. Also, add empty useEffect with blogs dependency in App.js so its change will cause re-render.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement