How can i stop this infinite loop in my next.js app? (Search page via url params) - javascript

I want to create a search page that in driven by the url query. It includes params such as:
searchQuery e.g. "nike shoes"
page e.g. "1"
etc.
However im struggling at the moment with getting it work without heading into an infinite loop.
Here is my search page:
function Search() {
const [listings, setListings] = useState<null | Array<ListingObject>>(null);
const [noResultsListings, setNoResultsListings] = useState<
Array<ListingObject>
>([]);
const [loading, setLoading] = useState("idle");
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
const [paginationData, setPaginationData] = useState<PaginationData>({
totalHits: 0,
totalPages: 0,
});
const { query } = Router;
function onFilterCheck(checked: Boolean, id: string, option) {
let query = Router.query;
if (checked) {
if (query[id]) {
query[id] += `,${option.value}`;
} else {
query[id] = `${option.value}`;
}
}
if (!checked) {
if (query[id] && query[id].toString().split(",").length > 1) {
let param = query[id].toString();
let array = param.split(",");
query[id] = array.filter((param) => param !== option.value);
} else {
delete query[id];
}
}
Router.push(
{
pathname: `/search`,
query,
},
undefined,
{ shallow: true }
);
}
const onSortByChanged = (sortByIndex: string) => {
let { query } = Router;
if (sortByIndex) {
query.sortBy = sortByIndex;
Router.push(
{
pathname: `/search`,
query,
},
undefined,
{ shallow: true }
);
}
};
const search = useCallback(async (query) => {
console.log("in search");
if (loading === "idle") {
console.log("inside IF");
setLoading("loading");
const readyQuery = await handleSearchQuery(query);
let listingIndex = searchClient.initIndex(query.sortBy ?? "listings");
const response = await listingIndex.search(readyQuery.searchQuery, {
numericFilters: readyQuery.filters.numericFilters,
facetFilters: readyQuery.filters.facetFilters,
page: query.page - 1 ?? 0,
});
if (response) {
const hits = response.hits;
setPaginationData({
totalPages: response.nbPages,
totalHits: response.nbHits,
});
if (hits.length === 0) {
const response = await getNewListings(3);
if (response.success) {
setNoResultsListings(response.listings);
}
}
setListings(hits);
}
setLoading("idle");
}
}, []);
const handlePageClick = useCallback(
(pageNumber) => {
console.log("page number from handlePageClick", pageNumber);
if (loading === "idle") {
let { query } = Router;
query.page = (parseInt(pageNumber) + 1).toString();
Router.push(
{
pathname: `/search`,
query,
},
undefined,
{ shallow: true }
);
search(query);
window.scrollTo(0, 0);
}
},
[loading, search]
);
useEffect(() => {
search(query);
}, [search, query]);
useEffect(() => {
if (process.env.NODE_ENV === "production") {
hotjar.initialize(
parseInt(process.env.NEXT_PUBLIC_HOTJAR_ID),
parseInt(process.env.NEXT_PUBLIC_HOTJAR_SV)
);
}
}, []);
if (!Router.query.page) {
const { query } = Router;
query.page = "1";
Router.push({ pathname: "/search", query }, undefined, {
shallow: true,
});
}
return (
<main className="mx-auto w-full transition-all duration-200 ease-in md:px-0">
<section className="flex w-full flex-col pt-0 md:flex-row">
<div className="hidden min-h-screen w-1/5 grid-cols-1 md:grid lg:grid-cols-1 lg:pr-4">
{/* Desktop filters */}
<form className="hidden h-full overflow-y-auto p-0 md:inline-block">
<div className="relative z-30 flex h-full flex-col border-r pb-6 pr-6 md:z-0">
<SearchSortBy onSortByChanged={onSortByChanged} />
<SearchFilters
mobileFiltersOpen={mobileFiltersOpen}
setMobileFiltersOpen={() => {
setMobileFiltersOpen(false);
}}
onFilterCheck={onFilterCheck}
/>
</div>
</form>
</div>
<span className="flex items-center justify-between">
<SearchSortBy
className="md:hidden"
onSortByChanged={onSortByChanged}
/>
<Button
className="!px-4 md:hidden"
variant="none"
type="button"
onClick={() => {
setMobileFiltersOpen(true);
}}
>
<FilterIcon className="w-4 text-blue-600" />
</Button>
</span>
<SearchResultsGrid
listings={listings}
noResultsListings={noResultsListings}
/>
</section>
{listings?.length > 0 && (
<SearchPagination
pageNumber={parseInt(Router?.query?.page.toString()) - 1}
paginationData={paginationData}
handlePageClick={handlePageClick}
/>
)}
</main>
);
}
export default Search;
If I search something on my site, the console will look like this:
in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:92 in search
Search.tsx?c6ec:94 inside IF
I tried using loading as a gate, where it will only search if it is equal to idle, however I change the the loading state at the end of the search back to idle, therefore it triggers a loop.
Does anybody have any experience in how I will handle this? Is adding the router query to a useEffect to fire the search the right way to go? Or should I just pass the search function to all the things that need it?

