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