React Hooks API call - does it have to be inside useEffect? - reactjs

I'm learning React (with hooks) and wanted to ask if every single API call we make has to be inside the useEffect hook?
In my test app I have a working pattern that goes like this: I set the state, then after a button click I run a function that sends a get request to my API and in the .then block appends the received data to the state.
I also have a useEffect hook that runs only when the said state changes (using a dependency array with the state value) and it sets ANOTHER piece of state using the new data in the previous state. That second piece of state is what my app renders in the render block.
This way my data fetching actually takes place in a function run on a button click and not in the useEffect itself. It seems to be working.
Is this a valid pattern? Thanks in advance!
Edit: example, this is the function run on the click of the button
const addClock = timezone => {
let duplicate = false;
selectedTimezones.forEach(item => {
if (item.timezone === timezone) {
alert("Timezone already selected");
duplicate = true;
return;
}
});
if (duplicate) {
return;
}
let currentURL = `http://worldtimeapi.org/api/timezone/${timezone}`;
fetch(currentURL)
.then(blob=>blob.json())
.then(data => {
setSelectedTimezones(prevState => [...prevState, data]);
}
);
}

Yes, apis calls that happen on an action like button click will not be part of useEffect call. It will be part of your event handler function.
When you call useEffect, you’re telling React to run your “effect”
function after flushing changes to the DOM
useEffect contains logic which we would like to run after React has updated the DOM. So, by default useEffect runs both after the first render and after every update.
Note: You should always write async logic inside useEffect if it is not invoked by an event handler function.

Yes, you can make api requests in an event handler such as onClick.
What you don't want to do is make a request directly inside your functional component (since it will run on every render). As long as the request is inside another function and you only call that function when you actually want to make a request, there is no problem.

Related

when changing deps, useEffect re-runs the setInterval inside itself. how to avoid?

There is a web application - a simple widget that loads local time from the WorldTime API for different regions.
The task is to fetch time from the server every five seconds.
To do this, I used setInterval inside useEffect , in which I put the function where fetch occurs. However, there is a problem - when selecting / changing the region, I have to wait five seconds until setInterval completes.
setInterval, as I understand it, is restarted inside useEffect when deps changes, when a new region is selected.
How can this problem be solved?
App on github: https://mmoresun.github.io/live-clock/
The code itself: https://codesandbox.io/s/codepen-with-react-forked-66iz3n?file=/src/App.js
You can call directly the same getTime function before setting the interval
useEffect(() => {
getTime(value);
const myInterval = setInterval(() => {
// refreshing with setInterval every 5 seconds
getTime(value);
}, 5000);
return () => clearInterval(myInterval);
}, [value]); /
As for your codepen example, you are continuosly calling the timezone api, because you fetch it inside the SearchPanel component. That's wrong, remove it.

React/Redux : Detect and exectute instructions before component destruction

