Hi,
I have created an asp.net MVC core 3.0 application with react REDUX.
I am trying to create an example.
I am trying to create cards with data and want to share the code.
I think my source should be more improved (like async/await and other approaches).
I am displaying cards but UI doesn't seems to be accurate. can someone suggest a better fix to me ?
-- product.ts ---------------
import { Action, Reducer } from 'redux';
import { AppThunkAction } from '.';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface ProductState {
isLoading: boolean;
startDateIndex?: number;
products: Product[];
}
export interface Product {
Id: number;
Date: string;
Code: number;
Name: string;
Price: number;
Description: 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 RequestProductsAction {
type: 'REQUEST_PRODUCTS';
startDateIndex: number;
}
interface ReceiveProductsAction {
type: 'RECEIVE_PRODUCTS';
startDateIndex: number;
products: Product[];
}
// 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 = RequestProductsAction | ReceiveProductsAction;
// ----------------
// 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 = {
requestProducts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
//debugger;
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState();
if (appState && appState.products && startDateIndex !== appState.products.startDateIndex) {
fetch(`products`)
.then(response => response.json() as Promise<Product[]>)
.then(data => {
dispatch({ type: 'RECEIVE_PRODUCTS', startDateIndex: startDateIndex, products: data });
});
dispatch({ type: 'REQUEST_PRODUCTS', 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: ProductState = { products:[], isLoading: false };
export const reducer: Reducer<ProductState> = (state: ProductState | undefined, incomingAction: Action): ProductState => {
//debugger;
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_PRODUCTS':
return {
startDateIndex: action.startDateIndex,
products: state.products,
isLoading: true
};
case 'RECEIVE_PRODUCTS':
// 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,
products: action.products,
isLoading: false
};
}
break;
}
return state;
};
--------------Home.tsx-------------------------------
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { Link } from 'react-router-dom';
import { ApplicationState } from '../store';
import * as logoImg from "../images/191d14327e6facffb4cefb2d8e7ff2ce.jpg";
const mystyles = {
width: '18rem',
} as React.CSSProperties;
import * as ProductStateStore from '../store/Product';
// At runtime, Redux will merge together...
type ProductProps =
ProductStateStore.ProductState // ... state we've requested from the Redux store
& typeof ProductStateStore.actionCreators // ... plus action creators we've requested& RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
class Home extends React.PureComponent<ProductProps> {
// This method is called when the component is first added to the document
public componentDidMount() {
this.ensureDataFetched();
}
// This method is called when the route parameters change
public componentDidUpdate() {
this.ensureDataFetched();
}
public render() {
return (
<React.Fragment><h1>Weather forecast</h1><p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{this.renderProducts()}
{/*this.renderPagination()*/}</React.Fragment>
);
}
private ensureDataFetched() {
const startDateIndex = parseInt(this.props.match.params.startDateIndex, 10) || 0;
this.props.requestProducts(startDateIndex);
}
private renderProducts() {
return (
<React.Fragment>
{
this.props.products.map((prod: ProductStateStore.Product) =><div id={String(prod.Id)} className="card" style={{ width: '18rem' }} ><img src={String(logoImg)} className="card-img-top" alt="Card image cap" /><div className="card-body"><h5 className="card-title">{prod.Name}</h5><p className="card-text">{prod.Description}.</p><p className="card-text">US${prod.Price} / Piece <br />
2 Pieces (Min Order)<br />
3 buyers </p><a href="#" className="btn btn-primary">Add To Cart</a></div></div>
)}</React.Fragment>
);
}
private renderPagination() {
const prevStartDateIndex = (this.props.startDateIndex || 0) - 5;
const nextStartDateIndex = (this.props.startDateIndex || 0) + 5;
return (
<div className="d-flex justify-content-between"><Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${prevStartDateIndex}`}>Previous</Link>
{this.props.isLoading && <span>Loading...</span>}<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${nextStartDateIndex}`}>Next</Link></div>
);
}
}
export default connect(
(state: ApplicationState) => state.products, // Selects which state properties are merged into the component's props
ProductStateStore.actionCreators // Selects which action creators are merged into the component's props
) (Home as any);
------------- ProductsController----------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace WebApplication6.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
private static readonly string[] Summaries = new[]
{"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<ProductsController> _logger;
public ProductsController(ILogger<ProductsController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<Product> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new Product
{
Id = rng.Next(1, 155),
Date = DateTime.Now.AddDays(index),
Code = "CEDE"+ rng.Next(-20, 55),
Price = 56,
Name = "Apple",
Description = "This is a apple."
})
.ToArray();
}
}
}
----------------------------Product.cs---------------------------------------
public class Product
{
public int Id { get; set; }
public DateTime Date { set; get; }
public string Code { set; get; }
public string Name { set; get; }
public decimal Price { get; set; }
public string Description { get; set; }
}
----------------App.tsx-----------------------------
import * as React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
export default () => (<Layout><Route exact path='/home' component={Home}/><Route path='/counter' component={Counter} /><Route path='/fetch-data/:startDateIndex?' component={FetchData} /></Layout>
);