Best way to wait for another action in flux - javascript

I'm using react and flux (facebook implementation). In my component I should do something like:
componentDidMount () {
UserActions.loadUser(this.props.username);
PublicationActions.findByUser(this.state.userId);
UserStore.on('CHANGE', this._setUser);
// ...
},
The problem is this.state.userId will be defined only when request triggered by UserActions.loadUser is finished.
So the only way of resolving this problem is putting something like:
if (this.state.userId && !this.state.publications) {
PublicationActions.findByUser(this.state.userId);
}
directly to render() method. I don't like this solution cause render() is for rendering and I don't want to mess any business logic in it.
The only acceptable solution I have found is have special component hierarchy for this case like:
<User username={someVar}>
<PublicationsList userId={this.state.userId}/>
</User>
This will enforce PublicationsList to be smart component (with state and so) but I'd like it to be dumb renderer. Is this the only way or there's something better?

You need store which is listening on UserAction.loadUser finish.
You invoke this action.
In your component you register callback which listening your new store.
When your store invoke your callback, then you have in this callback function access to userId from this store. (Im not sure but maybe your UserStore do wiat I write).
In this callback function you invoke
PublicationActions.findByUser(YourStore.getState().userId)
In render function you check if you have userId. If no then show some loading info. If tez then show normal rending data.

You could expand your loadUser method to accept callback with payload as arguments.
ie:
loadUser(username, callback) {
// some ajax stuff
callback(payload);
}
Then, in your jsx file you could write something like this:
componentDidMount () {
UserActions.loadUser(this.props.username, (user) => {
PublicationActions.findByUser(user.userId);
});
UserStore.on('CHANGE', this._setUser);
// ...
},

Related

Vue3 call a components method from another component

