Strapi
Add Remove Items with Strapi Connect Disconnect

Strapi connect - add and remove items from relations with NextJS

When you create relation in strapi, it can add/remove preserving other items, similar to merge with spread operator in frontend ie: [...array, 5]

Managing Relations With Strapi (opens in a new tab)

Using default action of connect and disconnect from Strapi make us avoid to make a GET request just to check wheather item is in list or not.

  • You may want some utils:

cn Tailwind Function Utils Local Storage

./app/src/app/components
"use client"; // Any import begining with useSomething is client side makink it mandatory
 
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { cn } from "../utils/cn"; // to merge default and props classes
import { useSession } from "next-auth/react"; // session settled in next-auth file auth.ts
import { authService } from "../services/authService"; // strapi backend with fetch, or axios or any other
import { storage } from "../utils/storage"; // localStorage methods: setItem, getItem, removeItem
import { IUserResponse } from "../interfaces/userInterface"; // User interface with default properties, custom properties and merged jwt from strapi into next-auth
 
interface IProductProps {
  id: number;
  slug: string;
  productName: string;
  className?: string;
  displayFavourites?: boolean;
}
 
 
const api_url = process.env.NEXT_PUBLIC_API_URL;
 
export default function ProductItem({
  id,
  slug,
  productName,
  displayFavourites = true, // may want to disable in some places
  className,
}: IProductProps) {
  const [favouriteHover, setFavouriteHover] = useState(false); // change star when hovered
  const [favourites, setFavourites] = useState<IUserResponse["user"]["favourites"] | null>(null);
  const session = useSession(); // user side session data
  const user = session?.data?.user; // variable to shorten
  const jwt = user?.jwt; // jwt generated in strapi and merged in next auth, the auth.js file
 
  const handleFavourites = async (event: React.MouseEvent<HTMLElement>) => {
    event.preventDefault(); // prevent default classic submit and do the below instead
    const idData = (event.currentTarget as HTMLElement).getAttribute("data-id"); // id from div as String
    const id = idData ? +idData : null; // + convert to number for strapi 4 or null when fail to pass
    const isProductInList = favourites?.some(favourite => favourite.id === id); // check state fed by localStorage true OR false
    const action = isProductInList ? "disconnect" : "connect"; // true = in the list do disconnect OR false = out of list do connect
 
    if(id && jwt){
      const user = { 
        favourites: {
          [action]: [id] // connect | disconnect: will add in backend as new array with unique item
        }
      } 
 
      const headers: HeadersInit = {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${jwt}` // Very IMPORTANT
      };
 
      const response = await fetch(`${api_url}/api/user/me`, { method: "PUT", body: JSON.stringify(user)}, headers, { body: JSON.stringify(user) })
        .then((response) => {
          if ("user" in response && "favourites" in response) { // Good response
            const newfavourites = response.user.favourites;
            storage.setItem("favourites", newfavourites); // update the storage
            setFavourites(newfavourites); // Update the state/view
          }
        })
        .catch((error)=>{
          console.log("check and treat erros": error);          
        });
        
      if("user" in response){ // user response came valid
          const updateFavourites = response.user.favourites;
          storage.setItem("favourites", updateFavourites); // /localstorage-utils
          setFavourites(updateFavourites); // update favourites state
      }
 
      return; // Return stop if id && jwt = Good
    }
    
    console.log("Favourites failed to get: id or jwt: ", id, jwt ); // Check empty or invalid values
  }
 
  
  useEffect(() => { // Update on start
    const retrieveFromStorageOrDatabase = () => {
      const storedfavourites = storage.getItem("favourites"); // retrieve previous written favourites on load
  
      if (storedfavourites) { // if there is no favourites in storage
        setFavourites(storedfavourites); // update as empty
        return; // stop at return void
      }
  
      if (jwt && storedfavourites === null) { // Try to find user favourites
        const headers: HeadersInit = {
          Accept: "application/json",
          "Content-Type": "application/json",
          Authorization: `Bearer ${jwt}` // user jwt is the most IMPORTANT here
        };
 
        fetch(`${api_url}/api/user/me`, {headers})
        .then((response) => {
          if ("user" in response && "favourites" in response) { // Has the minimum properties in response
            const newfavourites = response.user.favourites;
            storage.setItem("favourites", newfavourites); // update the storage
            setFavourites(newfavourites); // Update the state/view
          }
        })
        .catch((error)=>{
          console.log("check and treat erros": error);          
        });
      }
    }
 
    retrieveFromStorageOrDatabase();
  }, [jwt]);
 
  const isProductInFavourites = favourites?.some(favourite => favourite.id === id); // check if inside the array
 
  return (
    <Link
      href={`/nome/${slug}`} // make link with slug
      className={cn("flex items-center w-full", className)} // merge static and the dynamic classes from props
    >
      {/* Favourite Icon = Star, Heart, Book */}
      {displayFavourites && ( // may want to disable in some places
        <span
          title="Favourite"
          data-id={id} // id to add
          onMouseEnter={() => setFavouriteHover(true)} // hovering the icon turns on 
          onMouseLeave={() => setFavouriteHover(false)} // hover out icon turns off 
          onClick={handleFavourites} // call funtion and get event data
        >
          <i
            className={`bx ${(favouriteHover || isProductInFavourites) ? "bxs-star text-yellow-500" : "bx-star"}`} // on/off icon in hover and when in list
          />
        </span>
      )}
 
      <span>{productName}</span>
    </Link>
  );
}

Usage

Single call

<ProductItem id={5} name="Laptop RTX 4090" slug="laptop-rtx-4090" />

Or multiple calls inside array or functions, distributing the component and clicks

array.map(()=> <ProductItem id={5} name="Laptop RTX 4090" slug="laptop-rtx-4090" />)
Last updated on