Ola,
In my redux state, I have a isEditing boolean value, initiate at false, to manage a profile editing mode. I create a redux action to toggle this state. Below, some screen of the component render.
It works in most cases:
When I click on Edit name, I toggle to true and display the edit form
When I click on Save (or Cancel), it perform (or not) api request, then toggle to false, displaying the initial component
But if I start editing then quit page manualy (via logo link or url), and then come back to this page, obviously, the edit mode is still active at true.
I would like to put my state to false when I leave page, but I don't find any option to dispatch an action before component destroyed (like old componentWillUnmount() class programming method or beforeUnmount VueJS equivalent)
PS : I use react router V6. It seems a hook was implemented during beta but never released, and no news since :(
An alternative is to force value to false on component creation, with useEffect. It works, but I think it's not the right place. I see the edit mode opened then close in my ui during a few ms.
Thanks in advance
Try using the clean up on an useEffect hook
useEffect(() => {
// Specify how to clean up after this effect:
return function cleanup() {
// make redux call to toggle edit mode
};
});
I believe you could dispatch an action in the useEffect return value like useEffect(() => { return () => dispatch(changeEditMode(false)) }, []). It is mostly similar to a componentWillUnmount().

useState not updating correctly with socket.io

I am pulling data via socket.io but when listening for incoming messages from the server my state is not updating correctly.
I can see the data is being pulled correctly from the socket (50 reqs peer second) but setQuotes just replaces the existing item with the new item returned from the server (so my state always has a length of one).
const [quotes, setQuotes] = useState([])
const subscribe = () => {
let getQuote = 'quote.subscribe';
socket.emit(getQuote, {});
socket.on('listenAction', function (msg) {
setQuotes([...quotes, msg]) // This just replace the entire state instead of adding items to the existing array
});
}
Subscribe // Open web socket stream
You need to move the listener outside of the subscribe function.
Since it is a sideEffect you should wrap it in React.useEffect
It's also a good idea to use functional setstate to read previous values.
React.useEffect(() => {
socket.on('listenAction', function (msg) {
setQuotes((quotes) => [...quotes, msg])
});
}, []);
In your example you use setState like this
setQuotes([...quotes, msg])
And every time you get message, javascript engine tries to find "quotes" variable in scope of this function
function (msg) {
It can not find it here and move to the scope, where this function was defined ( scope of component function).
For every function call, there is new scope. And react calls component function for each render. So, your function with "msg" in arguments, where you use setQuotes, every time uses "quotes" state from first render.
In first render you have an empty array.
Then you have [...firstEmptyArray, firstMessage]
Then you have [...firstEmptyArray, secondMessage]
Then you have [...firstEmptyArray, thirdMessage].
Probably, you can fix it if you will use setQuotes like;
setQuotes(oldQuotes => [...oldQuotes, msg]);
In this way it will use previousValue for calclulating new one.
PS. Be aware of not to call your subscribe function directly in each render and put it in useEffect with correct dependency array as second argument.
You can use the concat func over here
setQuotes(prevState => prevState.concat(msg))

set state in a callback of an async function

I am new to React, so bear with me please. I have a component that calls another component that takes a property. This property will get it's value on a callback of a function, something like this:
render(){
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp))
this.setState({myProp: p})
});
return <MyComponent myProp={this.state.myProp}/>
}
myFunc will or will not make an API request and depending on that will call the callback sooner or later. This seems to work fine when API request is made and the callback takes longer to return. However, when the request is not needed and callback returns instantaneously (or almost) I am getting a Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
What am I doing wrong and what is the right way to approach this? Where would be the right place to put this code? Basically what I need is to re-render MyComponenent if this.state.myProp changes
You shouldn't be calling setState inside the render method, you might end up having an infinite loop.
The call to myFunc should be somewhere else (depending on the business logic you have). When the function finishes, it will update the state and then trigger a re-render so MyComponent will get the latest value.
UPDATE
I don't know which conditions will require calling myFunc again, but you can do:
state = {
myProp: null // or some other value that MyComponent can handle as a null state
}
componentDidMount () {
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp)) // This is needed only if you get null back from the callback and you don't want to perform an unnecesary state update
this.setState({myProp: p})
}
}
render(){
const { myProp } = this.state
// You can also do if (!myProp) return null
return <MyComponent myProp={myProp}/>
}

React-redux make a sync callback

i have this code:
that.props.getChats(that.props.state.chatModule.login.body.agent[0].company_id, that.props.state.chatModule.sessions.chatHashesChoosed)
that.scrollChatToBottom(sessionHash)
that.props.getChats is a function creator which use async ajax call.
i want that.scrollChatToBottom to run only after the ajax call return. the thing is that because i use this function creator, i dont get any promise or someting...
i dont want to make the call inside the component.
I guess getChats will retrieve some data and update the app state, which then trigger your component to be re-rendered according to the newly retrieved data? If so, you can trigger scrollChatToBottom in componentDidUpdate, e.g.
componentDidUpdate(prevProps, prevState) {
if (this.props.chatData !== NULL) { // assuming chatData is the data retrieved by getChats
// Do scrollChatToBottom here
}
}

Resources