Call a function that triggers a service call on every state change - javascript

I have a group of check boxes that filter the data. After the initial data is rendered and as I'm dealing with charts, I want to make the UX dynamic. Therefore, for every state change in my react component, I want to call a function that triggers a service.
handleChange = (query) => {
if(this.state.initialSearchTriggered) {
this.setState({query})
this.triggerReportsService()
}
}
Now the problem is, react takes time to update the state, and the triggerReportsService uses this.state.query to call the service. Therefore, the service query parameter does not have the latest filters. Is there a better way to do this? I was thinking to add componentDidUpdate() method but service calls are getting called multiple times than expected.
componentDidUpdate() {
this.state.initialSearchTriggered ? this.triggerReportsService() : null;
}
Please help
Thank you!

Add a callback to your setState function. The callback will fire when the state update is complete.
handleChange = (query) => {
if(this.state.initialSearchTriggered) {
this.setState({query}, this.triggerReportsService)
}
}

Related

JS executing out of order when dispatching redux actions

I have a more complex version of the following pseudo-code. It's a React component that, in the render method, tries to get a piece of data it needs to render from a client-side read-through cache layer. If the data is present, it uses it. Otherwise, the caching layer fetches it over an API call and updates the Redux state by firing several actions (which theoretically eventually cause the component to rerender with the new data).
The problem is that for some reason it seems like after dispatching action 1, control flow moves to the top of the render function again (starting a new execution) and only way later continues to dispatch action 2. Then I again go to the top of the render, and after a while I get action 3 dispatched.
I want all the actions to fire before redux handles the rerender of the component. I would have thought dispatching an action updated the store but only forced components to update after the equivalent of a setTimeout (so at the end of the event loop), no? Is it instead the case that when you dispatch an action the component is updated synchronously immediately, before the rest of the function where the dispatch happens is executed?
class MyComponent {
render() {
const someDataINeed = CachingProvider.get(someId);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}
}
class CachingProvider {
get(id) {
if(reduxStoreFieldHasId(id)) {
return storeField[id];
}
store.dispatch(setLoadingStateForId(id));
Api.fetch().then(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
});
return null;
}
}
In addition to #TrinTragula's very important answer:
This is React behaviour. Things that trigger rerenders that are invoked synchronously from an effect/lifecycle or event handler are batched, but stuff that is invoked asnychronously (see the .then in your code) will trigger a full rerender without any batching on each of those actions.
The same behaviour would apply if you would call this.setState three times in a row.
You can optimize that part by adding batch which is exported from react-redux:
Api.fetch().then(() => {
batch(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
})
});
You should never invoke heavy operations inside of a render function, since it's going to be triggered way more than you would like to, slowing down your app.
You could for example try to use the useEffect hook, so that your function will be executed only when your id changes.
Example code:
function MyComponent {
useEffect(() => {
// call your method and get the result in your state
}, [someId]);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}

API call on event in react, componentDidUpdate or eventHandler?

Where should we ideally place an api call to be made on occurrence of an event in React
Inside the eventHandler or componentDidUpdate ?
example:
handleItemClick = (item) => (event) => {
this.setState({selectedItem: item});
this.props.requestDataActionDispatch(item);
}
OR
componentDidUpdate(prevProps, prevState, snapshot) {
if(prevState.item !== this.state.item) {
this.props.requestDataActionDispatch(item);
}
}
Depends
But a simple solution is, if you want to call some API after change of state value then you must go for eventHandler. Also check for callback in setState.
handleItemClick = (item) => (event) => {
this.setState({selectedItem: item}, () => this.props.requestData(item));
}
I don't see any reason to wait for component update, I'd just put it in the event handler.
Naturally, in either case your component needs to know how to render appropriately when it has a selected item but doesn't have the data from the API yet...
(Side note: If requestDataActionDispatch results in a state change in your component, you probably want to clear that state when setting the selected item prior to the request, so you don't have one item selected but still have the state related to the previous item. But I'm guessing from the fact it's on props that it doesn't...)
It depends
I would prefer to call the api inside componentDidUpdate. Why ? Because it's cleaner. Whenever there is a change in state or props, componentDidUpdate will be called. So definitely, there has to be a condition inside componentDidUpdate like you have mentioned
if(prevState.item !== this.state.item)

React - setInterval inside an onChange event