Related

In nextjs . I want to block navigation and appear a confirmation window popup when trying to navigate to another page

In nextjs . I want to block navigation and appear a confirmation window popup when user trying to navigate to another page. the navigation should continue if the user click yes in the confirmation popup. If the user clicks the "no" in confirmation window , the user should stay in the current page.
There is no way in the official nextjs documentation about blocking a router.
You can use JavaScript's window.onbeforeunload event to do that.
See an example code below
const ExamplePage = () => {
useEffect(() => {
window.onbeforeunload = () => true;
return () => {
window.onbeforeunload = null;
};
}, []);
return (
<div>
<h1>Example Page</h1>
<p>This page will display a confirmation dialog when navigating away.</p>
</div>
);
};
export default ExamplePage;
If you are using NextJS 12 or less you can have the following:
Stack
NextJS 12.x
Typescript
Tailwind
Some custom components, replace on your own.
The idea behind the components below is to get when the router fires the routeChangeStart and stop it immediately, then we have two ways of getting user confirmation, you can use the window.confirm() which cannot be styled, so you probably don't want that or you can trigger an async function (which is used on the example), pass a callback so you can continue the action in case the user press "Yes" and throw immediately after, if you don't do that, it will continue the route change.
BeforePopState is when you fire the go back / go forward on the router/browser.
BeforeUnload is when the user refreshes or clicks to exit the page (in this case you can only show the browser default window.
To help you understand better and implement, here is the logic I have created.
Code examples
/hooks/useWarnIfUnsaved
import getUserConfirmation, {
IConfirmationBody,
IConfirmationDialogConfig,
} from "#/utils/ui/confirmationDialog"
import Router from "next/router"
import { useEffect, useRef } from "react"
import useWarnBeforeUnload from "./useWarnBeforeUnload"
export interface TConfirmationDialog {
body: IConfirmationBody
config?: IConfirmationDialogConfig
}
/**
* Asks for confirmation to leave/reload if there are unsaved changes.
* #param {boolean} unsavedChanges Whether there are unsaved changes. Use a ref to store the value or make a comparison with current and previous values.
* #param {TDialog} confirmationDialog [Optional] The dialog to show.
* #returns {void}
*/
export default function useWarnIfUnsaved(
unsavedChanges: boolean,
confirmationDialog: TConfirmationDialog
) {
const unsavedRef = useRef(unsavedChanges)
// * Keep unsaved in sync with the value passed in.
// * At the same time we want to be able to reset
// * the trigger locally so we can move forward on the redirection
useEffect(() => {
unsavedRef.current = unsavedChanges
}, [unsavedChanges])
useWarnBeforeUnload(unsavedRef.current)
useEffect(() => {
const handleRouteChange = (url: string) => {
if (unsavedRef.current && Router.pathname !== url) {
Router.events.emit("routeChangeError")
getUserConfirmation(confirmationDialog.body, {
...confirmationDialog.config,
callbackAfterConfirmation: () => {
unsavedRef.current = false
Router.replace(url)
},
})
throw "Route change aborted. Ignore this error."
}
}
Router.beforePopState(({ url }) => {
if (unsavedRef.current) {
if (Router.pathname !== url) {
getUserConfirmation(confirmationDialog.body, {
...confirmationDialog.config,
callbackAfterConfirmation: () => {
unsavedRef.current = false
window.history.pushState("", "", url)
},
})
return false
}
}
return true
})
// For changing in-app route.
if (unsavedRef.current) {
Router.events.on("routeChangeStart", handleRouteChange)
} else {
Router.events.off("routeChangeStart", handleRouteChange)
}
return () => {
Router.beforePopState(() => true)
Router.events.off("routeChangeStart", handleRouteChange)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [unsavedRef.current])
}
#/utils/ui/confirmationDialog
import Button from "#/components/Buttons/Button.primitive"
import DynamicIcon from "#/components/Icons/DynamicIcon"
import Text from "#/components/Typography/Text.primitive"
import { PolarisIcons } from "#/types/polarisIcons.types"
import { createRoot } from "react-dom/client"
import { cn } from "../className"
export interface IConfirmationDialogConfig {
overrideBody?: JSX.Element
cancelButtonText?: string
okButtonText?: string
title?: string
hideOkButton?: boolean
confirmationInput?: string
className?: string
bodyClassName?: string
isDeleteConfirmation?: boolean
callbackAfterConfirmation?: () => void
callbackAfterCancel?: () => void
}
export interface IConfirmationBody {
icon?: PolarisIcons | JSX.Element
title: string
message: string
}
interface IRenderDialogProps {
resolve: (value: { value: string } | PromiseLike<{ value: string }>) => void
body: IConfirmationBody
config?: IConfirmationDialogConfig
}
type TPromiseResolve = (
value:
| {
value: string
}
| PromiseLike<{
value: string
}>
) => void
const renderDialog = (
body: IConfirmationBody,
resolve: TPromiseResolve,
config?: IConfirmationDialogConfig
) => {
const root = document.querySelector("body")
if (!root) {
console.error("No root element found.")
return
}
const div = document.createElement("div")
div.setAttribute("id", "confirmationDialogContainer")
div.setAttribute(
"class",
"h-screen w-screen fixed z-50 inset-0 grid place-items-center globalConfirmationModalContainer"
)
root.appendChild(div)
const container = document.getElementById("confirmationDialogContainer")
if (container === null) {
console.error("Container was not found.")
}
const dialog = createRoot(container as HTMLElement)
return dialog.render(
<ConfirmationDialog body={body} resolve={resolve} config={config} />
)
}
const removeDialog = () => {
const root = document.querySelector("body")
if (!root) {
console.error("No root element found.")
return
}
const divs = root.querySelectorAll(".globalConfirmationModalContainer")
divs && divs.forEach((div) => root.removeChild(div))
}
const ConfirmationDialog = ({ resolve, body, config }: IRenderDialogProps) => {
const clickOK = () => {
removeDialog()
config?.callbackAfterConfirmation?.()
resolve({ value: "true" })
}
const clickCancel = () => {
removeDialog()
config?.callbackAfterCancel?.()
resolve({ value: "" })
}
return (
<div
className="fixed inset-0 grid h-screen w-screen place-items-center"
style={{ zIndex: 9999 }}
>
<div
className="fixed inset-0 z-0 h-screen w-screen bg-black opacity-60"
onClick={clickCancel}
onKeyDown={() => undefined}
role="button"
tabIndex={0}
/>
<div className="dark:bg-blue-dark relative z-10 max-w-[95vw] rounded-lg bg-white shadow-lg">
<div className="p-14">
{config?.overrideBody ? (
<></>
) : (
<>
<div className="flex items-center justify-between border-b border-ds-gray-300 p-6">
<Text
as="p"
className="text-sm font-extrabold"
color="text-ds-primary-700 dark:text-ds-primary-200"
>
{config?.title || "Confirmation"}
</Text>
<Button
variant="icon"
subvariant="solo"
onClick={clickCancel}
iconName={PolarisIcons.CancelMajor}
className="border-none !p-0"
/>
</div>
<div
className={cn(
"flex flex-col items-center justify-center px-7 py-12",
config?.bodyClassName
)}
>
{body.icon && (
<div className="mb-4 flex items-center justify-center rounded-full">
{typeof body.icon === "string" ? (
<DynamicIcon
iconName={body.icon as PolarisIcons}
width={120}
className="max-w-[25%] bg-ds-gray-400 dark:bg-ds-gray-700"
/>
) : (
body.icon
)}
</div>
)}
<div className="px-8 text-center">
<Text
as="h3"
className="mb-2 text-lg font-extrabold leading-[1.44]"
color="text-ds-primary-700 dark:text-ds-primary-200"
>
{body.title}
</Text>
<Text
as="p"
className="text-sm leading-[1.57] text-ds-gray-600 dark:text-ds-gray-400"
font="inter"
>
{body.message}
</Text>
</div>
</div>
<div className="flex items-center justify-end gap-3 border-t border-ds-gray-300 p-6">
<Button
variant="main"
subvariant="outlinePrimary"
size="md"
onClick={clickCancel}
className="text-ds-gray-600 dark:text-ds-gray-400"
>
{config?.cancelButtonText || "Cancel"}
</Button>
{!config?.hideOkButton && (
<Button
variant="main"
subvariant={
config?.isDeleteConfirmation ? "error" : "primary"
}
onClick={clickOK}
size="md"
>
{config?.okButtonText || "Ok"}
</Button>
)}
</div>
{/* <Text
as="h1"
label="Modal Title"
color="text-mirage dark:text-link-water"
>
{config?.title || "Confirmation"}
</Text>
<Text as="p">{body}</Text>
</div>
<div className="mt-10 flex items-center justify-center gap-3">
<Button
variant="main"
subvariant={
config?.isDeleteConfirmation ? "outlinePrimary" : "primary"
}
onClick={clickCancel}
>
{config?.cancelButtonText || "Cancel"}
</Button>
{!config?.hideOkButton && (
<Button
variant={config?.isDeleteConfirmation ? "5" : "2"}
onClick={clickOK}
className="px-8"
>
{config?.okButtonText || "Ok"}
</Button>
)}
</div>*/}
</>
)}
</div>
</div>
</div>
)
}
/**
*
* #param {IConfirmationBody} body
* #param {IConfirmationDialogConfig} config
* #returns
*/
const getUserConfirmation = (
body: IConfirmationBody,
config?: IConfirmationDialogConfig
): Promise<{ value: string }> =>
new Promise((resolve) => {
renderDialog(body, resolve, config)
})
export default getUserConfirmation
Usage
// only one simple example
const UnsavedChangesDialog: TConfirmationDialog = {
body: {
icon: <svg>ICON CODE OPTIONAL</svg>,
title: "Your title",
message:
"Your message",
},
config: {
okButtonText: "Yes",
cancelButtonText: "No",
title: "Unsaved changes",
isDeleteConfirmation: true,
bodyClassName: "px-[15vw]",
},
}
const hasChanges = useRef(false)
useWarnIfUnsaved(hasChanges.current, UnsavedChangesDialog)
const handleFormChange = (e: ChangeEvent<HTMLInputElement>) => {
hasChanges.current = true
...
}
const submitForm = (data) => {
...
if (success) {
// important to set as false when you actually submit so you can stop showing the message
hasChanges.current = false
}
}

React x Sanity || Button to Fetching All posts of users, and Button to fetch only posts of who the users follow

I'm new to react and Im working on a capstone project. My project is a web-app that is of a social media-like website, and im having a hard time debugging my code for days now. Issue when I press the buttons "All recipes" or "Following FYP", console throws me the string of my groq query, but im pretty sure my queries work right. Here are my codes:
`import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { fetchUser } from '../utils/fetchUser';
import { client } from '../client';
import { feedQuery, searchQuery, userFollowingPost, userQuery } from '../utils/data';
import MasonryLayout from './MasonryLayout';
import Spinner from './Spinner';
const activeBtnStyles = 'bg-nGreen text-white font-bold p-2 rounded-full w-20 outline-none';
const notActiveBtnStyles = 'bg-primary mr-4 text-black font-bold p-2 rounded-full w-20 outline-none';
const Feed = () => {
const [pins, setPins] = useState();
const [loading, setLoading] = useState(false);
const { categoryId } = useParams();
const [user, setUser] = useState();
const [text, setText] = useState('All Recipes');
const [activeBtn, setActiveBtn] = useState('All Recipes');
const navigate = useNavigate();
const { userId } = useParams();
// call the fetchUser function to get the user data
const users = fetchUser();
useEffect(() => {
if (categoryId) {
setLoading(true);
const query = searchQuery(categoryId);
client.fetch(query).then((data) => {
setPins(data);
setLoading(false);
});}
else if (text === 'All Recipes'){
client.fetch(feedQuery)
.then((data) => {
setPins(data);
setLoading(false);
});}
else
{
// pass the user data as a variable to the userFollowingPost function
const followingpost = userFollowingPost(users);
client.fetch(followingpost)
.then((data) => {
setPins(data);});
}
}, [categoryId, text, userId]);
useEffect(() => {
const query = userQuery(userId);
client.fetch(query).then((data) => {
if(data && data[0]){setUser(data[0])}
});
}, [userId]);
const ideaName = categoryId || 'new';
if (loading) {
return (
<Spinner message={`We are adding ${ideaName} recipes to your feed!`} />
);
}
return (
<div>
<div className="text-center mb-7">
<button
type="button"
onClick={(e) => {
setText(e.target.textContent);
setActiveBtn('All Recipes');
}}
className={`${activeBtn === 'All Recipes' ? activeBtnStyles : notActiveBtnStyles}`}>
All Recipe
</button>
<button
type="button"
onClick={(e) => {
setText(e.target.textContent);
setActiveBtn('Following FYP');}}
className={`${activeBtn === 'Following FYP' ? activeBtnStyles : notActiveBtnStyles}`}>
Following FY </button>
</div>
{pins && (
<MasonryLayout pins={pins} />
)}
{pins?.length == 0 &&
<div className="mt-10 text-center text-xl ">No Recipes Found!</div>
}
</div >
);
};
export default Feed;
For reference, here are my queries saved in data.js
`export const feedQuery = `*[_type == "pin"] | order(_createdAt desc) {
image{
asset->{
url
}
},
_id,
procedure[],
ingredient[],
ingredientVal[],
metric[],
postedBy->{
_id,
userName,
image
},
save[]{
_key,
postedBy->{
_id,
userName,
image
},
},
} `;
export const userFollowingPost = (userId) => {
const query = `*[_type == "pin" && userId in *[_type == "user" && id == '${userId}'].following[].userId] | order(_createdAt desc) {
image{
asset->{
url
}
},
_id,
procedure[],
ingredient[],
ingredientVal[],
metric[],
postedBy->{
_id,
userName,
image
},
save[]{
_key,
postedBy->{
_id,
userName,
image
},
},
}`;
console.log(query)
return query;
};
What I want to achieve is when i press the button "All recipes" it would fetch all the posts(pins). And when I press the button "Following FYP", it would fetch all the posts(pins) of those posts of the users whom he/she follows

After click on like button the whole website become disappear but after refresh the webpage it increments why does it happens?

I am working with a video application website with a fake json server. Everything is going well but the problem appears while I want to like a video it becomes disappears and throwing an error: Objects are not valid as react child. Why this is happening?
The whole state slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit"
import { getVideo, updateReaction } from "./videoApi"
//initializing the state
const initialState = {
video: {}, //while we are going to push a single video into the state we should put it an object instance...
loading: false,
isError: false,
error: ''
}
// async thunk for getting single video
export const fetchVideo = createAsyncThunk('vidoes/fetchVideo', async (id) => {
const videos = await getVideo(id);
return videos;
});
// another async thunk to react on a single video (like or dislike)
export const fetchReact = createAsyncThunk('video/reaction', async ({ id, reaction }) => {
const videoReaction = await updateReaction({ id, reaction })
return videoReaction;
})
//creating slice
const videoSlice = createSlice({
name: 'video',
initialState,
extraReducers: (builder) => {
builder.addCase(fetchVideo.pending, (state) => {
state.loading = true;
state.isError = false;
})
builder.addCase(fetchVideo.fulfilled, (state, action) => {
state.loading = false;
state.video= action.payload;
})
builder.addCase(fetchVideo.rejected, (state, action) => {
state.loading = false;
state.isError = true;
state.error = action.error?.message;
state.video = [];
})
//writing codes for emplementing like funcitonality
builder.addCase(fetchReact.pending, (state) => {
return state;
})
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload;
state.video.unlikes = action.payload;
})
builder.addCase(fetchReact.rejected, (state) => {
return state;
})
}
});
export default videoSlice.reducer;
This one is video api
import axios from '../../utils/axios'
export const getVideo = async (id) => {
const response = await axios.get(`/videos/${id}`)
return response.data;
}
export const updateReaction = async ({ id, reaction }) => {
console.log(id)
const { data } = await axios.get(`/videos/${id}`)
if (data) {
let updatedReaction =
reaction === 'like'
? {
likes: data.likes + 1,
}
: {
unlikes: data.unlikes + 1,
};
const response = await axios.patch(`/videos/${id}`, updatedReaction);
return response.data;
}
}
Video description component
import LIke from '../Like/LIke';
const VidoeDescription = ({ video }) => {
const { title, date, description, likes, unlikes, id } = video;
return (
<div>
<h1 className="text-lg font-semibold tracking-tight text-slate-800">
{title}
</h1>
<div className="pb-4 flex items-center space-between border-b">
<h2 className="text-sm leading-[1.7142857] text-slate-600 w-full">
Uploaded on {date}
</h2>
<LIke likes={likes} unlikes={unlikes} id={id} />
</div>
<div className="mt-4 text-sm text-[#334155] dark:text-slate-400">
{description}
</div>
</div>
);
};
export default VidoeDescription;
Like component where I put my like and unlike component
import React from 'react';
import { useDispatch } from 'react-redux';
import LikeImg from '../../assets/like.svg';
import UnLikeImg from '../../assets/unlike.svg'
// import { updateReaction } from '../../features/video/videoApi';
import { fetchReact } from '../../features/video/video_slice';
const LIke = ({ likes, unlikes, id }) => {
console.log(id)
const dispatch = useDispatch();
//handle like features
const reactionHandler = ({ id, reaction }) => {
dispatch(fetchReact({ id, reaction }))
}
return (
<div className="flex gap-10 w-48">
<div className="flex gap-1">
<div
className="shrink-0 cursor-pointer"
onClick={() => reactionHandler({ id, reaction: 'like' })}
>
<img className="w-5 block" src={LikeImg} alt="Like" />
</div>
<div className="text-sm leading-[1.7142857] text-slate-600">
{likes >= 1000 ? `${ likes }K` : likes}
</div>
</div>
<div className="flex gap-1">
<div
onClick={() => reactionHandler({ id, reaction: 'unlike' })}
className="shrink-0 cursor-pointer"
>
<img className="w-5 block" src={UnLikeImg} alt="Unlike" />
</div>
<div className="text-sm leading-[1.7142857] text-slate-600">
{unlikes >= 1000 ? `${ unlikes }K` : unlikes}
</div>
</div>
</div>
);
};
export default LIke;
This one is root video page
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import Player from '../component/Player/Player';
import VidoeDescription from '../component/VideoDescription/VidoeDescription';
import { fetchVideo } from '../features/video/video_slice';
import Loading from '../component/Loading/Loading';
import RelatedVideo from '../component/VideoList/RelatedVideo';
const Videos = () => {
const { loading, isError, video, error } = useSelector(state => state.video)
const { id, link, title , tags } = video || {};
const dispatch = useDispatch()
const { videoId } = useParams();
useEffect(() => {
dispatch(fetchVideo(videoId))
}, [dispatch, videoId])
//decide what to render in the ui
let content = null;
if (loading) {
content = <Loading />
}
if (loading && isError) {
content = <div className="col-span-12"><srong>{error}</srong></div>
}
if (!loading && !isError && video?.id) {
content = <div className="col-span-12"><srong>{error}</srong></div>
}
if (!loading && !isError && video?.id) {
content = <div className="mx-auto max-w-7xl px-2 pb-20 min-h-[400px]">
<div className="grid grid-cols-3 gap-2 lg:gap-8">
<div className="col-span-full w-full space-y-8 lg:col-span-2">
<Player link={link} title={title} />
<VidoeDescription video={video} />
</div>
<RelatedVideo currentId={id} tags={tags} />
</div>
</div>
}
return (
<div>
<section className="pt-6 pb-20">
{content}
</section>
</div>
);
};
export default Videos;
Problem
Here:
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload;
state.video.unlikes = action.payload;
})
action.payload looks like this:
{
likes: 1,
}
because it's how you created it in updateReaction
Solution
You wanted to do this instead:
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload.likes;
state.video.unlikes = action.payload.unlikes;
})

