React-Native onSubmitEditting taking too much time to execute function - javascript

I am following a react-native tutorial but am having some trouble with React contexts and ActivityIndicator, I dont know where the problem lies, but I will try to be as descriptive as possible.
The problem:-
The code :
I am using contexts to provide the app with the location that has been searched and then searching for that location within my mock data, later returning the restaurants around that location.
complete source code at https://github.com/diivi/KiloBite/blob/main/src/services/location/location.context.js
Here I am using the onSearch function and passing it as a context prop to my search box to use with onSubmitEditing.

In your code you call onSearch and there you setILoading(true)
and setKeyword(searchKeyword).
Then in the useEffect you use the keyword you set in onSearch. Your useEffect runs only in keyword changes (see dependencies).
Try to add onSearch in your dependencies (look below).
Or maybe even locationRequest, locationTransform.
I would also try setIsLoading and generally try to put as dependencies everything you use in your useEffect.
const onSearch = (searchKeyword) => {
setIsLoading(true);
setKeyword(searchKeyword);
};
useEffect(() => {
if (!keyword.length) {
return;
}
locationRequest(keyword.toLowerCase())
.then(locationTransform)
.then((result) => {
setIsLoading(false);
setLocation(result);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
}, [keyword, onSearch]);
BUT, at the end I wonder, why you use a useEffect?
Why don't you just move all the code in onSearch:
const onSearch = (searchKeyword) => {
setIsLoading(true);
// setKeyword(searchKeyword); // not needed
if (!searchKeyword.length) {
return;
}
locationRequest(searchKeyword.toLowerCase())
.then(locationTransform)
.then((result) => {
setIsLoading(false);
setLocation(result);
})
.catch((err) => {
setIsLoading(false);
setError(err);
});
};

Related

How do I mock this custom React hook with API call

I am new to React testing and I am trying to write the tests for this custom hook which basically fetches data from an API and returns it.
const useFetch = (url) => {
const [response, setResponseData] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => {
if(response.ok === false){
throw Error('could not fetch the data for that resource');
}
return response.json();
})
.then(data => {
setResponseData(data);
setIsLoading(false);
})
.catch((err) => {
console.log(err.message);
})
}, [url]);
return {response, isLoading};
}
export default useFetch;
Since I am new to this I have tried many solutions but nothing is working and I am getting confused too. Can you provide me a simple test for this so that I can get an idea as to how it is done?
here's an example https://testing-library.com/docs/react-testing-library/example-intro/ using msw library to mock server call.
If you mock the server instead of the hook, it will be more resilient to changes.
Moreover, I suggest you to use a library like react-query or read source code of them and reimplement them if you want to learn to avoid fetching pitfalls

useEffect fetch request is pulling data twice

What I Want:
I'm pulling data from an api, then setting the data to state. I've done this inside a useEffect hook, but when I console.log the data afterwards, it's displaying the data twice, sometimes 4 times. I'm at a loss as to why this is happening.
What I've Tried:
console.log within useEffect to see data from source
disabling react developer tools within chrome.
My Code:
// make api call, assign response to data state
const [apiData, setApiData] = useState();
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
fetchData();
}, []);
console.log(apiData);
Result of console.log:
As was mentioned in the other comment this is due to effects being "double invoked" in strict-mode.
A common solution and one I believe has been suggested by the React team (although am struggling to find where I read this) is to use useRef.
// make api call, assign response to data state
const [apiData, setApiData] = useState();
const hasFetchedData = useRef(false);
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
if (hasFetchedData.current === false) {
fetchData();
hasFetchedData.current = true;
}
}, []);
If you are using React version 18+, StrictMode has a behavior change. Before it wasn't running effects twice, now it does to check for bugs. ( Actually, it does mount-remount, which causes initial render effects to fire twice)
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors

Twilio Remove listeners from conversations

Hello trying to make a conversations app based on twilio
The app is doing fine but everytime someone clicks a Conversation from the Conversations List, it starts a useEffect, the useEffect has something like the following function:
const setChannelEvents = useCallback((channel) => {
channel.on('messageAdded', (message) => {
setMessages(prevMessages => [...message, prevMessages])
})
The problem is that every time we leave the conversation that listener stays on so every time someone clicks on a conversation the app is creating a loot of listeners and well that isn't cool.
Wondering how to turn off the listener when it leaves the component
Tried something like this:
useEffect(() => {
Twilio.getClient()
.then((client) => client.getConversationBySid(channelId))
.then((channel) => setChannelEvents(channel))
.catch((err) => console.log('err', err.message))
.finally(() => setLoading(''))
return () => chatClientChannel.current.removeListener('messageAdded')
}, [channelId, setChannelEvents])
Didnt work
Any help would be appreciated, Thanks :)
Twilio developer evangelist here.
I think your useEffect is mostly right, but you are potentially using a different object in the return clause to the original channel. The beauty of a React hook like useEffect is that you can encapsulate some of the state in the function, and then use that in the tear down.
Try this instead:
const handleMessageAdded = (message) => {
// do something with the new message
}
useEffect(() => {
let currentChannel;
Twilio.getClient()
.then((client) => client.getConversationBySid(channelId))
.then((channel) => {
currentChannel = channel;
return setChannelEvents(channel);
})
.catch((err) => console.log('err', err.message))
.finally(() => setLoading(''))
return () => {
if (currentChannel) {
currentChannel.removeListener('messageAdded', handleMessageAdded);
}
};
}, [channelId, setChannelEvents]);
Here you set the currentChannel (I'm using let to define the variable and then updating it once the promise resolves. You could also do this by breaking your promise chain up into awaited functions that set the currentChannel) in the useEffect function, then remove the listener from that same object in the tear down function.

React state is empty after setting

I have simple nextjs app where i want to save and print state to fetchData
This is my code
const Room = () => {
const router = useRouter();
const [fetchData, setFetchData] = useState("");
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router]);
const getDataNames = async () => {
try {
await fetch("http://localhost:1337/rooms?_id=" + router.query.id)
.then((response) => response.json())
.then((jsonData) => setFetchData(jsonData) & console.log(jsonData)); // Data are logged in console
} catch (e) {
console.error(e);
}
};
Problem is that fetchData is empty on console log but jsonData gaves me actual data. And i have no idea what can be problem.
Ok so 3 things here. Firstly, your useEffect doesn't re-run when your fetchData value changes. You need to add it to the dependency list:
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router, fetchData]);
this way the effect will run when fetchData changes. And of course it will only console.log it if that condition inside the effect's if condition is satisfied.
Secondly, you're mixing async/await and .then/.catch syntax, don't do that. Use one or the other. In this case you can just remove the await keyword and try/catch block, and use your existing .then code, and add a .catch to it to catch any errors.
Finally, & in Javascript is the bitwise AND operator, so not sure why you were using it there. If you think it means "and also do this", then that's incorrect. Just put the .then function inside curly braces and call the setFetchData and console.log statements one after the other:
.then((jsonData) => {
setFetchData(jsonData);
console.log(jsonData)
});
useEffect(() => {
console.log(fetchData);
}, [fetchData]);
use this hook to log your data

