X Tutup
Skip to content

Commit a84688d

Browse files
Ensure data is only loaded if not already loaded (needed to keep client/server state consistent)
1 parent 3a56782 commit a84688d

File tree

9 files changed

+69
-58
lines changed

9 files changed

+69
-58
lines changed

samples/react/MusicStore/ReactApp/components/NavMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class NavMenu extends React.Component<NavMenuProps, void> {
1212
}
1313

1414
public render() {
15-
var genres = this.props.genres.slice(0, 5);
15+
const genres = this.props.genres.slice(0, 5);
1616
return (
1717
<Navbar inverse fixedTop>
1818
<Navbar.Header>

samples/react/MusicStore/ReactApp/components/public/AlbumDetails.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@ import { ApplicationState } from '../../store';
55
import * as AlbumDetailsState from '../../store/AlbumDetails';
66

77
interface RouteParams {
8-
albumId: number;
8+
albumId: string;
99
}
1010

1111
class AlbumDetails extends React.Component<AlbumDetailsProps, void> {
1212
componentWillMount() {
13-
this.props.requestAlbumDetails(this.props.params.albumId);
13+
this.props.requestAlbumDetails(parseInt(this.props.params.albumId));
1414
}
1515

1616
componentWillReceiveProps(nextProps: AlbumDetailsProps) {
17-
if (nextProps.params.albumId !== this.props.params.albumId) {
18-
nextProps.requestAlbumDetails(nextProps.params.albumId);
19-
}
17+
this.props.requestAlbumDetails(parseInt(nextProps.params.albumId));
2018
}
2119

2220
public render() {
23-
if (this.props.isLoaded) {
21+
if (this.props.album) {
2422
const albumData = this.props.album;
2523
return <div>
2624
<h2>{ albumData.Title }</h2>

samples/react/MusicStore/ReactApp/components/public/GenreDetails.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,25 @@ import * as GenreDetailsStore from '../../store/GenreDetails';
66
import { AlbumTile } from './AlbumTile';
77

88
interface RouteParams {
9-
genreId: number
9+
genreId: string
1010
}
1111

1212
class GenreDetails extends React.Component<GenreDetailsProps, void> {
1313
componentWillMount() {
14-
this.props.requestGenreDetails(this.props.params.genreId);
14+
this.props.requestGenreDetails(parseInt(this.props.params.genreId));
1515
}
1616

1717
componentWillReceiveProps(nextProps: GenreDetailsProps) {
18-
if (nextProps.params.genreId !== this.props.params.genreId) {
19-
nextProps.requestGenreDetails(nextProps.params.genreId);
20-
}
18+
this.props.requestGenreDetails(parseInt(nextProps.params.genreId));
2119
}
2220

2321
public render() {
2422
if (this.props.isLoaded) {
25-
let albums = this.props.albums;
2623
return <div>
2724
<h3>Albums</h3>
2825

2926
<ul className="list-unstyled">
30-
{albums.map(album =>
27+
{this.props.albums.map(album =>
3128
<AlbumTile key={ album.AlbumId } album={ album } />
3229
)}
3330
</ul>

samples/react/MusicStore/ReactApp/components/public/Genres.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@ import * as GenreList from '../../store/GenreList';
66

77
class Genres extends React.Component<GenresProps, void> {
88
componentWillMount() {
9-
if (!this.props.genres.length) {
10-
this.props.requestGenresList();
11-
}
9+
this.props.requestGenresList();
1210
}
1311

1412
public render() {
15-
let { genres } = this.props;
16-
13+
const { genres } = this.props;
1714
return <div>
1815
<h3>Browse Genres</h3>
1916

20-
<p>Select from { genres.length || '...' } genres:</p>
17+
<p>Select from { this.props.isLoaded ? genres.length : '...' } genres:</p>
2118

2219
<ul className="list-group">
2320
{genres.map(genre =>

samples/react/MusicStore/ReactApp/components/public/Home.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@ import { AlbumTile } from './AlbumTile';
77

88
class Home extends React.Component<HomeProps, void> {
99
componentWillMount() {
10-
if (!this.props.albums.length) {
11-
this.props.requestFeaturedAlbums();
12-
}
10+
this.props.requestFeaturedAlbums();
1311
}
1412

1513
public render() {
16-
let { albums } = this.props;
1714
return <div>
1815
<div className="jumbotron">
1916
<h1>MVC Music Store</h1>
2017
<img src="/Images/home-showcase.png" />
2118
</div>
2219
<ul className="row list-unstyled" id="album-list">
23-
{albums.map(album =>
20+
{this.props.albums.map(album =>
2421
<AlbumTile key={ album.AlbumId } album={ album } />
2522
)}
2623
</ul>

samples/react/MusicStore/ReactApp/store/AlbumDetails.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { Genre } from './GenreList';
77
// STATE - This defines the type of data maintained in the Redux store.
88

99
export interface AlbumDetailsState {
10-
isLoaded: boolean;
1110
album: AlbumDetails;
11+
requestedAlbumId: number;
1212
}
1313

1414
export interface AlbumDetails {
15-
AlbumId: string;
15+
AlbumId: number;
1616
Title: string;
1717
AlbumArtUrl: string;
1818
Genre: Genre;
@@ -49,23 +49,31 @@ class ReceiveAlbumDetails extends Action {
4949

5050
export const actionCreators = {
5151
requestAlbumDetails: (albumId: number): ActionCreator => (dispatch, getState) => {
52-
fetch(`/api/albums/${ albumId }`)
53-
.then(results => results.json())
54-
.then(album => dispatch(new ReceiveAlbumDetails(album)));
55-
56-
dispatch(new RequestAlbumDetails(albumId));
52+
// Only load if it's not already loaded (or currently being loaded)
53+
if (albumId !== getState().albumDetails.requestedAlbumId) {
54+
fetch(`/api/albums/${ albumId }`)
55+
.then(results => results.json())
56+
.then(album => {
57+
// Only replace state if it's still the most recent request
58+
if (albumId === getState().albumDetails.requestedAlbumId) {
59+
dispatch(new ReceiveAlbumDetails(album));
60+
}
61+
});
62+
63+
dispatch(new RequestAlbumDetails(albumId));
64+
}
5765
}
5866
};
5967

6068
// ----------------
6169
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
6270
// For unrecognized actions, must return the existing state (or default initial state if none was supplied).
63-
const unloadedState: AlbumDetailsState = { isLoaded: false, album: null };
71+
const unloadedState: AlbumDetailsState = { requestedAlbumId: null as number, album: null };
6472
export const reducer: Reducer<AlbumDetailsState> = (state, action) => {
6573
if (isActionType(action, RequestAlbumDetails)) {
66-
return unloadedState;
74+
return { requestedAlbumId: action.albumId, album: null };
6775
} else if (isActionType(action, ReceiveAlbumDetails)) {
68-
return { isLoaded: true, album: action.album };
76+
return { requestedAlbumId: action.album.AlbumId, album: action.album };
6977
} else {
7078
return state || unloadedState;
7179
}

samples/react/MusicStore/ReactApp/store/FeaturedAlbums.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ActionCreator } from './';
77

88
export interface FeaturedAlbumsState {
99
albums: Album[];
10+
isLoaded: boolean;
1011
}
1112

1213
export interface Album {
@@ -37,11 +38,13 @@ class ReceiveFeaturedAlbums extends Action {
3738

3839
export const actionCreators = {
3940
requestFeaturedAlbums: (): ActionCreator => (dispatch, getState) => {
40-
fetch('/api/albums/mostPopular')
41-
.then(results => results.json())
42-
.then(albums => dispatch(new ReceiveFeaturedAlbums(albums)));
43-
44-
return dispatch(new RequestFeaturedAlbums());
41+
if (!getState().featuredAlbums.isLoaded) {
42+
fetch('/api/albums/mostPopular')
43+
.then(results => results.json())
44+
.then(albums => dispatch(new ReceiveFeaturedAlbums(albums)));
45+
46+
return dispatch(new RequestFeaturedAlbums());
47+
}
4548
}
4649
};
4750

@@ -51,8 +54,8 @@ export const actionCreators = {
5154

5255
export const reducer: Reducer<FeaturedAlbumsState> = (state, action) => {
5356
if (isActionType(action, ReceiveFeaturedAlbums)) {
54-
return { albums: action.albums };
57+
return { albums: action.albums, isLoaded: true };
5558
} else {
56-
return state || { albums: [] };
59+
return state || { albums: [], isLoaded: false };
5760
}
5861
};

samples/react/MusicStore/ReactApp/store/GenreDetails.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { Album } from './FeaturedAlbums';
77
// STATE - This defines the type of data maintained in the Redux store.
88

99
export interface GenreDetailsState {
10-
isLoaded: boolean;
10+
requestedGenreId: number;
1111
albums: Album[];
12+
isLoaded: boolean;
1213
}
1314

1415
// -----------------
@@ -25,7 +26,7 @@ class RequestGenreDetails extends Action {
2526

2627
@typeName("RECEIVE_GENRE_DETAILS")
2728
class ReceiveGenreDetails extends Action {
28-
constructor(public albums: Album[]) {
29+
constructor(public genreId: number, public albums: Album[]) {
2930
super();
3031
}
3132
}
@@ -36,24 +37,31 @@ class ReceiveGenreDetails extends Action {
3637

3738
export const actionCreators = {
3839
requestGenreDetails: (genreId: number): ActionCreator => (dispatch, getState) => {
39-
fetch(`/api/genres/${ genreId }/albums`)
40-
.then(results => results.json())
41-
.then(albums => dispatch(new ReceiveGenreDetails(albums)));
42-
43-
dispatch(new RequestGenreDetails(genreId));
40+
// Only load if it's not already loaded (or currently being loaded)
41+
if (genreId !== getState().genreDetails.requestedGenreId) {
42+
fetch(`/api/genres/${ genreId }/albums`)
43+
.then(results => results.json())
44+
.then(albums => {
45+
// Only replace state if it's still the most recent request
46+
if (genreId === getState().genreDetails.requestedGenreId) {
47+
dispatch(new ReceiveGenreDetails(genreId, albums));
48+
}
49+
});
50+
51+
dispatch(new RequestGenreDetails(genreId));
52+
}
4453
}
4554
};
4655

4756
// ----------------
4857
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
4958
// For unrecognized actions, must return the existing state (or default initial state if none was supplied).
50-
const unloadedState: GenreDetailsState = { isLoaded: false, albums: [] };
5159
export const reducer: Reducer<GenreDetailsState> = (state, action) => {
5260
if (isActionType(action, RequestGenreDetails)) {
53-
return unloadedState;
61+
return { requestedGenreId: action.genreId, albums: [], isLoaded: false };
5462
} else if (isActionType(action, ReceiveGenreDetails)) {
55-
return { isLoaded: true, albums: action.albums };
63+
return { requestedGenreId: action.genreId, albums: action.albums, isLoaded: true };
5664
} else {
57-
return state || unloadedState;
65+
return state || { requestedGenreId: null as number, albums: [], isLoaded: false };
5866
}
5967
};

samples/react/MusicStore/ReactApp/store/GenreList.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ActionCreator } from './';
77

88
export interface GenresListState {
99
genres: Genre[];
10+
isLoaded: boolean;
1011
}
1112

1213
export interface Genre {
@@ -32,9 +33,11 @@ class ReceiveGenresList extends Action {
3233

3334
export const actionCreators = {
3435
requestGenresList: (): ActionCreator => (dispatch, getState) => {
35-
fetch('/api/genres')
36-
.then(results => results.json())
37-
.then(genres => dispatch(new ReceiveGenresList(genres)));
36+
if (!getState().genreList.isLoaded) {
37+
fetch('/api/genres')
38+
.then(results => results.json())
39+
.then(genres => dispatch(new ReceiveGenresList(genres)));
40+
}
3841
}
3942
};
4043

@@ -44,8 +47,8 @@ export const actionCreators = {
4447

4548
export const reducer: Reducer<GenresListState> = (state, action) => {
4649
if (isActionType(action, ReceiveGenresList)) {
47-
return { genres: action.genres };
50+
return { genres: action.genres, isLoaded: true };
4851
} else {
49-
return state || { genres: [] };
52+
return state || { genres: [], isLoaded: false };
5053
}
5154
};

0 commit comments

Comments
 (0)
X Tutup