I'm trying to have a component call a function from another component but just from
inside the setup.
I've read this from vuemastery https://www.vuemastery.com/blog/understanding-vue-3-expose/
and I see that you can accomplish this like this:
methods: {
reset () {
this.$refs.counter.reset()
},
terminate () {
this.$refs.counter.terminate()
}
}
however, I don't have access to those methods inside the setup, and I also can't use this.$refs inside the setup. Is there a way I can do the same thing inside setup, or a way to access these methods inside the setup?
Those methods are undefined in setup, and I cannot access data setup from within those functions, and I cannot use $refs in setup.
The $refs is a very easy way to call a function from another component - but I can't seem to find a relatively easy way to do this with vue3 composition api - am I missing something?
Hey I figured it out https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs
If I go
<script>
export default {
setup(props) {
const counter = ref(null);
//now I have access to counter.value.reset after it's mounted
counter.value.reset(); //does not work! it's null here
onMounted(() => {
counter.value.reset(); //here it works
})
//make sure to return counter in return
looks like there is an easy way to do this in setup just has a caveat
you cant just call it right away because setup happens before mounted https://vuejs.org/guide/essentials/lifecycle.html
I am calling it on a trigger - so if someone selects something from a dropdown I am watching the v-model and am able to call it there no problem.
Thanks for all the input.
Putting this answer here in case anyone else needs to accomplish the same thing

React Redux - quick flash of previous state before dispatch issue

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!
The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.
Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

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.

ReactJS - waiting for action to finish within componentDidMount

Code in question is below. I have an async UserActions call within componentDidMount, and immediately afterwards I am looking to user information from within the UserStore populated by this action. Clearly, I cannot rely upon UserStore.isLargeAccess() being defined. Is the best convention to place the code relying on the action within a callback, or am I missing some bigger design choice?
componentDidMount() {
this.changeListener = this._onChange.bind(this);
UserStore.addChangeListener(this.changeListener);
OrganizationStore.addChangeListener(this.changeListener);
// Fetch user info
UserActions.get(AuthStore.username);
// UserStore.isLargeAccess() is undefined here,
// because the previous action has not finished.
if (UserStore.isLargeAccess()) {
OrganizationActions.getUsers(this.state.organization);
}
if (UserStore.isGlobalAccess()) {
OrganizationActions.getOrganizations();
}
}
How it should to work (If I understand your flow):
You should register different callbacks for each of your stores (otherwise you don't know which store emit event)
You start some async work.
When async work is finished then it dispatch action with some data from async work
your stores UserStore and OrganizationStore listens for this action, and when they receive it, they do some job and emit change.
When they emit change, then they call callbacks from your component. In each callback you know which store invoke it, And therefore you know from what store get data.

How to avoid dispatching in the middle of a dispatch

Within my Flux architected React application I am retrieving data from a store, and would like to create an action to request that information if it does not exist. However I am running into an error where the dispatcher is already dispatching.
My desired code is something like:
getAll: function(options) {
options = options || {};
var key = JSON.stringify(options);
var ratings = _data.ratings[key];
if (!ratings) {
RatingActions.fetchAll(options);
}
return ratings || [];
}
However intermittently fails when the dispatcher is already dispatching an action, with the message Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.. I am often making requests in response to a change in application state (eg date range). My component where I make the request, in response to a change event from the AppStore has the following:
getStateFromStores: function() {
var dateOptions = {
startDate: AppStore.getStartISOString(),
endDate: AppStore.getEndISOString()
};
return {
ratings: RatingStore.getAll(dateOptions),
};
},
I am aware that event chaining is a Flux antipattern, but I am unsure what architecture is better for retrieving data when it does not yet exist. Currently I am using this terrible hack:
getAll: function(options) {
options = options || {};
var key = JSON.stringify(options);
var ratings = _data.ratings[key];
if (!ratings) {
setTimeout(function() {
if (!RatingActions.dispatcher.isDispatching()) {
RatingActions.fetchAll(options);
}
}, 0);
}
return ratings || [];
},
What would be a better architecture, that avoids event chaining or the dispatcher error? Is this really event chaining? I just want to change the data based on the parameters the application has set.
Thanks!
You can use Flux waitFor() function instead of a setTimeout
For example you have 2 stores registered to the same dispatcher and have one store waitFor the other store to process the action first then the one waiting can update after and dispatch the change event. See Flux docs example
My particular error was occurring because my stores emitted their change event during the action dispatch, while it was still cycling through the listeners. This meant any listeners (ie components) that then triggered an action due to a data change in the store would interrupt the dispatch. I fixed it by emitting the change event after the dispatch had completed.
So this:
this.emit(CHANGE_EVENT);
Became
var self = this;
setTimeout(function() { // Run after dispatcher has finished
self.emit(CHANGE_EVENT);
}, 0);
Still a little hacky (will probably rewrite so doesn't require a setTimeout). Open to solutions that address the architectural problem, rather than this implementation detail.
The reason you get a dispatch in the middle of a previous dispatch, is that your store dispatches an action (invokes an action creator) synchronously in the handler for another action. The dispatcher is technically dispatching until all its registered callbacks have been executed. So, if you dispatch a new action from either of the registered callbacks, you'll get that error.
However, if you do some async work, e.g. make an ajax request, you can still dispatch an action in the ajax callbacks, or the async callback generally. This works, because as soon as the async function has been invoked, it per definition immediately continues the execution of the function and puts the callback on the event queue.
As pointed out by Amida and in the comments of that answer, it's a matter of choice whether to make ajax requests from the store in response to an action, or whether to do it in the store. The key is that a store should only mutate its state in response to an action, not in an ajax/async callback.
In your particular case, this would be exemplified by something like this for your store's registered callback, if you prefer to make the ajax calls from the store:
onGetAll: function(options) {
// ...do some work
request(ajaxOptions) // example for some promise-based ajax lib
.then(function(data) {
getAllSuccessAction(data); // run after dispatch
})
.error(function(data) {
getAllFailedAction(data); // run after dispatch
});
// this will be immediately run during getAllAction dispatch
return this.state[options];
},
onGetAllSuccess: function(data) {
// update state or something and then trigger change event, or whatever
},
onGetAllFailed: function(data) {
// handle failure somehow
}
Or you can just put the ajax call in your action creator and dispatch the "success/failed" actions from there.
you can user the "defer" option in the dispatcher.
In your case it would be like:
RatingActions.fetchAll.defer(options);
In my case, I fetch data through the actions/actions creators. The store is only a dump place that receives the payload of an action.
This means that I would "fetchall" in an action and then pass the result to the store which will do whatever with it and then emit a change event.
Some people consider using stores like me, others think like you.
Some people at Facebook uses "my" approach:
https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51
I think it would probably avoid the dispatch problem treating your stores like this, but I may be wrong.
An interesting discussion is this one: https://groups.google.com/forum/#!topic/reactjs/jBPHH4Q-8Sc
where Jing Chen (Facebook engineer) explains what she thinks about how to use stores.

Categories

Resources