I am building a small chat app, but I have problems with receiving messages in react. The problem is that when I receive a message I setMessages, but at the same time the messages state gets cleared. The problem is problably that I don't know where to place socket.on, currently it's inside a useEffect hook.
export const Room = ({ socket }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const { id } = useParams();
useEffect(() => {
socket.emit("join room", id);
}, []);
useEffect(() => {
socket.on("message", (message) => {
setMessages([
...messages,
{ message: message, createdBy: "other", id: uuidv4() },
]);
});
}, []);
const sendMessage = () => {
console.log("send");
socket.emit("message", input);
setMessages([
...messages,
{ message: input, createdBy: "me", id: uuidv4() },
]);
};
console.log("foo");
return (
<div className="flex justify-center text-center">
<div className="w-screen h-screen px-2 max-w-4xl mt-10">
<p className="text-3xl">Code: {id}</p>
<div className="bg-white w-full h-3/4 rounded-xl border-2 border-black overflow-y-auto">
{messages.map((message) => (
<Message
text={message.message}
createdBy={message.createdBy}
key={message.id}
/>
))}
</div>
<div className="flex flex-row border-2 border-black mt-2 rounded-xl p-2 bg-white">
<input
className="flex-grow border-none focus:outline-none"
onInput={(e) => setInput(e.target.value)}
/>
<button
className=" bg-green-500 rounded-xl px-2 py-1"
onClick={sendMessage}
>
Send
</button>
</div>
</div>
</div>
);
};
I'm pretty sure you have a closure problem. messages is not what you think it is when it is run inside the useEffect -- it may not be the latest version.
It's safer and good practice to use the functional variant of the setter. This way you can be certain that messages is the current version:
setMessages(messages => [
...messages,
{ message: message, createdBy: "other", id: uuidv4() },
]);
Related
This is my Apı request file. I designed like that.
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
// DEV ONLY!!!
const pause = (duration) => {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
};
const usersApi = createApi({
reducerPath: "users",
baseQuery: fetchBaseQuery({
baseUrl: "http://127.0.0.1:4000/",
fetchFn: async (...args) => {
// REMOVE FOR PRODUCTION
await pause(3000);
return fetch(...args);
},
}),
endpoints(builder) {
return {
addUser: builder.mutation({
query: (token, user) => {
return {
url: "/users",
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: {
role: user.role,
firmId: user.firmId,
firmName: user.firmName,
name: user.name,
lastName: user.lastName,
email: user.email,
tel: user.tel,
isActive: user.isActive,
},
invalidates: ["fetchUser"],
provides: ["fetchUser"],
};
},
}),
getUser: builder.mutation({
query: (token, id) => {
return {
url: `/users/${id}`,
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
};
},
}),
updateUser: builder.query({
query: (token, user) => {
return {
url: `/users/${user.id}`,
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
},
body: {
role: user.role,
firmId: user.firmId,
firmName: user.firmName,
name: user.name,
lastName: user.lastName,
email: user.email,
tel: user.tel,
isActive: user.isActive,
},
invalidates: ["fetchUser"],
provides: ["fetchUser"],
};
},
}),
fetchUser: builder.query({
query: (token) => {
return {
url: "/users",
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
};
},
}),
};
},
});
export const {
useFetchUserQuery,
useAddUserMutation,
useGetUserQuery,
useUpdateUserMutation,
} = usersApi;
export { usersApi };
I import my api file in here. Export api functions as below of my code.
import { configureStore } from "#reduxjs/toolkit";
import { setupListeners } from "#reduxjs/toolkit/query";
import { devicesApi } from "./devicesApi";
import { deviceTypeApi } from "./deviceTypeApi";
import { deviceStatusApi } from "./deviceStatusApi";
import { usersApi } from "./usersApi";
import { cityApi } from "./cityApi";
import { townApi } from "./townApi";
import {
deviceFormReducer,
changeDeviceTypeId,
changeDeviceTypeName,
changeFirmId,
changeIp,
changeImei,
changeSerialNo,
changeUserPassword,
changeAdminPassword,
changeConnectionLevel,
changeQuota,
changeCounter,
changeDeviceStatusId,
changeDeviceStatusName,
changeIsActive,
changeNote,
} from "./slices/deviceForm";
export const store = configureStore({
reducer: {
form: deviceFormReducer,
[townApi.reducerPath]: townApi.reducer,
[cityApi.reducerPath]: cityApi.reducer,
[devicesApi.reducerPath]: devicesApi.reducer,
[deviceTypeApi.reducerPath]: deviceTypeApi.reducer,
[deviceStatusApi.reducerPath]: deviceStatusApi.reducer,
[usersApi.reducerPath]: usersApi.reducer,
},
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware()
.concat(usersApi.middleware)
.concat(devicesApi.middleware)
.concat(deviceTypeApi.middleware)
.concat(townApi.middleware)
.concat(cityApi.middleware)
.concat(deviceStatusApi.middleware);
},
});
setupListeners(store.dispatch);
export {
changeDeviceTypeId,
changeDeviceTypeName,
changeFirmId,
changeIp,
changeImei,
changeSerialNo,
changeUserPassword,
changeAdminPassword,
changeConnectionLevel,
changeQuota,
changeCounter,
changeDeviceStatusId,
changeDeviceStatusName,
changeIsActive,
changeNote,
};
export {
useFetchDevicesQuery,
useAddDeviceMutation,
useGetDeviceByIdQuery,
useGetDeviceByIpQuery,
useRemoveDeviceMutation,
} from "./devicesApi";
export {
useFetchUserQuery,
useAddUserMutation,
useGetUserQuery,
useUpdateUserMutation,
} from "./usersApi";
export { useFetchCityQuery } from "./cityApi";
export { useFetchTownQuery } from "./townApi";
export { useFetchDeviceStatusQuery } from "./deviceStatusApi";
export { useFetchDeviceTypeQuery } from "./deviceTypeApi";
This is my code. In here I want to update my user but I am gettin error about the usage "const [updateUser, { isLoading }] = useUpdateUserMutation();".
I am getting this error I tried something , I am newly person in react. Please help about what is my problem ?
import { useUpdateUserMutation } from "../../store";
import { useState, useEffect } from "react";
import Dropdown from "../../components/DropDown";
import { MdUpdate } from "react-icons/md";
import { AiOutlineSave } from "react-icons/ai";
import { TbReportAnalytics, TbReportMedical } from "react-icons/tb";
import { ImCross } from "react-icons/im";
import { GiConfirmed, GiCancel } from "react-icons/gi";
import useAuth from "../../hooks/useAuth";
function EditUserModals({ onClose, onClick, user }) {
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [Info, setInfo] = useState(0);
const [input, setInput] = useState(user);
const { auth } = useAuth();
const token = auth.accessToken;
const [updateUser, { isLoading }] = useUpdateUserMutation();
const HandleOpenModal = (e) => {
e.preventDefault();
if (input.role !== "" && input.firmId !== "") {
setShowConfirmModal(true);
}
};
useEffect(() => {
if (Info === 0) setInput(user);
}, [Info]);
const handleCloseModel = (bool) => {
if (bool) {
onClick();
} else {
setShowConfirmModal(false);
}
};
const userRoles = [
{ label: "Admin", value: "admin" },
{ label: "User", value: "user" },
];
const firmIds = [{ label: "Bayıner", value: "Bayıner" }];
const handleSelectFirmName = (option) => {
//Firma ID atılacak
setInput({
...input,
firmName: option.value,
firmId: 1,
});
};
const handleSelectUserRoles = (option) => {
setInput({ ...input, role: option.value });
};
return (
<div className="fixed w-screen h-screen top-0 left-0 z-30 bg-white">
{/*
Confirm Modal
*/}
<div
className={`z-50 flex items-center justify-center absolute ${
showConfirmModal ? "flex" : "hidden"
} w-full h-full bg-opacity-50 transition-all duration-100`}
>
<div
onClick={() => handleCloseModel(false)}
className="fixed inset-0 bg-gray-300 opacity-80 "
></div>
<div className=" flex z-10 flex-col gap-3 bg-slate-600 mx-auto w-fit p-1 rounded-xl ">
<div className="bg-white rounded-xl p-4 flex flex-col items-center">
<p className="mb-4 flex flex-col gap-2">
<p>İsim: {input.name}</p>
<p>Soysim: {input.lastName}</p> <p>Email: {input.email}</p>
<p>Firma: {input.firmName}</p>
<p>Telefon: {input.tel}</p>
bilgilerine sahip kullanıcıyı oluşturmak istiyor musunuz?
</p>
<div className="flex gap-2">
<button
onClick={() => handleCloseModel(true)}
className="flex items-center bg-slate-800 text-white p-2 rounded-xl border-4
hover:bg-white hover:border-slate-800 hover:text-slate-800"
>
<GiConfirmed />
Onay
</button>
<button
onClick={() => handleCloseModel(false)}
className="flex items-center bg-white border-4 rounded-xl p-2 hover:bg-slate-600 hover:text-white"
>
<GiCancel />
İptal
</button>
</div>
</div>
</div>
</div>
{/*
Top Absolute Information
*/}
<div className="absolute top-0 bg-primary z-10 w-full flex justify-between h-fit">
<div className="flex">
<button
onClick={() => setInfo(0)}
className={`${
Info === 0 ? "bg-white border-primary " : "bg-fourth text-white"
} flex px-6 py-3 z-20 transition-all duration-500
hover:bg-white hover:text-fourth gap-4`}
>
<div className="flex justify-center items-center pt-2">
<MdUpdate className="mr-2 w-6 h-6 " />
<p className=" ">Kullanıcı Düzenleme</p>
</div>
</button>
<button
onClick={() => {
//getDeviceByIp(device.ip);
setInfo(1);
}}
className={`${
Info === 1 ? "bg-white border-primary " : "bg-fourth text-white"
} flex px-6 py-3 z-20 transition-all duration-500
hover:bg-white hover:text-fourth gap-4`}
>
<div className="flex justify-center items-center pt-2">
<TbReportAnalytics className="mx-4 w-6 h-6 " />
<p className=" ">Rapor Ekle</p>
</div>
</button>
</div>
<button
onClick={onClose}
className={`flex px-8 py-2 transition-all duration-200 text-fourth
h-14 z-10 justify-center items-center
hover:text-fifth w-fit `}
>
<ImCross className="w-6 h-6 " />
</button>
</div>
<form
onSubmit={HandleOpenModal}
className={` grid-flow-row w-full h-full z-0 text-sm px-32 ${
Info === 0 ? "grid" : "hidden"
} `}
>
</form>
<form
onSubmit={HandleOpenModal}
className={` grid-flow-row w-full h-full z-0 text-sm px-32 ${
Info === 1 ? "grid" : "hidden"
} `}
>
<div className="grid sm:grid-cols-2 grid-cols-1 gap-6 overflow-scroll mt-24 mb-16">
<button className="h-fit items-center text-fourth flex bg-secondary rounded-br-2xl rounded-tl-2xl px-6 py-3 active:bg-primary hover:bg-third transition-all duration-500">
<TbReportMedical />
<p>Rapor Ekle</p>
</button>
<Dropdown
options={firmIds}
value={{
label: input.firmName,
value: input.firmName,
}}
onChange={handleSelectFirmName}
search={true}
barValue={"-Raporlar-"}
/>
</div>
</form>
<div className="fixed bottom-0 ">
<div
style={{ height: "70px" }}
className="w-screen bg-primary flex justify-center items-center"
></div>
</div>
</div>
);
}
export default EditUserModals;
This error that I am gettin in browser
The above error occurred in the <EditUserModals> component:
at EditUserModals (http://localhost:3000/static/js/bundle.js:3467:5)
at div
at UsersPage (http://localhost:3000/static/js/bundle.js:9828:65)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:578653:5)
at Outlet (http://localhost:3000/static/js/bundle.js:579034:26)
at RequireAuth (http://localhost:3000/static/js/bundle.js:5039:5)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:578653:5)
at Outlet (http://localhost:3000/static/js/bundle.js:579034:26)
at div
at div
at div
at SiteLayout (http://localhost:3000/static/js/bundle.js:5750:74)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:578653:5)
at Routes (http://localhost:3000/static/js/bundle.js:579119:5)
at App
at RenderedRoute (http://localhost:3000/static/js/bundle.js:578653:5)
at Routes (http://localhost:3000/static/js/bundle.js:579119:5)
at AuthProvider (http://localhost:3000/static/js/bundle.js:6940:5)
at NavigationProvider (http://localhost:3000/static/js/bundle.js:7026:5)
at UsersProvider (http://localhost:3000/static/js/bundle.js:7125:5)
at Provider (http://localhost:3000/static/js/bundle.js:573644:5)
at Router (http://localhost:3000/static/js/bundle.js:579057:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:577259:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
overrideMethod # react_devtools_backend.js:4012
logCapturedError # react-dom.development.js:18687
update.callback # react-dom.development.js:18720
callCallback # react-dom.development.js:13923
commitUpdateQueue # react-dom.development.js:13944
commitLayoutEffectOnFiber # react-dom.development.js:23391
commitLayoutMountEffects_complete # react-dom.development.js:24688
commitLayoutEffects_begin # react-dom.development.js:24674
commitLayoutEffects # react-dom.development.js:24612
commitRootImpl # react-dom.development.js:26823
commitRoot # react-dom.development.js:26682
performSyncWorkOnRoot # react-dom.development.js:26117
flushSyncCallbacks # react-dom.development.js:12042
(anonymous) # react-dom.development.js:25651
``
function Product(props) {
const [Shop, setShop] = useState(false);
const Shopped = () => setShop(!Shop);
return(
<section onClick={() => { Shopped();}} >
{!Shop ? (
<FaShoppingCart
title="افزودن به سبد خرید"
className="absolute right-0 m-4 cursor-pointer hover:text-green-700"
/>) :
(<FaShoppingCart className="absolute right-0 m-4 cursor-pointer text-green-700" />
)}
</section>
)}
export default Product
I want to pass addToCart() function to shop when its inactive. and pass removeFromCart() function when its active in react.
You can create this prop with a condition:
<FaShoppingCart
...
onClick={Shop ? removeFromCart : addToCart}
/>
You can provide one function and change the functionality based on your Shop state.
function Product(props) {
const [Shop, setShop] = useState(false);
const Shopped = () => setShop(!Shop);
const handleIconClick = () => {
if (Shop === true) {
removeFromCart();
} else {
addToCart();
}
};
return (
<section
onClick={() => {
Shopped();
}}
>
{!Shop ? (
<FaShoppingCart
title="افزودن به سبد خرید"
className="absolute right-0 m-4 cursor-pointer hover:text-green-700"
onClick={handleIconClick}
/>
) : (
<FaShoppingCart
className="absolute right-0 m-4 cursor-pointer text-green-700"
onClick={handleIconClick}
/>
)}
</section>
);
}
I am trying to submit the form using the react-hook form. But it always returns empty data.
The Request payload returns something like this in the browser
{nameOnCurrentLicense: "test Biden",
driversLicenseNumber: "dfghj3243546",
deliveryAddress: "vbn 456 fghjgfs, UK",
driversLicenseNumber: "dfghj3243546",
driversLicensePhoto: {0: {}},
driversLicensePricingId: 1,
nameOnCurrentLicense: "test Biden",
passportPhoto: {0: {}}
}
import { data } from 'autoprefixer';
import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Link } from 'react-router-dom';
import { toast } from 'react-toastify';
import {
Button,
Container,
FileInput,
Input,
Line,
Radio,
TextInput,
Typography,
} from '../../../../common/components';
import { axiosInstance } from '../../../../config/axiosInstance';
import { createFormData } from '../../../../helpers';
import { useAppDispatch, useAppSelector } from '../../../../hooks';
import {
useDriverLicenseMutation,
useDriverLicensePayCardMutation,
useDriverLicensePayWalletMutation,
} from '../../apis';
import { formTwo } from '../../apis/slice/FormSlice';
import { formProps } from '../../types';
import { DriverLicenseSubmitPayload } from './types';
function Drift() {
const [price, setPrice] = useState([]);
const [passportFile, setPassportFile] = useState(null);
const [licenseFile, setLicenseFile] = useState(null);
const [driversLicensePricingId, setDriversLicensePricingId] = useState('');
// api hooks
const [onDriverLicense, { isLoading }] = useDriverLicenseMutation();
const [onInitPaymentViaWallet, { isLoading: isInitiatingWallet }] =
useDriverLicensePayWalletMutation();
const [onInitPaymentViaCard, { isLoading: isInitiatingCard }] = useDriverLicensePayCardMutation();
//RHF context
const priceSelection = useAppSelector((state) => state.formAction.FormTwo);
const { register, handleSubmit, errors } = useForm({
defaultValues: {
nameOnCurrentLicense: data.nameOnCurrentLicense,
driversLicenseNumber: data.driversLicenseNumber,
deliveryAddress: data.deliveryAddress,
passportPhoto: data.passportPhoto,
driversLicensePricingId: Number(priceSelection.id),
driversLicensePhoto: data.driversLicensePhoto,
},
mode: 'onBlur',
// resolver: yupResolver(schema),
});
//submit handler
const onSubmitForm = async (data) => {
try {
let result = await onDriverLicense({
nameOnCurrentLicense: data.nameOnCurrentLicense,
driversLicenseNumber: data.driversLicenseNumber,
deliveryAddress: data.deliveryAddress,
passportPhoto: data.passportPhoto,
driversLicensePricingId: Number(priceSelection.id),
driversLicensePhoto: data.driversLicensePhoto,
}).unwrap();
result.success === true &&
toast.success(result.message, {
position: toast.POSITION.BOTTOM_RIGHT,
});
} catch (error) {
console.log('This is the error', error);
}
};
useEffect(() => {
axiosInstance
.get('drivers-license/pricing')
.then((response) => {
setPrice(response.data.data);
})
.catch((error) => console.log(error));
return () => {};
}, []);
const dispatch = useAppDispatch();
//form field
const personalInputs = [
{ name: 'nameOnCurrentLicense', type: 'text', label: 'Name on current License' },
{ name: 'driversLicenseNumber', type: 'text', label: "Driver's license number" },
{ name: 'deliveryAddress', type: 'text', label: 'Delivery address' },
];
return (
<Container containerStyle='py-12'>
<form onSubmit={handleSubmit(onSubmitForm)}>
<>
<div className='w-full flex md:flex-row flex-col justify-between items-start flex-wrap h-full'>
<div className=' w-full md:w-[55%] px-6 py-3'>
<Typography textStyle='text-md font-semibold'>Fill out your informations</Typography>
<Typography variant='label'>
We’ll require you to fill out some information before we get you started.
</Typography>
<Line variant='header' title='Personal Information' />
{personalInputs.map((item, index) => (
<TextInput
key={index}
{...register(`${item.name}`)}
id={item.name}
type='text'
label={item.label}
name={item.name}
// error={!!errors.item.name}
// helperText={errors?.field.name?.message}
/>
))}
<Line variant='header' title='Upload Document' />
<div className='flex justify-between items-center'>
<div className='w-[45%] pt-4'>
<input
{...register('passportPhoto')}
type='file'
name={'passportPhoto'}
accept='image/png, image/gif, image/jpeg'
/>
</div>
<div className='w-[45%] pt-4'>
<input
{...register('driversLicensePhoto')}
type='file'
name={'driversLicensePhoto'}
accept='image/png, image/gif, image/jpeg'
/>
</div>
</div>
</div>
<div className='w-full md:w-[45%] pl-0 lg:pl-6 pt-12 md:pt-0'>
<Typography variant='h6' textStyle='font-semibold text-center'>
Pricing / Years of Validity
</Typography>
<div className='pt-8'>
<Radio
ref={register}
name='driversLicensePricingI'
onChange={(e) => setDriversLicensePricingId(e.target.value)}
data={price}
/>
</div>
<div className='w-full bg-GRAY flex justify-between items-center p-8 rounded-xl'>
<Typography variant='h5' textStyle='font-semibold'>
Total cost
</Typography>
{priceSelection.amount !== undefined ? (
<Typography textStyle='text-3xl font-medium text-black'>{`₦ ${priceSelection.amount}`}</Typography>
) : (
<Typography textStyle='text-3xl font-medium text-black'>{`₦ 0.00`}</Typography>
)}
</div>
</div>
</div>
<div className='w-full md:w-[55%]'>
<div className='flex flex-row justify-end items-center w-full pt-16'>
<Link to='' className='font-medium text-xs lg:text-sm hover:text-opacity-50'>
Cancel
</Link>
<div className='w-40 ml-4'>
<Button
type='submit'
height={'h-10'}
variant='secondary'
containerButton=' w-full px-4 text-black'
text='Buy now'
/>
</div>
</div>
</div>
</>
</form>
</Container>
);
}
export default Drift;
I'm buildind a React Calendar using Tailwind and Redux and I have a little problem. I have a form where I create a new event and I push this date in my state 'savedEvents'. Atfer that, I want to use events from my state to generate dinamically some spans but my useEffect hook can't rerender when my state is updated and I don't understand why.
GitHub repository for code: https://github.com/snnack123/Events-Calendar
My global state (/store/events.js):
import dayjs from 'dayjs'
const initialState = {
monthIndex: dayjs().month(),
compare: dayjs().month(),
smallCalendarMonth: 0,
daySelected: dayjs(),
showEventModal: false,
savedEvents: [],
}
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, ...state.savedEvents.push(action.payload) };
default:
return state;
}
}
My submit form (/components/EventModal.js):
<form className='bg-white rounded-lg shadow-2xl w-1/4'>
<header className='bg-gray-100 px-4 py-2 flex justify-between items-center'>
<span className='material-icons-outlined text-gray-400'>
drag_handle
</span>
<button onClick={setShowModal}>
<span className='material-icons-outlined text-gray-400'>
close
</span>
</button>
</header>
<div className='p-3'>
<div className="grid grid-cols-1/5 items-end gap-y-7">
<div></div>
<input
type="text"
name="title"
placeholder='Add title'
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className='pt-3 border-0 text-gray-600 text-xl font-semibold pb-2 w-full border-b-2
border-gray-200 focus:outline-none focus:ring-0 focus-blue-500'
/>
<span className='material-icons-outlined text-gray-400'>
schedule
</span>
<p>{daySelected.format('dddd, MMMM DD')}</p>
<span className='material-icons-outlined text-gray-400'>
segment
</span>
<input
type="text"
name="description"
placeholder='Add a description'
value={description}
onChange={(e) => setDescription(e.target.value)}
required
className='pt-3 border-0 text-gray-600 pb-2 w-full border-b-2
border-gray-200 focus:outline-none focus:ring-0 focus-blue-500'
/>
<span className='material-icons-outlined text-gray-400'>
bookmark_border
</span>
<div className="flex gap-x-2">
{labelsClasses.map((lblClass, i) => (
<span key={i} className={`bg-${lblClass}-500 w-6 h-6 rounded-full flex items-center justify-center cursor-pointer`}
onClick={() => setSelectedLabel(lblClass)}>
{selectedLabel === lblClass && (
<span className="material-icons-outlined text-white text-sm">
check
</span>
)}
</span>
))}
</div>
</div>
</div>
<footer className='flex justify-end border-t p-3 mt-5 '>
<button type='button' className='bg-blue-500 hover:bg-blue-600 px-6 py-2 rounded text-white' onClick={saveEvent}>
Save
</button>
</footer>
</form>
Function which update my state from this form:
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const labelsClasses = [
"indigo",
"gray",
"green",
"blue",
"red",
"purple",
];
const [selectedLabel, setSelectedLabel] = useState(labelsClasses[0]);
function saveEvent() {
let new_event = {
title,
description,
label: selectedLabel,
day: daySelected.valueOf(),
id: Date.now(),
};
dispatch({ type: 'events/setNewEvent', payload: new_event });
setShowModal();
}
This is my useEffect that doesn't work (/components/Day.js)
const savedEvents = useSelector((state) => state.events.savedEvents);
const [dayEvents, setDayEvents] = useState([]);
useEffect(() => {
const events = savedEvents.filter(
(evt) =>
dayjs(evt.day).format("DD-MM-YY") === day.format("DD-MM-YY")
);
setDayEvents(events);
}, [savedEvents, day]);
I want to use my state here:
{dayEvents.map((evt, idx) => (
<div
key={idx}
className={`bg-${evt.label}-200 p-1 mr-3 text-gray-600 text-sm rounded mb-1 truncate`}
>
{evt.title}
</div>
))}
The hook UseEffect is working fine, the source of the problem is this:
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, ...state.savedEvents.push(action.payload) }; // <== This will not trigger the re-rendering of your component
default:
return state;
}
}
Your component will not re-render even the change in the state, because you mutated the array savedEvents and Redux will not know that there is a change in the array savedEvents.
Solution:
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, savedEvents: [...state.savedEvents, action.payload] }; // <== add this to trigger the re-rendering of your component
default:
return state;
}
}
...state.savedEvents.push(action.payload)
You're mutating your array, so it is the same array and doesn't trigger the effect. Create a new one instead.
savedEvents: [...state.savedEvents, action.payload]
I have a double slider like https://zillow.github.io/react-slider with min & max values. It calls a query when one of the sliders changes.
But since the query is huge, it takes a lot of time & I need to find a way to use debounce so that the query doesn't get called every so often.
I did find an excellent solution → https://stackoverflow.com/a/58594348/6141587 using just React.js but not sure how to use it with urql?
Home.tsx
import React from 'react'
import ReactSlider from 'react-slider'
import debounce from 'lodash.debounce'
import { AcquisitionList } from '../components/index'
const Home = () => {
const [price, setPrice] = React.useState([0, 1000000000])
const debounceSetPrice = React.useCallback(debounce(setPrice, 2000), [])
return (
<div className="h-full p-8 text-white bg-blue-gray-900">
<div className="flex items-center justify-center">
<div className="flex flex-col items-center text-white">
<span className="">Min</span>
<input
className="text-lg font-bold text-center min-w-16 rounded-xl bg-gradient-to-b from-indigo-700 bg-blue-gray-900"
name="minPrice"
type="text"
value={price[0]}
onChange={(e) => {
const minPrice = e.target.value
const maxPrice = price[1]
debounceSetPrice([minPrice, maxPrice])
}}
/>
</div>
<ReactSlider
step={1}
min={0}
max={1000000000}
className="w-1/2 h-5 pr-2 mx-8 my-4 rounded-md bg-blue-gray-700 cursor-grab"
thumbClassName="absolute w-8 h-8 cursor-[grab] rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 ring-offset-blue-gray-700 -top-1 bg-gradient-to-b from-indigo-700 bg-blue-gray-900 focus:ring-indigo-500 focus:border-indigo-500"
ariaLabel={['Min Price', 'Max Price']}
value={price}
onChange={(price) => {
debounceSetPrice(price)
}}
/>
<div className="flex flex-col items-center text-white">
<span className="">Max</span>
<input
className="text-lg font-bold text-center min-w-16 rounded-xl bg-gradient-to-b from-indigo-700 bg-blue-gray-900"
name="maxPrice"
type="text"
value={price[1]}
onChange={(e) => {
const minPrice = price[0]
const maxPrice = e.target.value
debounceSetPrice([minPrice, maxPrice])
}}
/>
</div>
</div>
<AcquisitionList minPrice={price[0]} maxPrice={price[1]} />
</div>
)
}
export default Home
AcquisitionsList.tsx
import React from 'react'
import { useQuery } from 'urql'
import { Card } from '../components/index'
import {
GET_ALL_ACQUISITIONS,
GET_ACQUISITIONS_BY_PRICE,
} from '../graphql/index'
export const AcquisitionList = ({ minPrice, maxPrice }) => {
const [result, reexecuteQuery] = useQuery({
query: GET_ALL_ACQUISITIONS,
variables: {
minPrice,
maxPrice,
skip: 10,
take: 10,
},
})
const { data, fetching, error } = result
if (fetching) return <p>Loading...</p>
if (error) return <p>Oh no... {error.message}</p>
return (
<div className="flex flex-wrap justify-center mt-10">
{data.getAllAcquisitions.map((startup, i) => {
return <Card key={i} startup={startup} index={i} />
})}
</div>
)
}
My current solution delays the slider values to change for 2 seconds which makes sense as I call debounceSetPrice directly. How do I go about solving this?
I found the solution thanks to URQL maintainer Jovi:
const Home = () => {
const priceRange = [0, 100000000000] // minPrice = 0, maxPrice = 100 billion
const timeout = React.useRef(null)
const [acquisitionPriceRange, setAcquisitionPriceRange] = React.useState(
priceRange
)
const [price, setPrice] = React.useState(priceRange)
React.useEffect(() => {
timeout.current = setTimeout(() => {
setAcquisitionPriceRange(price)
timeout.current = null
}, 2000)
return () => {
if (timeout.current) clearTimeout(timeout.current)
}
}, [price])
return (
<div className="h-full min-h-screen p-8 text-white bg-blue-gray-900">
.
.
.
<AcquisitionList
minPrice={acquisitionPriceRange[0]}
maxPrice={acquisitionPriceRange[1]}
undisclosed={enabled}
sortByPrice={sortByPrice}
sortByStartupName={sortByStartupName}
/>
</div>
)
}
export default Home
I set the value synchronously so the UI doesn't get delayed & queue up a debounce for the state I'm passing down in React.useEffect. I pass the same debounced state down to <AcquisitionList minPrice={acquisitionPriceRange[0]} maxPrice={acquisitionPriceRange[1]} />