why my debounce event call 5 times query? - javascript

I use lodash-debounce on input tag.
If I typed in five letters, api called 5 times.
const onChangeInput: ChangeEventHandler = (e: any) => {
setWords(e.target.value);
e.target.value.length >= 2 && debounceInputChanged();
};
const debounceInputChanged = useMemo(
() =>
debounce(() => {
searchWords();
}, 1000),
[words],
);
doesnt work lodash-debounce.
const onChangeInput: ChangeEventHandler = (e: any) => {
setInput(e.target.value);
debounceInput(e.target.value);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceInput = useMemo(
() =>
debounce((value: any) => {
console.log(value);
}, 3000),
[input],
);
console.log shows 1 12 123 1234
I expect console.log only shows 1234

Create seperate hook for debounce like this
import { useEffect, useState } from "react";
export default function useDebounce(value: string, delay: number = 1000) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
In the Component
const debouncedSearch = useDebounce(words); // usestate variable
useEffect(() => {
searchWords();
}, [debouncedSearch]);

const onChangeInput: ChangeEventHandler = (e: any) => {
setWords(e.target.value);
e.target.value.length >= 2 && debounceInputChanged();
};
const debounceInputChanged = useMemo(
() =>
debounce(() => {
searchWords();
}, 1000),
[words], // you are passing dependency over here so it's called when the word is updated.
);

Related

react-native state is read-only

Following Component in react-native:
import { useEffect, useState } from 'react'
let startValue = null // only using this to restart the counter from resetTimer() (other better approaches?)
export const NewTimer = () => {
const [seconds, setSeconds] = useState(startValue)
const formatedTime = () => {
return [pad(parseInt(seconds / 60)), pad(seconds % 60)].join(':')
}
useEffect(() => {
const timer = setInterval(() => setSeconds(++seconds), 1000) // I guess this line triggers the error
return () => {
clearInterval(timer)
}
}, [])
return formatedTime
}
const pad = (num) => {
return num.toString().length > 1 ? num : `0${num}`
}
export const resetTimer = () => {
startValue = 0
}
results in Uncaught Error: "seconds" is read-only
Can anyone point where the mistake is? Thx!
when you do ++seconds, you are attempting to mutate seconds this render, which isn't allowed. I would use the setState callback to get the current value, and just do seconds + 1, which will accomplish the same thing:
useEffect(() => {
const timer = setInterval(() => setSeconds((seconds) => seconds + 1), 1000)
return () => {
clearInterval(timer)
}
}, [])

How to stop setInterval when tab is not active while using useEffect?

I'm using setInterval in useEffect. When use not actively using tab, I't requesting like forever. It causing some memory issue. I have to, fetch data in every 3000ms, also stop it when user is not using this tab actively. How can I do such a thing?
I tried to use document.visibiltyState and I couldn't worked it.
My code:
useEffect(() => {
try {
const interval = setInterval(() => {
getTransactionGroupStats()
getTransactionGroups()
}, 3000)
getBinanceBalanceStats()
return () => {
clearInterval(interval)
}
} catch (error) {
console.error(error)
}
}, [])
Another alternative and a little more scalable could be that you create a custom hook to see if the user is active or not and every time it changes you execute the useEffect
useActive.ts
export const useActive = (time: number) => {
const [active, setActive] = useState(false)
const timer: any = useRef()
const events = ['keypress', 'mousemove', 'touchmove', 'click', 'scroll']
useEffect(() => {
const handleEvent = () => {
setActive(true)
if (timer.current) {
window.clearTimeout(timer.current)
}
timer.current = window.setTimeout(() => {
setActive(false)
}, time)
}
events.forEach((event: string) => document.addEventListener(event, handleEvent))
return () => {
events.forEach((event: string) => document.removeEventListener(event, handleEvent))
}
}, [time])
return active
}
YourApp.tsx
const active = useActive(3000)
useEffect(() => {
if(active){
try {
const interval = setInterval(() => {
getTransactionGroupStats()
getTransactionGroups()
}, 3000)
getBinanceBalanceStats()
return () => {
clearInterval(interval)
}
} catch (error) {
console.error(error)
}
}
}, [active])
I solved with this approach: https://blog.sethcorker.com/harnessing-the-page-visibility-api-with-react/
export function usePageVisibility () {
const [isVisible, setIsVisible] = useState(!document.hidden)
const onVisibilityChange = () => setIsVisible(!document.hidden)
React.useEffect(() => {
document.addEventListener('visibilitychange', onVisibilityChange, false)
return () => {
document.removeEventListener('visibilitychange', onVisibilityChange)
}
})
return isVisible
}

why my code calling API for multiple time instedof just once after delaying of 500 ms using debounce

I'm trying to call API using debounce but in this case, API calling for every character,
for example, I type hello in search then it calls for he, hel, hell, and hello but I want only for final word hello
useEffect(() => {
updateDebounceWord(word);
}, [word]);
const updateDebounceWord = debounce(() => {
{
word.length > 1 && dictionaryApi();
}
});
function debounce(cb, delay = 500) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
cb(...args);
}, delay);
};
}
const dictionaryApi = async () => {
// inital state []
console.log("hited")
try {
const data = await axios.get(
`https://api.dictionaryapi.dev/api/v2/entries/${category}/${word}`
);
console.log("Fetched",word);
setMeanings(data.data);
} catch (e) {
console.log("error||", e);
}
};
In addition to Dilshans explanation, I wan't to suggest making a hook out of your debounce function, so you can easily reuse it:
const useDebounce = (cb, delay = 500) => {
const timer = useRef();
// this cleans up any remaining timeout when the hooks lifecycle ends
useEffect(() => () => clearTimeout(timer.current), [cb, delay]);
return useCallback(
(...args) => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
cb(...args);
}, delay);
},
[cb, delay]
);
};
use it like this in your components:
const updateDebounceWord = useDebounce((word) => {
console.log("api call here", word);
});
useEffect(() => {
updateDebounceWord(word);
}, [word, updateDebounceWord]);
You are using the debounce on render phase of the component. so each time when the component rebuild a new tree due to the state update, the updateDebounceWord will redeclare. Both current and workInProgress node of the component will not share any data. If you want to share the data between current and workInProgress tree use useRef or else put in global scope
A quick fix is, put the timer variable in global scope.
// keep this on global scope
let timer = null;
function debounce(cb, delay = 500) {
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
cb(...args);
}, delay);
};
}
export default function App() {
const [word, setWord] = useState("");
const sendReq = debounce((keyword) => {
apiReq(keyword);
})
useEffect(() => {
if (word.length > 0) {
sendReq(word);
}
}, [word, sendReq])
const apiReq = (keyword) => {
console.log('reached', keyword);
}
return (
<div className="App">
<input value={word} onChange={(e) => setWord(e.target.value)} />
</div>
);
}
Also put all the dependencies in the useEffect dep array otherwise it may not work as expected.
useEffect(() => {
updateDebounceWord(word);
}, [word, updateDebounceWord]);

