React Query Hook Not Running On Initial Load - javascript

Here is my React component. The hook I am trying to get to work is useGetNotesQuery.
On initial load of the component, it does not run. If I tab switch, or create a new note, then the hook will be called and the component will update. I don't have this problem with other hooks and I can't seem to figure out what I am doing wrong. I'm still pretty new to react-query. I've verified that when the hook is called on tab switch or the creation of a new note, the correct data is being passed in and the hook works as expected. It just will not run on initial load.
import React, { useState } from 'react';
import useGetNotesQuery from 'hooks/notes/useNotes';
import { useCreateNote } from 'hooks/notes/useCreateNote';
import Note from './Note';
export default function Notes({ projectItemId, itemId, itemType }: any) {
const createNote = useCreateNote();
const {
data: notes,
isLoading,
isError
} = useGetNotesQuery({ projectItemId, itemId, itemType });
const [body, setBody] = useState('');
const createNewNote = async () => {
await createNote.mutateAsync({
body,
projectItemId,
itemId,
itemType
});
setBody('');
};
return (
<section aria-labelledby="notes-title">
{!isLoading && (
<div className="sm:overflow-hidden sm:rounded-lg">
{console.log(notes)}
<div className="divide-y divide-gray-200">
<div className="px-4 py-5 sm:px-6">
<h2
id="notes-title"
className="text-lg font-medium text-gray-900"
>
Notes
</h2>
</div>
<div className="px-4 py-6 sm:px-6">
<ul role="list" className="space-y-8">
{notes?.map((note) => (
<Note key={note.id} note={note} />
))}
</ul>
</div>
</div>
<div className="bg-gray-50 px-4 py-6 sm:px-6">
<div className="flex space-x-3">
{/* <div className="flex-shrink-0">
<img
className="h-10 w-10 rounded-full"
src={user.imageUrl}
alt=""
/>
</div> */}
<div className="min-w-0 flex-1">
<form action="#">
<div>
<label htmlFor="comment" className="sr-only">
About
</label>
<textarea
id="comment"
name="comment"
rows={3}
className="block w-full p-2 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm text-black"
placeholder="Add a note"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
</div>
<div className="mt-3">
<button
type="submit"
onClick={(e) => {
e.preventDefault();
createNewNote();
}}
className="inline-flex items-center justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Comment
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</section>
);
}
Here is the hook
import { getNotesById } from './../../queries/notes/get-notes';
import { useQuery } from 'react-query';
function useGetNotesQuery({ projectItemId, itemId, itemType }: any) {
return useQuery('notes', async () => {
if (projectItemId) {
return getNotesById({ projectItemId, itemId, itemType }).then(
(result) => result.data
);
}
});
}
export default useGetNotesQuery;
My _app.tsx file in NextJS
import 'styles/main.css';
import 'styles/chrome-bug.css';
import '#/styles/tailwind.css';
import 'katex/dist/katex.css';
import '../styles/globals.css';
import 'react-datepicker/dist/react-datepicker.css';
import { useEffect, useState } from 'react';
import React from 'react';
import { SessionContextProvider } from '#supabase/auth-helpers-react';
import { createBrowserSupabaseClient } from '#supabase/auth-helpers-nextjs';
import { AppProps } from 'next/app';
import { MyUserContextProvider } from 'utils/useUser';
import type { Database } from 'types_db';
import { SidebarProvider } from 'context/SidebarContext';
import { QueryClient, QueryClientProvider } from 'react-query';
// import { ReactQueryDevtools } from 'react-query/devtools';
import { ThemeProvider } from 'next-themes';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 3
}
}
});
export default function MyApp({ Component, pageProps }: AppProps) {
const [initialContext, setInitialContext] = useState();
const [supabaseClient] = useState(() =>
createBrowserSupabaseClient<Database>()
);
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
return (
<QueryClientProvider client={queryClient}>
<SessionContextProvider supabaseClient={supabaseClient}>
<MyUserContextProvider initial={initialContext}>
<SidebarProvider>
<ThemeProvider
attribute="class"
enableColorScheme={false}
defaultTheme="light"
>
<Component {...pageProps} />
</ThemeProvider>
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
</SidebarProvider>
</MyUserContextProvider>
</SessionContextProvider>
</QueryClientProvider>
);
}

