Nextjs Listener with callback executes itself - reactjs

I have been working on a real-time notification system for my nextjs app. It is working great, and I call it like this in each page:
export default function Bot({user}) {
startNotifications(user)
}
The function itself looks like this
const userID = getUserID(user)
const pubnub = new PubNub({
subscribeKey: subKey,
uuid: userID
});
const { asPath } = useRouter()
pubnub.unsubscribeAll();
pubnub.addListener({
signal: function(signal) {
const message = signal.message
if (message[0] == "bot") {
Notiflix.Notify.Info(notificationMessage);
if (check if url is correct here) {
callback()
return
}
}
}
})
pubnub.subscribe({
channels: ["announcements", userID.toString()],
});
But when i try to add a callback funtion to change the state, depending on notification like this:
export default function Bot({user}) {
startNotifications(user, changeState)
const [value, setValue] = useState(true)
function changeState(){
console.log("changing state")
setValue(false)
}
}
The code works fine, and the state is getting changed, but for some reason the code loops over itself and doubles the number of listeners each notification. In the startNotifications function I even tried to unsubscribe and resubscribe, but that doesn't work either. It looks like this
(First notification)
And then if I do it a couple more times the 4. Notification looks like this:
I am pretty much at the end of trying. I have tried to implement the startNotifications in the same file, but that does the same thing.
If anyone has a solution to this (its probably something very obvious that I don't know) please let me know. Have a great day.

One thing that you are missing is putting the whole startNotifications inside an useEffect hook. Without it, all of this code is executed each time the component rerenders.
export default function Bot({user}) {
useEffect(() => startNotifications(user), []);
// rest of the code
}

Related

How to get current state value inside an async function

I have created some state at the top level component App() and created a getter method for this state so I could pass it on to any function to be able to get its current state.
App.js
const [searchState, changeScreen] = useState("");
const getSearchState = () => {
console.log("searchState is", searchState);
return searchState;
}
scripts/search.js
export const performSearch = async (searchText, changeScreen, getSearchState) => {
if(searchText) {
console.log("1", getSearchState())
let res = await doSearchQuery(searchText);
console.log("2", getSearchState())
if(res.status) {
// *** getSearchState() should have a value of "loading" here
if(getSearchState() !== "expanded") {
changeScreen("results");
}
}
else {
//
}
}
}
components/SearchComponent.js
import { performSearch } from '../scripts/search';
function SearchHistoryComponent({changeScreen, getSearchState}) {
...
// This method is fired from an onPress()
const performHistorySearch = async (text) => {
changeScreen('loading');
await performSearch(text, changeScreen, getSearchState);
}
...
}
I then pass getSearchState() as a parameter to a standalone asynchronous function in a different script to be able to look up the searchState value but it doesn't seem to be working as intended.
The value I'm getting seems to be the previous value and not the current value at the time getSearchState() is called - as can be seen from the console outputs I have setup:
searchState is expanded
searchState is loading
1 expanded
2 expanded
searchState is results
Am I doing something wrong?
This is exactly how React callback functions work.
That is, every line in your performSearch function is engaged to one searchState value, i.e, "expanded".
If you want to get "loading" from getSearchState(), you need to call getSearchState() again in useEffect by passing searchState or getSearchState in the dependency array.
To be more clear, setting a state value after awaiting a promise works fine, but if you want to pull the newly set state value inside the same function body, it won't work.
That said, you need to listen to the newly set state value in useEffect or just make your code declarative so it behaves according to state changes.
Just to help you understand this better, I've written a quick snack here to show the difference between pulling the state from a function body and a useEffect.
https://snack.expo.dev/h65-cPvIb
Thanks.

Update state in setInterval via dispatch outside component

I currently have a functional component Form that triggers a task to occur. Once the submission is complete, I create a setInterval poll to poll for the status of the task. The code roughly looks like
export function Form(props: FormProps) {
const dispatch = useDispatch()
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
dispatch(Actions.displayTaskComplete())
clearInterval(intervalId)
}
})
}
const submitForm = async() => {
await onSubmitForm() // Function in different file
pollTaskStatus()
}
return (
...
<button onClick={submitForm}>Submit</button>
)
}
When the action is dispatched, the redux store is supposed to be updated and a component is supposed to update alongside it showing a message that the task is complete. However, I see the action logged with an updated store state but nothing occurs. If I just try to dispatch the same action with useEffect() wrapped around it outside the submitForm functions, the message appears. I've searched online and people say that you need to wrap useEffect around setInterval but I can't do that because the function that calls setInterval is not a custom hook or component. Is there a way to do this?
It's a bit difficult to answer your question without seeing all the code.
But my guts feeling is that this might no have anything to do with React.
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
console.log('fire to fetch')
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
console.log('success from fetch')
dispatch(Actions.displayTaskComplete())
}
})
}
Let's add two console lines to your code. What we want to see is to answer the following questions.
is the setInterval called in every 500ms?
is any of the call finished as success?
how many dispatch has been fired after submission
If you can answer all these questions, most likely you can figure out what went wrong.