How to use useeffect hook in react?

i want to return a function that uses useEffect from the usehook and i am getting error "useeffect is called in a function which is neither a react function component or custom hook.
what i am trying to do?
i have addbutton component and when user clicks add button i want to call the function requestDialog.
below is my code within addbutton file
function AddButton () {
const count = useGetCount();
const requestDialog = useRequestDialog(); //using usehook here
const on_add_click = () => {
requestDialog(count); //calling requestDialog here
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (availableSiteShares: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useRequestDialog(enabled: boolean,count: number) {
return function requestDialog() { //here is the error
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}
}, [count, trigger]);
}
How to solve the error ""useEffect is called in a function which is neither a react function component or custom hook."
i am not knowing how to use useeffect and the same time use it in the addbutton component.
could someone help me with this. thanks
useEffect method is like, useEffect(() => {}, []), But your usage in requestDialog is wrong. Try changing with following.
function requestDialog() {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}

Condition inside setInterval in functional component

I set an interval inside useEffect to update data every 33 seconds if a state variable can_update is true.
The initial value for can_pdate = true. The problem is, even if I change can_update to false (using disable_update function), in the update_groups function it still comes as true.
const [can_update, set_can_update] = useState(true);
const [groups, set_groups] = useState([]);
const intervalRef = useRef();
useEffect(() => {
update_groups();
const update_interval = setInterval(() => {
update_groups();
}, 33000);
intervalRef.current = update_interval;
return () => {
clearInterval(intervalRef.current);
};
}, [project_data.id]);
const update_groups = () => {
if (can_update) {
UI.get(`/project/${project_data.id}/controllers/`).then(
(data) => {
set_groups(data.groups);
},
(error) => {
console.log("Не удалось загрузить список групп");
},
);
}
};
const enable_update = () => {
set_can_update(true);
};
const disable_update = () => {
set_can_update(false);
};
I've tried moving condition into
setInterval: `const update_interval = setInterval(() => {
if (can_update){ update_groups()};
}
and replacing setInterval for recursive setTimeout. No changes.
I've had somewhat similar code inside a class component, and there didn't seem to be any problems like this.
You need add can_update to useEffect deps, otherwise
all values
from the component scope (such as props and state) that change over
time and that are used by the effect.
https://reactjs.org/docs/hooks-effect.html
In your case useEffect was called once, inside it every 33 seconds a function update_groups was called with scoped value can_update = true.
React.useEffect(() => {
if (can_update) {
update_groups();
const update_interval = setInterval(() => {
update_groups();
}, 33000);
intervalRef.current = update_interval;
return () => {
clearInterval(intervalRef.current);
};
}
}, [project_data.id, can_update]);
const update_groups = () => {
UI.get(`/project/${project_data.id}/controllers/`).then(
data => {
set_groups(data.groups);
},
error => {
console.log('Не удалось загрузить список групп');
},
);
};

Categories

Resources