mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-25 19:17:30 +00:00
Fix templates directory structure to produce correct nupkg output
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import './css/site.css';
|
||||
import 'bootstrap';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'react-router-redux';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import configureStore from './configureStore';
|
||||
import { ApplicationState } from './store';
|
||||
import * as RoutesModule from './routes';
|
||||
let routes = RoutesModule.routes;
|
||||
|
||||
// Create browser history to use in the Redux store
|
||||
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
|
||||
const history = createBrowserHistory({ basename: baseUrl });
|
||||
|
||||
// Get the application-wide store instance, prepopulating with state from the server where available.
|
||||
const initialState = (window as any).initialReduxState as ApplicationState;
|
||||
const store = configureStore(history, initialState);
|
||||
|
||||
function renderApp() {
|
||||
// This code starts up the React app when it runs in a browser. It sets up the routing configuration
|
||||
// and injects the app into a DOM element.
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Provider store={ store }>
|
||||
<ConnectedRouter history={ history } children={ routes } />
|
||||
</Provider>
|
||||
</AppContainer>,
|
||||
document.getElementById('react-app')
|
||||
);
|
||||
}
|
||||
|
||||
renderApp();
|
||||
|
||||
// Allow Hot Module Replacement
|
||||
if (module.hot) {
|
||||
module.hot.accept('./routes', () => {
|
||||
routes = require<typeof RoutesModule>('./routes').routes;
|
||||
renderApp();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { StaticRouter } from 'react-router-dom';
|
||||
import { replace } from 'react-router-redux';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
|
||||
import { routes } from './routes';
|
||||
import configureStore from './configureStore';
|
||||
|
||||
export default createServerRenderer(params => {
|
||||
return new Promise<RenderResult>((resolve, reject) => {
|
||||
// Prepare Redux store with in-memory history, and dispatch a navigation event
|
||||
// corresponding to the incoming URL
|
||||
const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash
|
||||
const urlAfterBasename = params.url.substring(basename.length);
|
||||
const store = configureStore(createMemoryHistory());
|
||||
store.dispatch(replace(urlAfterBasename));
|
||||
|
||||
// Prepare an instance of the application and perform an inital render that will
|
||||
// cause any async tasks (e.g., data access) to begin
|
||||
const routerContext: any = {};
|
||||
const app = (
|
||||
<Provider store={ store }>
|
||||
<StaticRouter basename={ basename } context={ routerContext } location={ params.location.path } children={ routes } />
|
||||
</Provider>
|
||||
);
|
||||
renderToString(app);
|
||||
|
||||
// If there's a redirection, just send this information back to the host application
|
||||
if (routerContext.url) {
|
||||
resolve({ redirectUrl: routerContext.url });
|
||||
return;
|
||||
}
|
||||
|
||||
// Once any async tasks are done, we can perform the final render
|
||||
// We also send the redux store state, so the client can continue execution where the server left off
|
||||
params.domainTasks.then(() => {
|
||||
resolve({
|
||||
html: renderToString(app),
|
||||
globals: { initialReduxState: store.getState() }
|
||||
});
|
||||
}, reject); // Also propagate any errors back into the host application
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { ApplicationState } from '../store';
|
||||
import * as CounterStore from '../store/Counter';
|
||||
import * as WeatherForecasts from '../store/WeatherForecasts';
|
||||
|
||||
type CounterProps =
|
||||
CounterStore.CounterState
|
||||
& typeof CounterStore.actionCreators
|
||||
& RouteComponentProps<{}>;
|
||||
|
||||
class Counter extends React.Component<CounterProps, {}> {
|
||||
public render() {
|
||||
return <div>
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>This is a simple example of a React component.</p>
|
||||
|
||||
<p>Current count: <strong>{ this.props.count }</strong></p>
|
||||
|
||||
<button onClick={ () => { this.props.increment() } }>Increment</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// Wire up the React component to the Redux store
|
||||
export default connect(
|
||||
(state: ApplicationState) => state.counter, // Selects which state properties are merged into the component's props
|
||||
CounterStore.actionCreators // Selects which action creators are merged into the component's props
|
||||
)(Counter) as typeof Counter;
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { ApplicationState } from '../store';
|
||||
import * as WeatherForecastsState from '../store/WeatherForecasts';
|
||||
|
||||
// At runtime, Redux will merge together...
|
||||
type WeatherForecastProps =
|
||||
WeatherForecastsState.WeatherForecastsState // ... state we've requested from the Redux store
|
||||
& typeof WeatherForecastsState.actionCreators // ... plus action creators we've requested
|
||||
& RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
|
||||
|
||||
class FetchData extends React.Component<WeatherForecastProps, {}> {
|
||||
componentWillMount() {
|
||||
// This method runs when the component is first added to the page
|
||||
let startDateIndex = parseInt(this.props.match.params.startDateIndex) || 0;
|
||||
this.props.requestWeatherForecasts(startDateIndex);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: WeatherForecastProps) {
|
||||
// This method runs when incoming props (e.g., route params) change
|
||||
let startDateIndex = parseInt(nextProps.match.params.startDateIndex) || 0;
|
||||
this.props.requestWeatherForecasts(startDateIndex);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <div>
|
||||
<h1>Weather forecast</h1>
|
||||
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
|
||||
{ this.renderForecastsTable() }
|
||||
{ this.renderPagination() }
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderForecastsTable() {
|
||||
return <table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Temp. (C)</th>
|
||||
<th>Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.forecasts.map(forecast =>
|
||||
<tr key={ forecast.dateFormatted }>
|
||||
<td>{ forecast.dateFormatted }</td>
|
||||
<td>{ forecast.temperatureC }</td>
|
||||
<td>{ forecast.temperatureF }</td>
|
||||
<td>{ forecast.summary }</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
|
||||
private renderPagination() {
|
||||
let prevStartDateIndex = (this.props.startDateIndex || 0) - 5;
|
||||
let nextStartDateIndex = (this.props.startDateIndex || 0) + 5;
|
||||
|
||||
return <p className='clearfix text-center'>
|
||||
<Link className='btn btn-default pull-left' to={ `/fetchdata/${ prevStartDateIndex }` }>Previous</Link>
|
||||
<Link className='btn btn-default pull-right' to={ `/fetchdata/${ nextStartDateIndex }` }>Next</Link>
|
||||
{ this.props.isLoading ? <span>Loading...</span> : [] }
|
||||
</p>;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props
|
||||
WeatherForecastsState.actionCreators // Selects which action creators are merged into the component's props
|
||||
)(FetchData) as typeof FetchData;
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
export default class Home extends React.Component<RouteComponentProps<{}>, {}> {
|
||||
public render() {
|
||||
return <div>
|
||||
<h1>Hello, world!</h1>
|
||||
<p>Welcome to your new single-page application, built with:</p>
|
||||
<ul>
|
||||
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
|
||||
<li><a href='https://facebook.github.io/react/'>React</a>, <a href='http://redux.js.org'>Redux</a>, and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>
|
||||
<li><a href='https://webpack.github.io/'>Webpack</a> for building and bundling client-side resources</li>
|
||||
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
|
||||
</ul>
|
||||
<p>To help you get started, we've also set up:</p>
|
||||
<ul>
|
||||
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
|
||||
<li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li>
|
||||
<li><strong>Hot module replacement</strong>. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, rebuilt React components will be injected directly into your running application, preserving its live state.</li>
|
||||
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li>
|
||||
<li><strong>Server-side prerendering</strong>. To optimize startup time, your React application is first rendered on the server. The initial HTML and state is then transferred to the browser, where client-side code picks up where the server left off.</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { NavMenu } from './NavMenu';
|
||||
|
||||
export class Layout extends React.Component<{}, {}> {
|
||||
public render() {
|
||||
return <div className='container-fluid'>
|
||||
<div className='row'>
|
||||
<div className='col-sm-3'>
|
||||
<NavMenu />
|
||||
</div>
|
||||
<div className='col-sm-9'>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as React from 'react';
|
||||
import { NavLink, Link } from 'react-router-dom';
|
||||
|
||||
export class NavMenu extends React.Component<{}, {}> {
|
||||
public render() {
|
||||
return <div className='main-nav'>
|
||||
<div className='navbar navbar-inverse'>
|
||||
<div className='navbar-header'>
|
||||
<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
|
||||
<span className='sr-only'>Toggle navigation</span>
|
||||
<span className='icon-bar'></span>
|
||||
<span className='icon-bar'></span>
|
||||
<span className='icon-bar'></span>
|
||||
</button>
|
||||
<Link className='navbar-brand' to={ '/' }>ReactReduxSpa</Link>
|
||||
</div>
|
||||
<div className='clearfix'></div>
|
||||
<div className='navbar-collapse collapse'>
|
||||
<ul className='nav navbar-nav'>
|
||||
<li>
|
||||
<NavLink exact to={ '/' } activeClassName='active'>
|
||||
<span className='glyphicon glyphicon-home'></span> Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to={ '/counter' } activeClassName='active'>
|
||||
<span className='glyphicon glyphicon-education'></span> Counter
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to={ '/fetchdata' } activeClassName='active'>
|
||||
<span className='glyphicon glyphicon-th-list'></span> Fetch data
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store, StoreEnhancerStoreCreator, ReducersMapObject } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { routerReducer, routerMiddleware } from 'react-router-redux';
|
||||
import * as StoreModule from './store';
|
||||
import { ApplicationState, reducers } from './store';
|
||||
import { History } from 'history';
|
||||
|
||||
export default function configureStore(history: History, initialState?: ApplicationState) {
|
||||
// Build middleware. These are functions that can process the actions before they reach the store.
|
||||
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
|
||||
// If devTools is installed, connect to it
|
||||
const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__ as () => GenericStoreEnhancer;
|
||||
const createStoreWithMiddleware = compose(
|
||||
applyMiddleware(thunk, routerMiddleware(history)),
|
||||
devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
|
||||
)(createStore);
|
||||
|
||||
// Combine all reducers and instantiate the app-wide store instance
|
||||
const allReducers = buildRootReducer(reducers);
|
||||
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;
|
||||
|
||||
// Enable Webpack hot module replacement for reducers
|
||||
if (module.hot) {
|
||||
module.hot.accept('./store', () => {
|
||||
const nextRootReducer = require<typeof StoreModule>('./store');
|
||||
store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
|
||||
});
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function buildRootReducer(allReducers: ReducersMapObject) {
|
||||
return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
.main-nav li .glyphicon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Highlighting rules for nav menu items */
|
||||
.main-nav li a.active,
|
||||
.main-nav li a.active:hover,
|
||||
.main-nav li a.active:focus {
|
||||
background-color: #4189C7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Keep the nav menu independent of scrolling and on top of other items */
|
||||
.main-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
/* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* On small screens, convert the nav menu to a vertical sidebar */
|
||||
.main-nav {
|
||||
height: 100%;
|
||||
width: calc(25% - 20px);
|
||||
}
|
||||
.main-nav .navbar {
|
||||
border-radius: 0px;
|
||||
border-width: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.main-nav .navbar-header {
|
||||
float: none;
|
||||
}
|
||||
.main-nav .navbar-collapse {
|
||||
border-top: 1px solid #444;
|
||||
padding: 0px;
|
||||
}
|
||||
.main-nav .navbar ul {
|
||||
float: none;
|
||||
}
|
||||
.main-nav .navbar li {
|
||||
float: none;
|
||||
font-size: 15px;
|
||||
margin: 6px;
|
||||
}
|
||||
.main-nav .navbar li a {
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.main-nav .navbar a {
|
||||
/* If a menu item's text is too long, truncate it */
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { Layout } from './components/Layout';
|
||||
import Home from './components/Home';
|
||||
import FetchData from './components/FetchData';
|
||||
import Counter from './components/Counter';
|
||||
|
||||
export const routes = <Layout>
|
||||
<Route exact path='/' component={ Home } />
|
||||
<Route path='/counter' component={ Counter } />
|
||||
<Route path='/fetchdata/:startDateIndex?' component={ FetchData } />
|
||||
</Layout>;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Action, Reducer } from 'redux';
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
export interface CounterState {
|
||||
count: number;
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
|
||||
// They do not themselves have any side-effects; they just describe something that is going to happen.
|
||||
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
|
||||
|
||||
interface IncrementCountAction { type: 'INCREMENT_COUNT' }
|
||||
interface DecrementCountAction { type: 'DECREMENT_COUNT' }
|
||||
|
||||
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
|
||||
// declared type strings (and not any other arbitrary string).
|
||||
type KnownAction = IncrementCountAction | DecrementCountAction;
|
||||
|
||||
// ----------------
|
||||
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
|
||||
// They don't directly mutate state, but they can have external side-effects (such as loading data).
|
||||
|
||||
export const actionCreators = {
|
||||
increment: () => <IncrementCountAction>{ type: 'INCREMENT_COUNT' },
|
||||
decrement: () => <DecrementCountAction>{ type: 'DECREMENT_COUNT' }
|
||||
};
|
||||
|
||||
// ----------------
|
||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
|
||||
|
||||
export const reducer: Reducer<CounterState> = (state: CounterState, action: KnownAction) => {
|
||||
switch (action.type) {
|
||||
case 'INCREMENT_COUNT':
|
||||
return { count: state.count + 1 };
|
||||
case 'DECREMENT_COUNT':
|
||||
return { count: state.count - 1 };
|
||||
default:
|
||||
// The following line guarantees that every action in the KnownAction union has been covered by a case above
|
||||
const exhaustiveCheck: never = action;
|
||||
}
|
||||
|
||||
// For unrecognized actions (or in cases where actions have no effect), must return the existing state
|
||||
// (or default initial state if none was supplied)
|
||||
return state || { count: 0 };
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { fetch, addTask } from 'domain-task';
|
||||
import { Action, Reducer, ActionCreator } from 'redux';
|
||||
import { AppThunkAction } from './';
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
export interface WeatherForecastsState {
|
||||
isLoading: boolean;
|
||||
startDateIndex?: number;
|
||||
forecasts: WeatherForecast[];
|
||||
}
|
||||
|
||||
export interface WeatherForecast {
|
||||
dateFormatted: string;
|
||||
temperatureC: number;
|
||||
temperatureF: number;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
|
||||
// They do not themselves have any side-effects; they just describe something that is going to happen.
|
||||
|
||||
interface RequestWeatherForecastsAction {
|
||||
type: 'REQUEST_WEATHER_FORECASTS';
|
||||
startDateIndex: number;
|
||||
}
|
||||
|
||||
interface ReceiveWeatherForecastsAction {
|
||||
type: 'RECEIVE_WEATHER_FORECASTS';
|
||||
startDateIndex: number;
|
||||
forecasts: WeatherForecast[];
|
||||
}
|
||||
|
||||
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
|
||||
// declared type strings (and not any other arbitrary string).
|
||||
type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction;
|
||||
|
||||
// ----------------
|
||||
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
|
||||
// They don't directly mutate state, but they can have external side-effects (such as loading data).
|
||||
|
||||
export const actionCreators = {
|
||||
requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
// Only load data if it's something we don't already have (and are not already loading)
|
||||
if (startDateIndex !== getState().weatherForecasts.startDateIndex) {
|
||||
let fetchTask = fetch(`api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex }`)
|
||||
.then(response => response.json() as Promise<WeatherForecast[]>)
|
||||
.then(data => {
|
||||
dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data });
|
||||
});
|
||||
|
||||
addTask(fetchTask); // Ensure server-side prerendering waits for this to complete
|
||||
dispatch({ type: 'REQUEST_WEATHER_FORECASTS', startDateIndex: startDateIndex });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------
|
||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
|
||||
|
||||
const unloadedState: WeatherForecastsState = { forecasts: [], isLoading: false };
|
||||
|
||||
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState, incomingAction: Action) => {
|
||||
const action = incomingAction as KnownAction;
|
||||
switch (action.type) {
|
||||
case 'REQUEST_WEATHER_FORECASTS':
|
||||
return {
|
||||
startDateIndex: action.startDateIndex,
|
||||
forecasts: state.forecasts,
|
||||
isLoading: true
|
||||
};
|
||||
case 'RECEIVE_WEATHER_FORECASTS':
|
||||
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
|
||||
// handle out-of-order responses.
|
||||
if (action.startDateIndex === state.startDateIndex) {
|
||||
return {
|
||||
startDateIndex: action.startDateIndex,
|
||||
forecasts: action.forecasts,
|
||||
isLoading: false
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// The following line guarantees that every action in the KnownAction union has been covered by a case above
|
||||
const exhaustiveCheck: never = action;
|
||||
}
|
||||
|
||||
return state || unloadedState;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as WeatherForecasts from './WeatherForecasts';
|
||||
import * as Counter from './Counter';
|
||||
|
||||
// The top-level state object
|
||||
export interface ApplicationState {
|
||||
counter: Counter.CounterState;
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState;
|
||||
}
|
||||
|
||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
|
||||
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
|
||||
// acts on the corresponding ApplicationState property type.
|
||||
export const reducers = {
|
||||
counter: Counter.reducer,
|
||||
weatherForecasts: WeatherForecasts.reducer
|
||||
};
|
||||
|
||||
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are
|
||||
// correctly typed to match your store.
|
||||
export interface AppThunkAction<TAction> {
|
||||
(dispatch: (action: TAction) => void, getState: () => ApplicationState): void;
|
||||
}
|
||||
Reference in New Issue
Block a user