React hooks with AJAX requests

I am using React and I was wondering if I am doing things correctly.
I want to make multiple ajax requests in React for multiple datalists for inputboxes.
I do something like this:
This is my fetch function
function GetData(url, processFunc) {
...
jquery ajax request using url
...
if (status === 'success') {
if (processFunc) {
processFunc(data);
}
}
}
A solution which only displays the results that were the fastest.
function ComponentA() {
const [ajaxDataA, setAjaxDataA] = useState(null);
const [ajaxDataB, setAjaxDataB] = useState(null);
const [ajaxDataC, setAjaxDataC] = useState(null);
const [dataA, setDataA] = useState(null);
...dataB..
...dataC..
const exampleFunctionA = function(data) {
..processes data into result
setDataA(result);
}
const exampleFunctionB = ....
const exampleFunctionC = ...
useEffect( () => {
GetData(url_1, exampleFunctionA);
GetData(url_2, exampleFunctionB);
GetData(url_3, exampleFunctionC);
}, []);
return (<>
...
{dataA}
{dataB}
...
<>);
}
B Solution is why Im asking this question. This works fine but I'm not sure it is correct or this is how hooks were meant to use.
function ComponentA() {
const [ajaxDataA, setAjaxDataA] = useState(null);
const [ajaxDataB, setAjaxDataB] = useState(null);
const [ajaxDataC, setAjaxDataC] = useState(null);
const [dataA, setDataA] = useState(null);
...dataB..
...dataC..
useEffect( () => {
GetData(url_1, setAjaxDataA);
GetData(url_2, setAjaxDataB);
GetData(url_3, setAjaxDataC);
}, []);
useEffect( () => {
..processes data into result
setDataA(result);
}, [ajaxDataA]);
..useEffect ... ,[ajaxDataB] ...
... [ajaxDataC] ...
return (<>
...
{dataA}
{dataB}
...
<>);
}
I have found this solution so I dont repeat the logic. I dont use this solution because I have to process the data so basically instead of
const a = GetData(..., processFunc);
I'd use this solution. And so I would still need the useEffects that watch when it refreshes
const a = useFetch(...)
Ajax request won't display in react render function
so basically the question is:
Is solution B a good solution?
Okay well.. I leave the question up if anyone else had gone on this awful path. So basically I was waiting for the AJAX requests to finish loading with the useEffect hook
At first it looked pretty logical:
I send the request.
when it's done it calls the useState hooks' setter function
the state variable updates
useEffect hook triggers
and I do something with the arrived data in the useEffect body
This was major dumb dumb
since it refreshed the state like it was forced to, having a gun pointed at its head
It caused inconsistencies: A child component would want to use the AJAX data before it arrived. It was like a random number generator. Half the time the page loaded and the other half it died running into nullreference exceptions.
solution: Promise

React Hooks: Referencing data that is stored inside context from inside useEffect()

I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!

How to log to the console in react

I'm a beginner in react and I'm just trying to dechiper a very complex code by simply logging values like props among others.
Specifically, I need to log data every time the handleScroll method is called by an event listener. The code uses lodash for throttling.
The code goes like this, but no data gets written to the console in F12:
appNode = null;
state = {
sticky: false
};
componentDidMount() {
this.props.actions.getQueues();
this.appNode = getScrollableNode(this.context.renderRoot);
if (this.appNode) {
this.appNode.addEventListener("scroll", this.handleScroll);
}
}
.
.
.
handleScroll = throttle(() => {
console.log(JSON.stringify(`this.props: ${this.props}`));
const {
actions,
query,
page,
hasMore,
loadingMore,
searchTerm,
loading
} = this.props;
const scrollTop = this.appNode.scrollTop;
The console.log there does nothing.
Any help would be greatly appreciated.
Nevermind, it wasn't a problem with the logging or anything else, but with the customized component's innerworkings.

Resources