I want to set setInterval inside an onChange event, like mostly it is done on componentDidMount.
I have 2 dropdowns that filter data and then render a table, the dropdowns are controlled by onChange methods. The last on change method has a request that needs to be re-polled every X # seconds to get updated information from the server. However, these methods are outside of cDM so I'm not sure how to handle the setInterval like I previously have.
cDM() {
//axios call, update state with fetched data
}
onChange1 () {
// uses data from cDM to render the options in a dropdown.
// another axios call based on the selection, fetches data to render more options in subsequent dropdown
}
onChange2 () {
//dropdown 2 use data from onChange1 axios call. After the selection from the dropdown is made, it makes an api call that then renders data to a table.
//Some of this data updates every 5 seconds, so I need to re-poll this service to get updated information from the server.
}
if all the data was in cDM I'd normally change the data requests in cDM to an arrow function to avoid setState issues, call the function inside/outside of the setInterval callback with the following:
componentDidMount() {
this.interval = setInterval(() => this.getData(), 10000);
this.getData();
}
componentWillUnMount() {
clearInterval(this.interval);
}
getData = () => {
//data requests
}
SetInterval does not wait for the AJAX response before polling starts over again. This can become extremely buggy/memory intensive in the event of network problems.
I would suggest you use a setTimeOut and every update I would put a piece of response data in state and start the timer upon state changes inside your render function. That way you always ensure you get your result back before pounding the server again and bogging down your client's UI.
Place the code of componentDidMount body into both onChange events.
Set Interval equal to some state variable, so on componentWillUnmount you can access that interval and remove it.
onChange () {
// Change Logic
....
// Interval logic
clearInterval(this.state.interval); //Clear interval everytime.
this.state.interval = setInterval(() => this.getData(), 10000);
this.getData();
}
componentWillUnMount() {
clearInterval(this.state.interval);
}
You can indeed place this in your update event handler - event handlers do not have to be pure. There is the minor issue that doing this means that your view is no longer a function of state + props, but in reality that's not usually a problem.
It seems your main concern is how you model this data.
One way to do this might be to use a higher order component or some kind of composition, splitting the display of your component from the implementation of the business logic.
interface Props<T> {
intervalMilliseconds: number
render: React.ComponentType<ChildProps<T>>
}
interface State<T> {
// In this example I assume your select option will be some kind of string id, but you may wish to change this.
option: string | undefined
isFetching: boolean
data: T
}
interface ChildProps<T> extends State<T> {
setOption(option: string | undefined): void
}
class WithPolledData<T> extends React.Component<Props<T>, State<T>> {
interval: number
componentDidMount() {
this.setupSubscription()
}
componentDidUnmount() {
this.teardownSubscription()
}
setupSubscription() {
this.interval = setInterval(() => this.fetch(), this.props.intervalMilliseconds)
}
teardownSubscription() {
clearInterval(this.interval)
}
componentDidUpdate(previousProps: Props, previousState: State) {
if (previousProps.intervalMilliseconds !== this.props.intervalMilliseconds) {
this.teardownSubscription()
}
if (previousState.option !== this.state.option && this.state.option !== undefined) {
this.fetch()
}
}
fetch() {
if (this.props.option === undefined) {
// There is nothing to fetch
return
}
// TODO: Fetch the data from the server here with this.props.option as the id
}
setOption = (option: string | undefined) => {
this.setState({ option })
}
render() {
return React.createElement(
this.props.render,
{
...this.state,
setOption: this.setOption
}
)
}
}
You could use this component like so:
<WithPolledData intervalMilliseconds={5000} render={YourComponent} />
YourComponent would call setOption() whenever a drop down option was selected and the fetching would happen in the background.
This is how you would technically do this. I agree with #ChrisHawkes that using setInterval like this is likely a mistake - You would at the very least need to cancel in-flight HTTP requests and you run the risk of causing issues on devices with poor network performance.
Alternatively, a push rather than a pull model may be better here - This scenario is exactly what WebSockets are designed for and it wouldn't require too much modification to your code.

Can I call APIs in componentWillMount in React?

