The problem:
VueJS is briefly showing the "else" condition before the "if" condition loads. This is causing an unintended experience for the user because they should never see the "else" when the "if" is truthy. They should only see the "if" if the "if" is true.
The detail:
I have a custom online store and many different markets. I get back an item count from my server and if that number is zero then I say there are no items for that country, in other words the country is not open yet for this store. If the number is not zero then don't show the message and actually show the items.
Here is a slimmed down version of my code:
HTML
<div v-if="count !== 0">Items are in the store, here they are!</div>
<div v-else>Sorry no items in the store.</div>
JS
mounted() {
// Checks the location and gets items from server.
if (this.location) {
this.getItemsForHomeView();
}
}
I found this: Understanding Vue.js Lifecycle Hooks but it didn't help because when I tried moving the function out of the mounted() and into any of the earlier ones, like beforeCreate() or created() or beforeMount() I get the same result.
In addition to handling an empty item count once the data has resolved, you need to handle the case of there being no data yet to count. You didn't mention using Vue Router, but this discussion of handling async data still applies: you can either have the async fetch prevent the component from rendering at all, using a beforeMount or beforeRouteEnter hook, or wrap all the item list markup in another conditional, which is only rendered after your data has been fetched, and something like isLoaded (local state) is set to true.
Related
Im using redux-thunk for API calling. While response coming back from the server, I catch them at componentDidUpdate with the simple validation as follow
async componentDidUpdate(prevProps) {
if (this.props.successCalling && !prevProps.successCalling) {
executeSomeCode();
}
}
The problem is, I'm having 2 same validation at 2 different screens, when responses are back from server componentDidUpdate both the validations are met. Hence it run both the executeSomeCode() at both different screens. How am I suppose to handle this kind of scenario?
While calling the service for a particular screen you can use a class level variable which can act as a switch like - true/ false, where this.switch will be set to false initially. on calling the service just update that class variable to true and add that variable to the if logic check as mentioned in the above example.
This way every screen will have its own switch which can be used to determine if the service call is for that particular screen or not.
I have dropdown filter to show items by date, for example and show data for last 24 hours, show data for last 3 days.
I have defaultState in my reducer
const defaultState = {
dataArray: [],
a: true,
b: false
}
By default dataArray is empty.
And I have reducer and action. In componentDidMount method I fetch data from server by dispatching some actions.
If I refresh page, default page that list last items for 24 hours is empty because dataArry comes from defaultState in my reducer. But if I change page to list data for last 3 days then componentWillReceiveProps works and inside this method I fetch data and it reduce my state and returns new one with
dataArray = [{some data}]
How to fetch data and set it to state to render it after page was refreshed?
Add to defaultState a 'loading' variable and initialize it to true. When fetching of data is completed, set it to false.
In your component check this variable. If it is true display a spinner and/or a loading message, otherwise display the data.
In addition to that, every time you start fetching, before fetching, fire an action called 'FETCH_START' which will set the loading variable to true.
If there is an error in fetching, you can set another state variable to the error message. This variable will be initialized (every time you start fetching) to an empty string. If loading is completed you can check this error variable, and display the error message if there was an error, instead of displaying the data.
This process is useful for various cases, such as authentication, etc.
Yossi's answer is pretty spot on. I just want to take it a step further and mention that you can also enhance your users' experience by knowning when to fetch data. For instance, if you're on a landing page and know most of your users are going to be browsing your store, start fetching that data on the landing page while nothing is happening.
To go along with that, take a look at this: Google Dev's requestIdleCallback()
I am working on a simple React.JS frontend piece.
I essentially have a browsing SPA for historical data. The design has a bunch of filters that I need to populate one at a time, starting the top one in my logic hierarchy.
I do something like:
componentWillMount() {
if (!this.props.advertisers) {
this.props.loadAdvertisers();
}
}
Once advertisers array has been loaded, I map it to options of a select component (using react-select), set the selection to the first item in the list and load the next list - campaigns.
As far as I understand, the best way to do this is still componentWillReceiveProps() and I am a little perplexed how this should be done differently, given that componentWillReceiveProps is being phased out.
My code looks like:
componentWillReceiveProps(nextProps) {
if (nextProps.advertisers && !this.props.advertisers) {
const advertiser = nextProps.advertisers[0];
this.setState({
advertiser: {
value: advertiser['id'],
label: advertiser['name']
}
});
nextProps.loadCampaigns(advertiser['id']);
}
// if campaigns list changed, reload the next filtered array
if (nextProps.campaigns && this.props.campaigns !== nextProps.campaigns) {
...
}
This worked fine, until I decided to add a loading indicator. I mapped state's loading property e.g. for campaigns it gets exposed via this.props.campaignsLoading then do:
return (this.props.campaignsLoading || this.props...Loading || ...) ?
<ProgressIndicator> : <MainContentPanel>
The problem is now, my state does not get set correctly inside componentWillReceiveProps().
The project is using #rematch and I initially tried this with #rematch/loading plugin and when the problem happened, thought the plugin does it wrong, somehow. Then, I mapped loading properties manually, and just added two more dispatches to manually set the loading flag.
All the props are being set/unset correctly, but my state is not being set and nothing works. Any suggestions?
When you do
if (nextProps.advertisers && !this.props.advertisers) {
You are not comparing the next and the previous props. "this.props.advertisers" is probably already set so you never go into the setState line. Although using componentWillReceiveProps is no longer the recommended way to go (You Probably Don't Need Derived State), what you probably want to do roughly is:
if (nextProps.advertisers && nextProps.advertisers !== !this.props.advertisers) {
Scenario
2 Container components rendered
Each container is subscribed to the "Basket" piece of state. Within their componentDidUpdate they should go and re-fetch data from the pricing API and display the results ( i.e running totals with offers etc )
Image of multiple containers all trying to fetch data # same time
Issue
The issue is that a race condition will happen. When basket updates the flow looks something like this
Component1 compenentDidUpdate is triggered due to basket update
Check if pricingDetails.isLoading is false, if not fetch data
Dispatch fetchPricingData() - this will set pricingDetails.isLoading to false
Component2 componentDidUpdate is triggered
Check if pricingDetails.isLoading is false ( IT is still is false at this point, as Component2 subscription update to the isLoading state hasn't happened quick enough )
Perform a second call to fetchPricingData - meaning now we've got 2 API calls on the go!
Solution?
Currently there seems a couple of ways I can think to tackle this, but none particularly elegantly
1) Delegate responsibility of fetching data to a higher level container where only a single instance of it will ever exist.
2) Have each container somehow use a "slave/master" setup, where only 1 initial instance is allowed to fetch data.
I want to map a state, but that state (the name of the state is "task", it is the child of parent state called "timesheet", the "timesheet" state is already defined using componentWillMount) is still undefined UNTIL an action creator has been triggered (click a button) inside my page. But, i already define timesheet.task.map inside jsx, and thus the page cannot load and it shows an error "cannot read property map of undefined".
code
So as you can see here, i want to map a list of 'select' options from a state which is called when an event is triggered (it is still undefined when the page renders), but the page will not load because the timesheet.task is still undefined when the page load the first time
Do you have any idea how to fix this? I was thinking to initialize the state, but i don't know how to do it.Thank you very much!
Very common situation. At the top of your render() - just add a conditional to check if that value is valid or not.. something like:
if (!this.state.timesheet.task) {
return <p> waiting for my value </p> // add any generic component to here that would await your value
}
Depending on your page layout, you might want to just create a small component to inject as a conditional as you await your value to be populated.