Function inside component not receiving latest version of Redux-state to quit polling

I have an issue where I am trying to use the Redux state to halt the execution of some polling by using the state in an if conditional. I have gone through posts of SO and blogs but none deal with my issue, unfortunately. I have checked that I am using mapStateToProps correctly, I update state immutably, and I am using Redux-Thunk for async actions. Some posts I have looked at are:
Component not receiving new props
React componentDidUpdate not receiving latest props
Redux store updates successfully, but component's mapStateToProps receiving old state
I was kindly helped with the polling methodology in this post:Incorporating async actions, promise.then() and recursive setTimeout whilst avoiding "deferred antipattern" but I wanted to use the redux-state as a single source of truth, but perhaps this is not possible in my use-case.
I have trimmed down the code for readability of the actual issue to only include relevant aspects as I have a large amount of code. I am happy to post it all but wanted to keep the question as lean as possible.
Loader.js
import { connect } from 'react-redux';
import { delay } from '../../shared/utility'
import * as actions from '../../store/actions/index';
const Loader = (props) => {
const pollDatabase = (jobId, pollFunction) => {
return delay(5000)
.then(pollFunction(jobId))
.catch(err => console.log("Failed in pollDatabase function. Error: ", err))
};
const pollUntilComplete = (jobId, pollFunction) => {
return pollDatabase(jobId, pollFunction)
.then(res => {
console.log(props.loadJobCompletionStatus) // <- always null
if (!props.loadJobCompletionStatus) { <-- This is always null which is the initial state in reducer
return pollUntilComplete(jobId, pollFunction);
}
})
.catch(err=>console.log("Failed in pollUntilComplete. Error: ", err));
};
const uploadHandler = () => {
...
const transferPromise = apiCall1() // Names changed to reduce code
.then(res=> {
return axios.post(api2url, res.data.id);
})
.then(postResponse=> {
return axios.put(api3url, file)
.then(()=>{
return instance.post(api3url, postResponse.data)
})
})
transferDataPromise.then((res) => {
return pollUntilComplete(res.data.job_id,
props.checkLoadTaskStatus)
})
.then(res => console.log("Task complete: ", res))
.catch(err => console.log("An error occurred: ", err))
}
return ( ...); //
const mapStateToProps = state => {
return {
datasets: state.datasets,
loadJobCompletionStatus: state.loadJobCompletionStatus,
loadJobErrorStatus: state.loadJobErrorStatus,
loadJobIsPolling: state.loadJobPollingFirestore
}
}
const mapDispatchToProps = dispatch => {
return {
checkLoadTaskStatus: (jobId) =>
dispatch(actions.loadTaskStatusInit(jobId))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DataLoader);
delay.js
export const delay = (millis) => {
return new Promise((resolve) => setTimeout(resolve, millis));
}
actions.js
...
export const loadTaskStatusInit = (jobId) => {
return dispatch => {
dispatch(loadTaskStatusStart()); //
const docRef = firestore.collection('coll').doc(jobId)
return docRef.get()
.then(jobData=>{
const completionStatus = jobData.data().complete;
const errorStatus = jobData.data().error;
dispatch(loadTaskStatusSuccess(completionStatus, errorStatus))
},
error => {
dispatch(loadTaskStatusFail(error));
})
};
}
It seems that when I console log the value of props.loadJobCompletionStatus is always null, which is the initial state of in my reducer. Using Redux-dev tools I see that the state does indeed update and all actions take place as I expected.
I initially had placed the props.loadJobCompletionStatus as an argument to pollDatabase and thought I had perhaps created a closure, and so I removed the arguments in the function definition so that the function would fetch the results from the "upper" levels of scope, hoping it would fetch the latest Redux state. I am unsure as to why I am left with a stale version of the state. This causes my if statement to always execute and thus I have infinite polling of the database.
Can anybody point out what might be causing this?
Thanks
I'm pretty sure this is because you are defining a closure in a function component, and thus the closure is capturing a reference to the existing props at the time the closure was defined. See Dan Abramov's extensive post "The Complete Guide to useEffect" to better understand how closures and function components relate to each other.
As alternatives, you could move the polling logic out of the component and execute it in a thunk (where it has access to getState()), or use the useRef() hook to have a mutable value that could be accessed over time (and potentially use a useEffect() to store the latest props value in that ref after each re-render). There are probably existing hooks available that would do something similar to that useRef() approach as well.

Categories