I'm getting started with using Redux, and currently have a working use of action creators, reducers etc. My container is set up to mapStateToProps, and everything is showing up on our pages as expected.
One thing I was hoping for advice for was where to actually dispatch the fetch action creator. The data is retrieved via an API call and gets formatted and displayed on the page. Right now I have the dispatch in the constructor:
constructor(props) {
super(props);
...
props.dispatch(fetchInfoBlah(someParamsFromRouter))
}
This works, but I just put it there as a guess since most examples I've seen online trigger fetches as a result of some trigger/event, rather than just the fact that the component needs to load. I was previously making the API call from the componentDidMount().
Any thoughts?
Thanks in advance!
When I need to fetch data asap I always use componentDidMount().
Mainly because I bookmarked this article which I've been referencing quite a bit and it's seemed to work out for me so far!
https://engineering.musefind.com/react-lifecycle-methods-how-and-when-to-use-them-2111a1b692b1
It totally depends on the situation.
If you need to display data/info when component first loaded, call it from constructor.
If you need to display/update data/info when user interact with your app, call it according to user actions.
componentDidMount() vs constructor()
componentDidMount() runs after render(). So componentDidMount is used when you want to make sure its run after component is rendered once. This helps reminding to set initial state data otherwise there's no data for render()
constructor() is the same as componentWillMount() and runs before render(). When calling inside constructor, the fetch response is available either before or after render()
Related
I have a (composition) store that is used in s couple of modules, they are unaware of each other and can be initialized in the same moment as well as at different times.
The store retrieves data from an API and saves it in the state. All of the modules await the execute store's fetchData method, all of them await a promise it returns.
The problem is that when the modules are initialized at the same moment, both perform fetchData resulting in two requests fired. The ideal situation would be to
Allow only one request being fired (that's pretty easy to do, just save the request state in a ref, f.e. "pending")
Export the state as a promise, so that the module awaits the state to be loaded from an API, receiving it's data only thereafter.
Those with a requirement that the modules are unaware of the store implementation - the less code on their side the better.
How do you handle such cases?
Is a there anything wrong with this approach? If not - how to implement it?
Is there a recommended way of how to propagate data through your vue components? What I'm trying to do is get the data from backend once and propagate it everywhere in my project but I can't find the right strategy.
sessionStorage: Works great and resets on refresh/close window but as soon as you need to create target="_blank" anchor tags, it will not propagate your data to new tabs.
localStorage: Requires in my opinion more work than sessionStorage because you need to delete data manually to keep things tidy. One big problem for me is that it looks like you can't pass markdown and arrays properly, at least without stringify. I've built a project with localStorage and ended up sending ajax requests from most of my components because I couldn't propagate the data through my app how I wanted. At this point my frontend is backend.
My personal problems with localStorage: I am using the marked package to display Markdown but it throws errors if passed undefined. This gets problematic, when I want to use it in a reactive state because instead of resulting in undefined, it throws an error and crashes the whole app. The point I am trying to make is that when you pass an undefined localStorage value to marked in an either or field like so:
const state = reactive({
value: marked(localStorage.value) || ""
})
it crashes your app, if localStorage.value is empty.
Another problem is that I fetch text content depending on a locale and store it in localStorage. This is great until the user changes locale and all content strings have to be replaced by the translated strings. It gets really tricky, if I want to use one component as template to load in different locales.
vuex: I've tried vuex shortly and found it useful but didn't see the benefit over just using localStorage for my purposes. Prolly I'll give it another go.
How do you propagate data through your app?
There are a few good arguments why Vuex is better than Local Storage:
https://www.quora.com/What-is-the-benefit-of-using-Vuex-over-LocalStorage-to-store-the-state-of-an-application-in-Vue-js
You can also try composables. They are reusable functions (similar to mixins) in composition-api (you need composition-api plugin in vue2, in vue3 it is built-in). It can be also the place you store your data. It can be easier and more intuitive than Vuex.
First, create directory /composables and add javascript file (it's
good practice to create file beginning with use word) useState.js:
import { reactive, toRefs } from "vue";
const state = reactive({
isMenuOpened: false
});
const toggleMenuState = () => {
state.isMenuOpened = !state.isMenuOpened;
};
export default {
...toRefs(state),
toggleMenuState
};
toRefs converts all of the properties, to a plain object with
properties that are refs
Now you can use composable in vue components:
<script>
import useState from "./composables/useState";
export default {
setup() {
const { isMenuOpened, toggleMenuState } = useState;
return {
isMenuOpened,
toggleMenuState,
};
},
};
</script>
Demo:
https://codesandbox.io/s/happy-chandrasekhar-o05uv?file=/src/App.vue
About composition api and composables:
https://v3.vuejs.org/guide/composition-api-introduction.html
Since you have mentioned local storage and session storage, I believe you must be trying to share your state across tabs/windows rather than just different components on a single page. At this scale I don't think this is necessarily a VueJS specific issue/pattern. Generally speaking, you want data to be shared pretty much across process boundaries.
Session Storage used to be one of the most sensible ways because it is shared between one window and all the child windows it has created, until all of them are closed at which point the storage will be discarded as well. However, depending on your use cases, Chrome (within the past year) made a change to NOT inherit the session storage from the original window if the popup windows is opened as noopener, hence if you are relying on the noopener (and its performance implications), session storage is no longer usable for this purpose.
Vuex does not solve this issue neither. In fact, Vuex is pretty much irrelevant here. Given the application architecture implied, the state management capability Vuex brings to your app will be likely redundant because any state mutation will probably be submitted to your backend anyway. In some sense the Vuex store is on your backend rather than your frontend.
So we typically do one of the three approaches:
directly broadcast from backend to all frontend tabs. E.g. there is no state sync-ing directly between frontend tabs. Every single tab (child window) communicates directly with the server: it mutates the state by submitting actions to the server, and only the server can change the state and broadcast the changes back to all the tabs in real time (again, conceptually it feels like the Vuex store is on your backend)
use SharedWorker. SharedWorker is shared by all the browsing context with the same origin. It is activated the moment the first browsing context (of a certain origin) is created, and is kept alive until the last browsing context is destroyed. In some sense its sharing semantic is similar to that of the old session storage. You can use SharedWorker as the single entity to communicate to your backend. States can be maintained by the SharedWorker and accessed from the tabs in a RPC fashion. Or states can be maintained separately in each tab and SharedWorker just broadcast the changes to the tabs.
if you actually do not have a backend, but you just want to build multi-window single page application, you can make one of your tabs special and act as the owner of the state store. For all the child windows created from this "master" window, their local store will be a proxy - the actions they perform against the local store will be proxied over to the master window; and the master window performs the action in its store, and broadcast the changes to all the child windows.
By the way, I have used the word "store" many times, but I do not necessary mean the Vuex store. The store is just a shared place where you keep your state.
My code
https://gist.github.com/ButuzGOL/707d1605f63eef55e4af
So when I get sign-in success callback I want to make redirect,
redirect works through dispatcher too.
And I am getting Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.
Is there any hack to call action in the middle ?
I don't see where in the gist that you posted you are doing the redirect. I only see the AUTH_SIGNIN and AUTH_SIGNIN_SUCCESS actions, and they look pretty straightforward.
But no, there is no hack to create an action in the middle of a dispatch, and this is by design. Actions are not supposed to be things that cause a change. They are supposed to be like a newspaper that informs the application of a change in the outside world, and then the application responds to that news. The stores cause changes in themselves. Actions just inform them.
If you have this error, then you need to back up and look at how you're handling the original action. Most often, you can set up your application to respond to the original action, accomplish everything you need to do, and avoid trying to create a second action.
You can make it work by "scheduling" the next action instead of calling it directly, here is an example code:
// instead of doing this
Dispatcher.dispatch(...);
// go like this
setTimeout(function() {
Dispatcher.dispatch(...);
}, 1);
This will cause your next dispatch to be called later out of the current dispatch process, and no error will happen.
If your dispatch code is on a callback any kind of other async operation that will work as well (for example in a response for an Ajax request).
I'm using this style to make some forms respond to generic data here and I'm facing no issue, at least the way I'm using it.
you can user the "defer" option in the dispatcher.
In your case it would be like:
Dispatcher.dispatch.defer(...);
You can check if the dispatcher is dispatching, such as:
if(!MyDispatcher.isDispatching()) {
MyDispatcher.dispatch({...});
}
Say I have a TodoStore. The TodoStore is responsible for keeping my TODO items. Todo items are stored in a database.
I want to know what is the recommended way for loading all todo items into the store and how the views should interact with the store to load the TODO items on startup.
The first alternative is to create a loadTodos action that will retrieve the Todos from the database and emit a TODOS_LOADED event. Views will then call the loadTodos action and then listen to the TODOS_LOADED event and then update themselves by calling TodoStore.getTodos().
Another alternative is to not have a loadTodos action, and have a TodoStore.getTodos() that will return a promise with the existing TODO items. If the TodoStore has already loaded the TODO items, it just returns them; if not, then it will query from the database and return the retrieved items. In this case, even though the store now has loaded the TODO items, it will not emit a TODOS_LOADED event, since getTodos isn't an action.
function getTodos() {
if (loaded)
return Promise.resolve($todoItems);
else
return fetchTodoItemsFromDatabase().then(todoItems) {
loaded = true;
$todoItems = todoItems;
return $todoItems;
});
}
I'm sure many will say that that breaks the Flux architecture because the getTodos function is changing the store state, and store state should only be changed though actions sent in from the dispatcher.
However, if you consider that state for the TodoStore is the existing TODO items in the database, then getTodos isn't really changing any state. The TODO items are exactly the same, hence no view need to be updated or notified. The only thing is that now the store has already retrieved the data, so it is now cached in the store. From the View's perspective, it shouldn't really care about how the Store is implemented. It shouldn't really care if the store still needs to retrieve data from the database or not. All views care about is that they can use the Store to get the TODO items and that the Store will notify them when new TODO items are created, deleted, or changed.
Hence, in this scenario, views should just call TodoStore.getTodos() to render themselves on load, and register an event handler on TODO_CHANGE to be notified when they need to update themselves due to a addition, deletion, or change.
What do you think about these two solutions. Are they any other solutions?
The views do not have to be the entities that call loadTodos(). This can happen in a bootstrap file.
You're correct that you should try your best to restrict the data flow to actions inside the dispatch payload. Sometimes you need to derive data based on the state of other stores, and this is what Dispatcher.waitFor() is for.
What is Flux-like about your fetchTodoItemsFromDatabase() solution is that no other entity is setting data on the store. The store is updating itself. This is good.
My only serious criticism of this solution is that it could result in a delay in rendering if you are actually getting the initial data from the server. Ideally, you would send down some data with the HTML. You would also want to make sure to call for the stores' data within your controller-views' getInitialState() method.
Here is my opinion about that, very close to yours.
I maintain the state of my application in Store via Immutable.Record and Immutable.OrderedMap from Immutable.js
I have a top controller-view component that get its state from the Store.
Something such as the following :
function getInitialState() {
return {
todos: TodoStore.getAll()
}
}
TodoStore.getAll methods will retrieve the data from the server via a APIUtils.getTodos() request if it's internal _todos map is empty. I advocate for read data triggered in Store and write data triggered in ActionCreators.
By the time the request is processing, my component will render a simple loading spinner or something like that
When the request resolves, APIUtils trigger an action such as TODO_LIST_RECEIVE_SUCCESS or TODO_LIVE_RECEIVE_FAIL depending on the status of the response
My TodoStore will responds to these action by updating its internal state (populating it's internal Immutable.OrderedMap with Immutable.Record created from action payloads.
If you want to see an example through a basic implementation, take a look to this answer about React/Flux and xhr/routing/caching .
I know it's been a couple of years since this was asked, but it perfectly summed up the questions I am struggling with this week. So to help any others that may come across this question, I found this blog post that really helped me out by Nick Klepinger: "ngrx and Tour of Heroes".
It is specifically using Angular 2 and #ngrx/store, but answers your question very well.
I'm a bit new to iron-router but I'm curious if there's an event handler I can define for after a route is loaded. If I attempt to call Router.current().data() in Meteor.startup, I get 'undefined' for Router.current(). I'd like to know the 'right' way to start doing things once the data is loaded in that route...
The code you're using is fine - Router.current().data().
The issue is that there is a race condition here. There are two things going on here, Meteor.startup may run before or after the router has decided what route its on. If it runs before the route has loaded, Router.current() would be null.
You might want to consider putting your code in your route's onAfterAction instead. This way it will also run on the correct page too. Router.current().data() would give back the wrong data on a different route.
Another thing to keep in mind is with Meteor you download the html, js and css first & have it load. Then the data comes after, so when you're looking for data when the page loads you have to wait for it first, otherwise you wont have anything.
Iron Router also has a hook called onData which reactively reruns when the corresponding route's data() changes. You can use this to ensure you have the data available and have it run after everything has loaded properly.