games.filter is not a function || React hooks component not receiving data

Up till this point, I have been able to fetch my data from MongoDB and view the data in my frontend console in react but games.filter is stopping my component from receiving the data and display them in the browser.
I have tried using Object.values(games).filter; the error goes away but it doesn't display the data still.
Here is a screenshot when i inspected the components
I can see the data in GamesByCategory component here
Here's is another image:
Datas in GamesBox is undefined
Here's my code
import React, {useState, useRef, useContext} from 'react'
import { useScrollPosition } from '#n8tb1t/use-scroll-position'
import GameBox from '../objects/GameBox'
import GamesContext from '../../contexts/GamesContext'
const GamesByCategories = () => {
const { games, setGames } = useContext(GamesContext)
const [currentCategory, setCurrentCategory] = useState(0)
const [categories, setCategories] = useState([
{id: 0, name: 'all', isActive: true},
{id: 1, name: 'gadgets', isActive: false},
{id: 2, name: 'sports', isActive: false},
{id: 3, name: 'family', isActive: false},
])
const categoriesTab = categories.map(category => {
var active = category.isActive ? 'active' : ''
return(
<div key={category.id} onClick={() => filterCategory(category.id)} className={"tab-element " + active } >
<span>{category.name}</span>
</div>
)
})
//filter and populate the games list by current category
// THIS IS WHERE I AM HAVING ISSUES
const gamesList = Object.values(games).filter(game => !game.isVersion )
.filter(game => (game.category === (currentCategory) || currentCategory === 0))
.map(game => {
return (
<GameBox id={game.id} image={game.image} name={game.name} price={game.price} isNew={game.gameIsNew} />
)
})
const filterCategory = (categoryId) => {
//update the current category
var updatedCategories = categories.map(category => {
if(category.id === categoryId){
category.isActive = true
}else{
category.isActive = false
}
return category
})
setCurrentCategory(categoryId)
setCategories(updatedCategories)
}
//add some styles to sticky categories tab
const [isCategoriesTabSticky, setIsCategoriesTabSticky] = useState(false)
const categoriesTabRef = useRef(null)
const stickyStyles = isCategoriesTabSticky ? "is-sticky" : ""
useScrollPosition( ({currPos}) =>{
if(-(currPos.y) >= categoriesTabRef.current.offsetTop){
setIsCategoriesTabSticky(true)
}else{
setIsCategoriesTabSticky(false)
}
})
return (
<div>
<div id="game-categories-tab" ref={categoriesTabRef} className={"flex justify-center mt-10 mb-8 custom-tabs " + stickyStyles}>
<div className="tab-container">
<div className="flex">
{categoriesTab}
</div>
</div>
</div>
<div className="grid justify-center categories-games">
<div className="flex flex-wrap justify-center categories-games-container">
{ gamesList }
</div>
</div>
</div>
)
}
export default GamesByCategories;
Kindly reach-out and help.
Thank you in advance

