-1

I'm working on an React app that lists my favorite music albums. Everything worked fine until i implemented the change order (ascend, descend) and the sort list buttons. On the first click it doesn't react, but on the second click it logs the value of the previous click and renders the previous value.
I'm fetching the data from server database, and everything works when I manually change order and sort values and than save the changes. It renders immediately and it looks good, but when i implemented the function to change those values on a button click, then the problems started.

I've tried multiple solutions that I found on other similar questions, but nothing worked. I've tried useCallback, I've added useEffect for console.log, I've tried using ordinary variables instead of setState, changing the dependencies in useEffect, and several other solutions I found but nothing worked for me. It always changes on the second click, and then on every other click it shows the previous value.

This is my code:

import { useEffect, useState } from "react";
import Album from "../Album";

export default function IndexPage() {
    const [albums, setAlbums] = useState([]);
    const [order, setOrder] = useState(1);
    const [key, setKey] = useState("title");
    const [sort, setSort] = useState({ title: 1 });

    useEffect(() => {
        fetch(`http://localhost:4000/list?${new URLSearchParams(sort).toString()}`).then(response => {
            response.json().then(albums => {
                setAlbums(albums);
            });
        });
    }, [albums]);

    function toggleOrder() {
        order === 1 ? setOrder(-1) : setOrder(1);
        setSort({ [key]: order });
        console.log(order);
    }

    function selectSort(selection) {
        setKey(selection);
        setSort({ [key]: order });
    }

    return (
        <div className="list">
            <div className="album-buttons">
                <button className="addTrack edit" onClick={toggleOrder}>⬍ Order</button>
                <p>|</p>
                <div className="sort">
                <button className="addTrack edit">Sort by: {key.charAt(0).toUpperCase()+key.slice(1)} ▾</button>
                <div className="dropdown-sort">
                    <button className="addTrack edit" onClick={() => {selectSort("title")}}>Title</button>
                    <button className="addTrack edit" onClick={() => {selectSort("artist")}}>Artist</button>
                    <button className="addTrack edit" onClick={() => {selectSort("year")}}>Year</button>
                    <button className="addTrack edit" onClick={() => {selectSort("genre")}}>Genre</button>
                </div>
                </div>
            </div>
            {albums.length > 0 && albums.map((album, i) => (
                <Album key={i} {...album} />
            ))}
        </div>
    )
}

I understood that React should re-render every time I use setState, but I don't understand why I's not changing in my situation. Any help is very appreciated.

Mor
  • 1
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – David Feb 22 '23 at 13:21
  • It'll probably be easier if you combine some of these state values into a single object as one state value. If `order`, `key`, and `sort` are all related to sorting then they really shouldn't be kept separate. Use a single object to track the sort logic and update that entire object any time the sort changes. Then the problem of waiting for other state values to update becomes moot. – David Feb 22 '23 at 13:25
  • Thank you David, I combined order and key in a single object "sort" and it worked!!! Thank you very much! – Mor Feb 22 '23 at 16:36

1 Answers1

1

It looks like your useEffect hook depandancies are incorrect:

  useEffect(() => {
        fetch(`http://localhost:4000/list?${new URLSearchParams(sort).toString()}`).then(response => {
            response.json().then(albums => {
                setAlbums(albums);
            });
        });
    }, [albums]);

Since the hook has albums as a dependancy, then when the data loads the hook will be triggered again. This will cause a loop, and re-render the page each time.

Remove albums from your dependancy list, add sort and key, and this will fix your problem.

  useEffect(() => {
        fetch(`http://localhost:4000/list?${new URLSearchParams(sort).toString()}`).then(response => {
            response.json().then(albums => {
                setAlbums(albums);
            });
        });
    }, [JSON.stringify(sort), key]);

The sort needs to stringified, as the hook will not pick up changes in a complex object natively.

gunwin
  • 4,578
  • 5
  • 37
  • 59
  • It works both as an object and stringified. But thank you nontheless, I'll keep it in mind for the future projects. – Mor Feb 22 '23 at 16:43