What do your other hooke return? Your query getNotesById is returning result.data, but the useQuery structure expects a parsed result object, like the example below:
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
return res.json();
};
const response = useQuery("users", fetchUsers);

Related

Unhandled Runtime Error TypeError: messages is undefined

In this file, I got this error. `Unhandled Runtime Error
TypeError: messages is undefined` (I'm building and NEXT app)
Please help me to solve this error. messages will be loaded with postman, Problem with this page. The query I have been checked with postman and data fetched succesfully
import React, { useEffect, useState } from 'react';
import { HiPlusSm } from 'react-icons/hi';
import router from 'next/router';
import Link from 'next/link';
import axios from 'axios';
const MessageList = () => {
const [messages, setMessages] = useState([]);
//TODO check how to get the current user id - localstorage.getitem()
useEffect(() => {
const fetchMessages = async () => {
await axios
.get('/api/messages/getMessages', {
params: { sender: '62de47ab819e077df87d0661' },
})
.then((result) => {
setTimeout(() => setMessages(result.data.messages), 1000);
})
.then((result) => {
if (!result.data.messages) {
return null;
}
})
.catch((err) => {
console.log(err);
});
};
fetchMessages();
}, []);
return (
<div className={'bg-gray-100'}>
<div className="mx-auto py-4 px-4 pt-8 md:max-w-4xl">
<div className={'w-full bg-white p-5 shadow-sm'}>
<div className="mt-4 px-1">
<Link href="/inbox/new">
<button
className={`inline-flex items-center rounded-md border border-transparent bg-red-100 px-4 py-1.5 text-xs font-medium text-susty shadow-sm hover:border-susty hover:bg-susty hover:text-white`}
>
<HiPlusSm className="mr-2 h-5 w-5 items-center" />
New Message
</button>
</Link>
</div>
<div className="w-full overflow-auto bg-white shadow md:h-[410px] xl:h-[630px]">
<div className="h-full w-full">
<div className={'mt-10 flex flex-col-reverse'}>
{messages.map((msg) => (
<div key={msg} id={msg}>
<span>{msg.message}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default MessageList;

Changing background based on weather description using next.js

I am trying to have it so that the background of my weather app will change based on the weather condition using nextjs and the current weather data API. But no image is display. This is my Main
import axios from "axios";
import { useState } from "react";
import Weather from "./Weather";
import Spinner from "./Spinner";
import { data } from "autoprefixer";
import Background from "./Background";
const Main = () => {
const [city, setCity] = useState("");
const [weather, setWeather] = useState({});
const [loading, setLoading] = useState(false);
//In celsius -(&units=metric) in fahrenheit -(&units=imperial)
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${process.env.NEXT_PUBLIC_WEATHER_KEY}`;
const fetchWeather = (e) => {
e.preventDefault();
setLoading(true);
axios.get(url).then((response) => {
setWeather(response.data);
// console.log(response.data);
});
setCity("");
setLoading(false);
};
if (loading) {
return <Spinner />;
} else {
return (
<div>
{/* Overlay, so we use a self-closing div */}
<div className="absolute top-0 left-0 right-0 bottom-0 bg-black/40 z-[1]" />
<Background
weatherDescription={data.weather ? data.weather[0].main : null}
/>
<div className="relative flex justify-between items-center max-w-[500px] w-full m-auto pt-4 text-white z-10">
<form
onSubmit={fetchWeather}
className="flex justify-between items-center w-full m-auto p-3 bg-transparent border border-gray-300 text-white rounded-2xl"
>
<div>
<input
onChange={(e) => setCity(e.target.value)}
className="bg-transparent border-none text-white focus:outline-none text-2xl"
type="text"
placeholder="Search"
/>
</div>
<button onClick={fetchWeather}>
<BsSearch size={20} />
</button>
</form>
</div>
{weather.main && <Weather data={weather} />}
</div>
);
}
};
And this is the background component
import React from "react";
import img1 from "../public/assets/Rainy weather.jpg";
import img2 from "../imgs/Cloudy Weather.jpg";
function Background(props) {
const images = [
{
name: "Rain",
background: img1,
},
{
name: "Clouds",
background: img2,
},
];
const imgURL = images.find((el) => el.name === props.weatherDescription)
?.background;
return (
<div className="-z-10">
<img
src={imgURL}
className="object-cover w-full min-h-scren"
key={imgURL}
/>
</div>
);
}
export default Background;
I've tried if statements and functions, but nothing is working, I've found this current template that was working with videos, I've tried to change it to use images, but I can't achieve it. I'm fairly new in coding, so I hope someone can help me.

Hydration error when fetching data from array of objects

I am using Nextjs and there is a sidebar in which I am trying to get random 5 posts from an array of objects. The defined function is working fine and displaying 5 posts but I am getting a Hydration error showing Prop alt did not match. When I tried to display it on the console the alt value is different.
import Link from 'next/link';
import Image from 'next/image';
import { BlogData } from '../data/blogdata';
function getMultipleRandom() {
const shuffled = [...BlogData].sort(() => 0.5 - Math.random());
return shuffled.slice(0, 5);
}
const Sidebar = () => {
return (
<>
<h2 className='font-roboto text-3xl font-semibold pb-10'>Featured Posts</h2>
{
getMultipleRandom().map((val) => {
return (
<div key={val._id} className='flex flex-col pt-5'>
<div className='w-56 pr-5'><Image src={val.featuredImage} alt={val.alt} width={1200} height={800} className=' rounded-3xl' /></div>
<Link href={`/blog/${val.slug}`}><a><h3 className='text-sm font-poppins font-medium hover:text-[#5836ed] transition-all duration-300'>{val.title}</h3></a></Link>
</div>
);
})
}
</>
)
}
export default Sidebar;
try this code :
import Link from "next/link";
import Image from "next/image";
import { BlogData } from "../data/blogdata";
import { useEffect, useState } from "react";
const Sidebar = () => {
const [shuffled, setShuffled] = useState([]);
const [loading, setLoading] = useState(true); // anyting you want !!!
useEffect(() => {
if (loading) {
const x = [...BlogData].sort(() => 0.5 - Math.random());
setShuffled(x.slice(0, 5));
setLoading(false);
}
}, [loading]);
return (
<>
<h2 className="font-roboto text-3xl font-semibold pb-10">
Featured Posts
</h2>
{loading ? <div>loading ... </div> : shuffled.map((val) => {
return (
<div key={val._id} className="flex flex-col pt-5">
<div className="w-56 pr-5">
<Image
src={val.featuredImage}
alt={val.alt}
width={1200}
height={800}
className=" rounded-3xl"
/>
</div>
<Link href={`/blog/${val.slug}`}>
<a>
<h3 className="text-sm font-poppins font-medium hover:text-[#5836ed] transition-all duration-300">
{val.title}
</h3>
</a>
</Link>
</div>
);
})}
</>
);
};
export default Sidebar;

Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider> while using useselector

1.header.js
import Image from "next/image";
import {
MenuIcon,
SearchIcon,
ShoppingCartIcon,
} from "#heroicons/react/outline";
import { signIn, signOut, useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useSelector } from "react-redux";
import { selectItems } from "../slices/basketSlice";
import { Provider } from "react-redux";
function Header() {
const { data: session } = useSession();
const router = useRouter();
const items= useSelector(selectItems);
return (
<header>
<div className="flex items-center bg-amazon_blue p-1 flex-grow py-2">
<div className="mt-2 flex items-center flex-grow sm:flex-grow-0">
<Image
onClick={() => router.push("/")}
src='https://links.papareact.com/f90'
width={150}
height={40}
objectFit="contain"
className="cursor-pointer"
/>
</div>
{/*search bar*/}
<div className="hidden sm:flex items-center h-10 rounded-md flex-grow cursor-pointer bg-yellow-400 hover:bg-yellow-500">
<input className="p-2 h-full w-6 flex-grow flex-shrink rounded-l-md focus:outline-none px-4" type="text" />
<SearchIcon className="h-12 p-4"/>
</div>
<div className="text-white flex items-center text-xs space-x-6 mx-6 whitespace-nowrap">
<div onClick={session ? signIn:signOut} className="link">
<p className="hover:underline">{session ? session.user.name:"signin"}</p>
<p className="font-extrabold md:text-sm">Acount & lists</p>
</div>
<div className=" link">
<p>Returns</p>
<p className="font-extrabold md:text-sm">& orders</p>
</div>
<div onClick={() => router.push("/checkout")} className=" link relative flex items-center">
<span className="absolute top-0 right-0 md:right-10 h-4 w-4 bg-yellow-400 rounded-full text-center text-black font-bold">
{items.length}
</span>
<ShoppingCartIcon className="h-10 "/>
<p className="hidden md:inline font-extrabold md:text-sm mt-2">Bascket</p>
</div>
</div>
</div>
{/*bottom nav*/}
<div className="flex items-center space-x-3 p-2 pl-6 bg-amazon_blue-light text-white text-sm">
<p className="link flex items-center ">
<MenuIcon className="h-6 mr-5"/>
all</p>
<p className="link">featured </p>
<p className="link">new arrival </p>
<p className="link">catalog </p>
<p className="link hidden lg-inline-flex">electronics </p>
</div>
</header>
);
}
export default Header;
bascketslice.js
'''
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
items: [],
};
export const basketSlice = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, action) => {
state.items = [...state.items, action.payload]
},
removeFromBasket: (state, action) => {},
},
});
export const { addToBasket, removeFromBasket } = basketSlice.actions;
// Selectors - This is how we pull information from the Global store slice
export const selectItems = (state) => state.basket.items;
export default basketSlice.reducer;
'''
product.js
'''
import Image from "next/image";
import {useState} from "react";
import{ StarIcon} from"#heroicons/react/solid";
import Currency from "react-currency-format";
import { useDispatch } from "react-redux";
import {addToBasket} from "../slices/basketSlice";
const MAX_RATING =5;
const MIN_RATING =1;
function Product({id, title, price, description, category,image}) {
const dispatch = useDispatch();
const{rating}= useState(
Math.floor(Math.random() * (MAX_RATING - MIN_RATING +1)) + MIN_RATING
);
const addItemToBascket = () => {
const product = {id, title, price, description, category,image};
// sending the product as an action to bascket slice
dispatch(addToBasket(product))
};
return (
{category}
<Image src={image} height={200} width={200} objectFit="contain" />
<h4 className="flex">{title}</h4>
<div className="flex">
{Array(rating)
.fill()
.map((_, i)=>(
<StarIcon className="h-5 text-yellow-500"/>
))}
</div>
<p className="text-xs my-2 line-clamp-2">{description}</p>
<div className="mb-5">
<Currency quantity={price}/>
</div>
<button onClick={addItemToBascket} className=" mt-auto button">Add to Basket</button>
</div>
);
}
export default Product
'''
store.js
import { configureStore } from "#reduxjs/toolkit";
import basketReducer from "../slices/basketSlice";
export const store = configureStore({
reducer: {
basket: basketReducer,
},
});
_app.js
import { Provider } from 'react-redux'
import { store } from '../app/store'
import '../styles/globals.css'
import { SessionProvider } from "next-auth/react"
const MyApp = ({ Component, pageProps: {session,...pageProps} }) => {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
};
export default MyApp
how do i use provider in the code
my repository :- https://github.com/Shadow2389/nmz-2.git
It seems we're both fans of Sonny. Your problem there is that you need to wrap up your App.js in
<Provider>
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
</Provider>
But then you'll most likely have a "Hydration error", if so, just remove the "Math.floor(Math.random() * (MAX_RATING - MIN_RATING +1)) + MIN_RATING"
Apparently with the new updates we can no longer use the useState() hook as we used to, so you have 3 options:
1.- Remove the "Math.floor(Math.random() * (MAX_RATING - MIN_RATING +1)) + MIN_RATING"
2.- Set a fixed rating for all products
3.- Set a number for your API (which you cannot, since you are using FakeStoreAPI)
This is due to basic rules of Hooks, since a Random number is "umpredictable", but now I don't know why it has that problem (although you're setting a min and a max)

Test click event on parent component to show children component

I write a unit test to test a function in parent component. When func click, childrend component render. But I can't make it pass. Here is my code
const useModal = () => {
const [isOpen, setIsShowing] = useState<boolean>(false);
const toggle = (isOpen: boolean) => {
setIsShowing(isOpen);
};
return [
isOpen,
toggle,
]
};
const Parent: React.FC = () => {
const [isOpen, toggle] = useModal();
return (
<React.Fragment>
<div
onClick={() =>
toggle(true)}
className="flex flex-col items-center justify-center h-full cursor-pointer">
<h3 className="mb-1 text-green-500 font-bold">
Select coupon
</h3>
<i className="fas fa-ticket-alt text-4xl text-gray-400"/>
</div>
....
<Modal title={'Coupon'}
isOpen={isOpen} toggle={toggle}>
<div className="p-6 pb-8">
<h3 className="font-medium text-gray-600 mb-4">
Pre-valid and valid coupons are listed
</h3>
...
</Modal>
</React.Fragment>
)
};
const Modal: React.FC<Props> = ({isOpen, toggle, title , children}) => {
....
return (
isOpen ? createPortal(
<React.Fragment>
<div className="flex absolute w-full top-0 bottom-0 justify-center items-center">
<div
className="modal-overlay absolute w-full h-full bg-black opacity-25 top-0 bottom-0"/>
<div ref={ref} style={{top: 100}} className="ease-in-out absolute z-50 bg-white">
<div className="h-12 flex flex-row justify-between border-b border-gray-300">
<span className="self-center ml-2 font-bold text-gray-600">{title}</span>
<button
onClick={() => toggle(false)}
className="focus:outline-none hover:bg-gray-300 h-10 w-10 tooltip">
<i className="fas fa-times text-gray-500"/>
<span className="tooltiptext text-xs font-thin">Close</span>
</button>
</div>
{children}
</div>
</div>
</React.Fragment>, el
) : null
)
};
Test func
import React from "react";
import {render, unmountComponentAtNode} from "react-dom";
import {act} from "react-dom/test-utils";
import {Provider} from 'react-redux'
import Enzyme, {mount, shallow} from 'enzyme'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk';
import Adapter from 'enzyme-adapter-react-16'
import ContentCoupon from "./index";
import {Modal} from "../components";
Enzyme.configure({adapter: new Adapter()});
const mockStore = configureMockStore([thunk]);
let container: Element | null = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
if (container) {
unmountComponentAtNode(container);
container.remove();
}
container = null;
});
const store = mockStore();
const wrapper = mount(
<Provider store={store}>
<ContentCoupon/>
</Provider>
);
describe('test ContentCoupon component', () => {
it('render correctly', () => { //pass
expect(wrapper.find('h3').text()).toEqual("Select coupon");
expect(wrapper.find('.font-sm')).toHaveLength(0);
});
it('not render modal', () => { //pass
expect(wrapper.find('h3.font-medium text-gray-600 mb-4')).toHaveLength(0);
});
it('click and show modal', () => { //not pass
wrapper.find('.cursor-pointer').simulate('click');
expect(wrapper.find('h3.font-medium text-gray-600 mb-4')).toHaveLength(1);
});
});
You should change the css selector used here:
expect(wrapper.find('h3.font-medium text-gray-600 mb-4')).toHaveLength(1);
to :
expect(wrapper.find('h3.font-medium.text-gray-600.mb-4')).toHaveLength(1);
//---------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Categories

Resources