Home > Mobile >  Rerender map with new values on button click without using state
Rerender map with new values on button click without using state

Time:11-11

So I have been trying to figure out how to rerender the items map with new items when I click the "Mark Read" button. I tried putting the items in a useState and updating the state on button click, but the issue with that is when I set the state it's always one render behind because set state is async. How would I go about implementing this?

const Main: React.FC<MainProps> = (props) => {
  // Get All Feeds
  const feeds = trpc.feed.getAllFeeds.useQuery();

  // Get Items
  let unsorteditems;
  if (props.FeedType == "one") {
    unsorteditems = trpc.feed.getItemsFromFeed.useQuery({
      feedId: props.feedId,
    });
  } else if (props.FeedType == "readalready") {
    unsorteditems = trpc.feed.getReadItems.useQuery();
  } else {
    unsorteditems = trpc.feed.getFeedItemsFromAllFeeds.useQuery();
  }

  // Sort Items than add title, logo, and markedAsRead
  const sorteditems = unsorteditems.data?.sort(
    (objA, objB) => Number(objB.createdAt) - Number(objA.createdAt)
    // Replace in other order to sort in reverse
  );
  let items = sorteditems?.map((item) => {
    const feed = feeds.data?.find((feed) => feed.id == item.feedId);
    return {
      ...item,
      feedTitle: feed?.title,
      feedLogoUrl: feed?.LogoUrl,
      markedAsRead: false,
    };
  });

  const feedName = items?.[0]?.feedTitle;

  // Mark Read function
  const markreadmutation = trpc.feed.markReadUsingUpsert.useMutation();
  const onClickMarkRead = async (itemId: string) => {
    markreadmutation.mutate({ itemId: itemId });
  };

  // I tried this but it didn't work, and I can't use state because its async and dosent update in time
  function updateItems(index: number) {
    const newItems = items?.map((item, i) => {
      if (i == index) {
        return {
          ...item,
          markedAsRead: true,
        };
      } else {
        return item;
      }
    });
    items = newItems;
  }

  return (
    <>
      {/* I removed alot of code here to make it easier to understand so css may look weird */}
      {/* <!-- Main --> */}
      <div className="basis-5/12 border-l border-slate-300/40">
        <h1 className="sticky top-0 z-50 border-b border-slate-300/40 bg-opacity-60 bg-clip-padding py-2 pl-2 text-xl font-bold backdrop-blur-xl backdrop-filter">
          {props.FeedType == "one"
            ? feedName
            : props.FeedType == "all"
            ? "All Feeds"
            : props.FeedType == "readalready"
            ? "Recently Read"
            : "Woops"}
        </h1>
        <div>
          {items?.length == 0 && <p>No Items</p>}
          {items?.map((item, i) => (
            <div
              className={`border-slate-300/4 relative flex h-28 flex-col border-b ${
                item.markedAsRead ? "bg-gray-200" : "bg-white"
              }`}
              key={item.id}
            >
              {/* ^^^ If item is marked read it will be grayed out ^^^ */}
              <button
                onClick={() => {
                  // THIS BUTTON
                  // When I click this is should rerender the map with the item marked as read
                  onClickMarkRead(item.id);
                  updateItems(i);
                }}
              >
                [Mark Read]
              </button>
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

CodePudding user response:

You better use state. You can't access asynchronous code on first render of React component. And in order to use the latest value of the items, you should store your items in state, then create useCallback function for your async calls and items manipulation (sort etc) and finally call this function in useEffect. It will look something like this:

const [items, setItems] = useState([]);

const getItems = useCallback( async() => {
  const fetchedItems = await yourFetchFunction();
  setItems(fetchedItems);
}, []);

useEffect(() => {
  getItems();
}, [getItems]);

CodePudding user response:

After clicking the button, you should call the updateItems only after awaiting the mutation. In the current code provided you call it synchronously. You can add it inside onClickMarkRead. Pass the index in it and try like this (in updateItems set items in state after updating them):

const onClickMarkRead = async (itemId: string, index: number) => {
  await markreadmutation.mutate({ itemId: itemId });
  updateItems(index);
};

If it still doesn't work, you can provide the whole component with the latest code changes via jsFiddle or CodePen or GitHub, so I can see and eventually play around with it.

  • Related