I'm working on react for last 1 year. The convention which we follow is make an API call in componentDidMount, fetch the data and setState after the data has come. This will ensure that the component has mounted and setting state will cause a re-render the component but I want to know why we can't setState in componentWillMount or constructor
The official documentation says that :
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method.
it says setting state in this method will not trigger a re-rendering, which I don't want while making an API call. If I'm able to get the data and able to set in the state (assuming API calls are really fast) in componentWillMount or in constructor and data is present in the first render, why would I want a re-render at all?
and if the API call is slow, then setState will be async and componentWillMount has already returned then I'll be able to setState and a re-render should occur.
As a whole, I'm pretty much confused why we shouldn't make API calls in constructor or componentWillMount. Can somebody really help me understand how react works in such case?
1. componentWillMount and re-rendering
Compare this two componentWillMount methods.
One causes additional re-render, one does not
componentWillMount () {
// This will not cause additional re-render
this.setState({ name: 'Andrej '});
}
componentWillMount () {
fetch('http://whatever/profile').then(() => {
// This in the other hand will cause additional rerender,
// since fetch is async and state is set after request completes.
this.setState({ name: 'Andrej '});
})
}
.
.
.
2. Where to invoke API calls?
componentWillMount () {
// Is triggered on server and on client as well.
// Server won't wait for completion though, nor will be able to trigger re-render
// for client.
fetch('...')
}
componentDidMount () {
// Is triggered on client, but never on server.
// This is a good place to invoke API calls.
fetch('...')
}
If you are rendering on server and your component does need data for rendering, you should fetch (and wait for completion) outside of component and pass data thru props and render component to string afterwards.
ComponentWillMount
Now that the props and state are set, we finally enter the realm of Life Cycle methods
That means React expects state to be available as render function will be called next and code can break if any mentioned state variable is missing which may occur in case of ajax.
Constructor
This is the place where you define.
So Calling an ajax will not update the values of any state as ajax is async and constructor will not wait for response. Ideally, you should use constructor to set default/initial values.
Ideally these functions should be pure function, only depending on parameters. Bringing ajax brings side effect to function.
Yes, functions depend on state and using this.setState can bring you such issues (You have set value in state but value is missing in state in next called function).
This makes code fragile. If your API is really fast, you can pass this value as an argument and in your component, check if this arg is available. If yes, initialise you state with it. If not, set it to default. Also, in success function of ajax, you can check for ref of this component. If it exist, component is rendered and you can call its state using setState or any setter(preferred) function.
Also remember, when you say API calls are really fast, your server and processing may be at optimum speed, but you can never be sure with network.
If you need just data only at first run and if you are ok with that. You can setState synchronously via calling a callback.
for eg:
componentWillMount(){
this.setState({
sessionId: sessionId,
}, () => {
if (this.state.hasMoreItems = true) {
this.loadItems() // do what you like here synchronously
}
});
}

Call componentDidMount when API responds

In my project I have a call to an action that makes a webservice call and in turn dispatch actions to the result of the ws, these actions edit the store.
My problem is in :
ComponentDidUpdate () {
If (this.props.messages.length) {
Const items = this.props.messages.filter (this.isDisplayable);
This.timer = setInterval (() => {
If (items.length> 0) {
This.props.popItem (items);
} Else {
ClearInterval (this.timer);
}
}, This.props.interval);
}
}
In fact it is launched several times and I have warnings of
Warning: flattenChildren (...): Encountered two children with the same
key, 1. Child keys must be unique; When two children share a key,
only the first child will be used.
I used the componentDidMount but it launches it before api responds.
my question is:
Is that there is a way to update the component only at the response of my action, or alternatively to pass the warnings ?
try this :
componentWillReceiveProps(nextProps) {
if (this.props.messages === nextProps.messages) return;
i had some probleme and i resolve it by force update
forceUpdate () {
If (this.props.messages.length) {
...
}
}
In my project I have a call to an action that makes a webservice call and in turn dispatch actions to the result of the ws, these actions edit the store.
None of the methods componentDidMount and componentDidUpdate are good.
Observe the Store in Redux and update your component accordingly when the correct action TYPE is found.
Since you are using the Redux architecture, the state for all your components is in a single place — in the Store.
yes i know, but the problem is that componentDidUpdate is called several times which gives me the index error.
This is quite normal in React. Check this lifecycle.
What you should do is the govern the Redux architecture.
I will try today to provide some diagrams for you.
In general, anything you do will be from the global Store.
You may forget the React.Component state, and props you had in the non-Redux applications.
You typically need to use the Wrapper as a context provider around your app, where the context is the property of React.Component.
The context will be passed to all children and grandchildren so this will be the global Store organization.
Then you will need to read the Store from the context, and call the two typical methods: dispatch and subscribe.

Categories

Resources