How to add a button and a dropdown in AgGrid Cell in react Functional Component

I have list of data. I am using AgGrid in react to display this data list. For each row i need to display a column having a delete button and a second column having a dropdown and a reset button.
When i click the delete button i need the corresponding row data and when i click reset button i need the dropdown option select as well as the corresponding row data.
I have searched but i am not able to figure out how to do it in react functional components. I have found that i need to use ICellRendererReactComp but i am not sure how as i am new to react and AgGrid
My current code looks something like this :
import React, { useState } from "react";
import toaster from "toasted-notes";
import { apiRequest, errorHandler } from "../../utilis/apiRequest";
import { columnDefsFromArr } from "../Threads/columnDefs";
import { AgGridReact, ICellRendererReactComp } from "ag-grid-react";
import { isResourcePresent } from "../../utilis/helper";
function Sessions(props) {
const [email, setEmail] = useState("");
const [reid, setReid] = useState(null);
const [sessionsResp, setSessionsResp] = useState(null);
const [columnDefs, setColumnDefs] = useState(null);
const [rowData, setRowData] = useState(null);
const defaultColDef = {
sortable: true,
filter: true,
resizable: true,
};
const colsToExlude = ["requestId"];
const resetSessionOptions = [
"None",
"ClearClientCache",
"PasswordChange",
"Suspended",
"InvalidSession",
"Expired",
];
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handleGetSession = () => {
setReid(email);
getSessions({ reid: email, env: props.env });
};
const getSessions = (data) => {
console.log(data, reid);
let isError = validateForm(data);
if (isError.status) {
apiRequest(
props.sessionToken,
"get_session",
data,
(res) => {
if (res.status === 200) {
console.log(res.data);
setRowData(res.data.sessions);
makeGrid(res.data);
}
},
(err) => {
errorHandler(err);
}
);
} else {
toaster.notify(isError.msg, {
duration: 1500,
});
}
};
const handleDelete = (data) => {
console.log(data);
};
const handleReset = (data) => {
console.log(data);
};
const makeGrid = (data) => {
let cols = [];
data.sessions.map((ele) => {
Object.keys(ele).map((key) => cols.push(key));
});
let localCols = [];
if (isResourcePresent(props.resources, "del_session")) {
localCols.push({
headerName: "Delete Sessio",
});
}
if (isResourcePresent(props.resources, "reset_session")) {
localCols.push({
headerName: "Reset Session"
});
}
cols = [...new Set(cols)];
colsToExlude.map((key) => {
let ind = cols.indexOf(key);
if (ind > -1) {
cols.splice(ind, 1);
}
});
let finalColDefs = [...localCols, ...columnDefsFromArr(cols)];
console.log(finalColDefs);
setColumnDefs(finalColDefs);
};
const validateForm = (data) => {
if (data.reid.trim() === "") {
return { status: false, msg: "Email/Email Id is reqd" };
} else {
return { status: true };
}
};
return (
<div className="container-fluid">
<div>
<h5>Get Sessions Information</h5>
</div>
<div className="card mt-2 p-3 bg-red shadow p-3 mb-5 bg-white rounded">
<div className="row m-2">
<div className="col-sm-6">
<input
type="text"
className="form-control"
value={email || ""}
placeholder="Email / Email ID"
onChange={handleEmailChange}
/>
</div>
<div className="col-sm-2">
<button className="button btn-primary" onClick={handleGetSession}>
Get Information
</button>
</div>
</div>
</div>
{rowData == null ? null : (
<div className="card mt-2 p-3 bg-red shadow p-3 mb-5 bg-white rounded">
<div
className="ag-theme-balham"
style={{ height: "500px", width: "100%" }}
>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}
></AgGridReact>
</div>
</div>
)}
</div>
);
}
export { Sessions };
handleDelete : this is the function i want to call when that delete button is clicked for some corresponding row,
resetSessionOptions : this is the dropdown list options i need to display in second column along with Reset button.
handleReset : this the function i want to call when that Reset button besides the dropdown is clicked for some corresponding row
So I searched a lot and finally came across this example : https://stackblitz.com/edit/angular-ag-grid-button-renderer?file=src%2Fapp%2Frenderer%2Fbutton-renderer.component.ts
Above example is for Angular and uses classes.
I figured out how to do it in react Functional Components. I did not used any interface or something but implemented all methods in above given example and made Renderer classes for the two columns i needed. You can see the code below.
UPDATE : My this solution works but this is causing my all other state variables to reset to initial state. I am not sure why thats happening. I am looking for a solution for that.
Session.js
import React, { useState } from "react";
import toaster from "toasted-notes";
import { apiRequest, errorHandler } from "../../utilis/apiRequest";
import { columnDefsFromArr } from "../Threads/columnDefs";
import { AgGridReact, ICellRendererReactComp } from "ag-grid-react";
import { isResourcePresent } from "../../utilis/helper";
import { ButtonRenderer } from "./ButtonRenderer";
import { DropDownRender } from "./DropDownRender";
function Sessions(props) {
const [email, setEmail] = useState("");
const [reid, setReid] = useState(null);
const [sessionsResp, setSessionsResp] = useState(null);
const [columnDefs, setColumnDefs] = useState(null);
const [rowData, setRowData] = useState(null);
const frameworkComponents = {
buttonRenderer: ButtonRenderer,
dropDownRenderer: DropDownRender,
};
const defaultColDef = {
sortable: true,
filter: true,
resizable: true,
};
const colsToExlude = ["requestId"];
const resetSessionOptions = [
"None",
"ClearClientCache",
"PasswordChange",
"Suspended",
"InvalidSession",
"Expired",
];
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handleGetSession = () => {
setReid(email);
getSessions({ reid: email, env: props.env });
};
const getSessions = (data) => {
console.log(data, reid);
let isError = validateForm(data);
if (isError.status) {
apiRequest(
props.sessionToken,
"get_session",
data,
(res) => {
if (res.status === 200) {
console.log(res.data);
setRowData(res.data.sessions);
makeGrid(res.data);
}
},
(err) => {
errorHandler(err);
}
);
} else {
toaster.notify(isError.msg, {
duration: 1500,
});
}
};
const handleDelete = (data) => {
console.log("DEL", data);
};
const handleReset = (data) => {
console.log("RESET", data);
};
const makeGrid = (data) => {
let cols = [];
data.sessions.map((ele) => {
Object.keys(ele).map((key) => cols.push(key));
});
let localCols = [];
if (isResourcePresent(props.resources, "del_session")) {
localCols.push({
headerName: "Delete Sessio",
cellRenderer: "buttonRenderer",
cellRendererParams: {
onClick: handleDelete,
label: "Delete",
},
});
}
if (isResourcePresent(props.resources, "reset_session")) {
localCols.push({
headerName: "Reset Session",
cellRenderer: "dropDownRenderer",
cellRendererParams: {
onClick: handleReset,
label: "RESET",
dropDown: resetSessionOptions,
},
});
}
cols = [...new Set(cols)];
colsToExlude.map((key) => {
let ind = cols.indexOf(key);
if (ind > -1) {
cols.splice(ind, 1);
}
});
let finalColDefs = [...localCols, ...columnDefsFromArr(cols)];
setColumnDefs(finalColDefs);
};
const validateForm = (data) => {
if (data.reid.trim() === "") {
return { status: false, msg: "Email/Email Id is reqd" };
} else {
return { status: true };
}
};
return (
<div className="container-fluid">
<div>
<h5>Get Sessions Information</h5>
</div>
<div className="card mt-2 p-3 bg-red shadow p-3 mb-5 bg-white rounded">
<div className="row m-2">
<div className="col-sm-6">
<input
type="text"
className="form-control"
value={email || ""}
placeholder="Email / Email ID"
onChange={handleEmailChange}
/>
</div>
<div className="col-sm-2">
<button className="button btn-primary" onClick={handleGetSession}>
Get Information
</button>
</div>
</div>
</div>
{rowData == null ? null : (
<div className="card mt-2 p-3 bg-red shadow p-3 mb-5 bg-white rounded">
<div
className="ag-theme-balham"
style={{ height: "500px", width: "100%" }}
>
<AgGridReact
defaultColDef={defaultColDef}
columnDefs={columnDefs}
rowData={rowData}
frameworkComponents={frameworkComponents}
></AgGridReact>
</div>
</div>
)}
</div>
);
}
export { Sessions };
ButtonRenderer.js
import React from "react";
function ButtonRenderer(params) {
const refresh = (param) => {
return true;
};
const onClick = ($event) => {
if (params.onClick instanceof Function) {
const retParams = {
event: $event,
rowData: params.node.data,
};
params.onClick(retParams);
}
};
return (
<button className="button btn-primary" onClick={onClick}>
{params.label}
</button>
);
}
export { ButtonRenderer };
DropDownRenderer.js
import React, { useState } from "react";
function DropDownRender(params) {
const [selection, setSelection] = useState(params.dropDown[0]);
const refresh = (param) => {
return true;
};
const handleDropDown = (e) => {
setSelection(e.target.value);
};
const onClick = ($event) => {
if (params.onClick instanceof Function) {
const retParams = {
event: $event,
rowData: params.node.data,
selection: selection,
};
params.onClick(retParams);
}
};
return (
<div className="row">
<div className="col">
<select className="form-control" onChange={handleDropDown}>
{params.dropDown.map((i) => {
return (
<option key={i} value={i}>
{i}
</option>
);
})}
</select>
</div>
<div className="col">
<button className="button btn-primary" onClick={onClick}>
{params.label}
</button>
</div>
</div>
);
}
export { DropDownRender };

Categories

Resources