react-virtualized: Table Column with CellMeasurer always has cache height - react-virtualized

I've been trying to follow the DynamicHeightTableColumn example and adapt it for my use case. I can see in the CellMeasurer demo that the single-line rows have the default height, defined when initializing the cache. However, in my case, it seems like single-line rows always have the tallest row height, stored in the cache, instead of the default one.
Here is my code:
import * as React from 'react';
import * as Immutable from 'immutable';
import { Table, Column, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
interface Props {
logs: Immutable.List<Immutable.Map<string, any>>;
columns: (
width: number
) => Array<{
name: string;
key: string;
width: number;
variableHeight?: boolean;
}>;
}
export default class LogTable extends React.Component<Props, {}> {
private cache: CellMeasurerCache;
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
defaultHeight: 20,
fixedWidth: true,
keyMapper: () => 1
});
}
render() {
const { logs } = this.props;
return (
<AutoSizer disableHeight>
{({ width }) => (
<Table
headerHeight={20}
height={250}
rowCount={logs.size}
rowHeight={this.cache.rowHeight}
width={width}
overscanRowCount={2}
rowRenderer={this.rowRenderer}
headerClassName='col-header'
gridClassName='log-table-grid'
rowClassName='log-table-row'
className='log-table'
rowGetter={({ index }) => logs.get(index)}
deferredMeasurementCache={this.cache}>
{this.renderColumns(width)}
</Table>
)}
</AutoSizer>
);
}
private renderColumns(width) {
return this.props.columns(width).map(({ name, key, width, variableHeight }, idx) => {
const props: any = {
label: name,
dataKey: key,
width,
key,
className: 'column'
};
if (variableHeight) {
props.cellRenderer = this.cellRenderer.bind(this, idx);
}
return <Column {...props} />;
});
}
private rowRenderer(params) {
const { key, className, columns, rowData, style } = params;
if (!rowData) {
return null;
}
return (
<div className={className} key={key} style={style}>
{columns}
</div>
);
}
private cellRenderer(colIndex, { dataKey, parent, rowIndex }) {
const content = this.props.logs.get(rowIndex).get(dataKey);
return (
<CellMeasurer
cache={this.cache}
columnIndex={colIndex}
key={dataKey}
parent={parent}
rowIndex={rowIndex}>
<div style={{ whiteSpace: 'normal' }} title={content}>
{content}
</div>
</CellMeasurer>
);
}
}
And this is the output (see 2nd row in the table that's too tall for its content)
The only styling (less) I have is the following, which I don't think can cause this behavior
.log-table {
font-size: 14px;
.col-header {
font-size: 15px;
text-align: center;
}
.log-table-grid {
outline: none;
font-family: monospace;
}
.log-table-row {
border-bottom: 1px solid gray;
padding: 3px;
}
}

Related

Adding Stripe Subscription to Blazor WASM

I am trying to add Stripe Subscription to my Blazor WASM Application following these instructions Since they are using JavaScript I am using the JavaScript interop. I added Stripe's script to my index.html and added a custom script with the javascript they have in the instructions.
Index.html inside the <head> tag
<script src="https://js.stripe.com/v3/"></script>
<script src="stripescript.js"></script>
stripescript.js:
let stripe = window.Stripe('MY PUBLIC KEY');
let elements = stripe.elements();
let card = elements.create('card', { style: style });
card.mount('#card-element');
card.on('change', function (event) {
displayError(event);
});
function displayError(event) {
changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}
function createPaymentMethod(cardElement, customerId, priceId) {
return stripe
.createPaymentMethod({
type: 'card',
card: cardElement,
})
.then((result) => {
if (result.error) {
displayError(error);
} else {
//change this to call .net
createSubscription({
customerId: customerId,
paymentMethodId: result.paymentMethod.id,
priceId: priceId,
});
}
});
}
My assumption is that the variable initializations would happen when the application is loaded. However, when I add the following HTML to my Razor page is not populating the card component.
<form id="payment-form">
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-element-errors" role="alert"></div>
<button type="submit">Subscribe</button>
</form>
I am lost on how to debug this, or if this is even possible in Blazor.
Thanks to #Umair's Comments, I realized that I had made a few mistakes and some of them were showing up in the console since I was trying to initialize the card element before the DOM was loaded. I was able to fix my problem by first changing the card mount into its own function. Here is the full stripescript.js for future people that have this problem:
let stripe = window.Stripe('MY KEY');
let elements = stripe.elements();
let style = {
base: {
fontSize: '16px',
color: '#32325d',
fontFamily:
'-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
fontSmoothing: 'antialiased',
'::placeholder': {
color: '#a0aec0',
},
},
};
let card = elements.create('card', { style: style });
function mountCard() {
card.mount('#card-element');
}
card.on('change', function (event) {
displayError(event);
});
function displayError(event) {
changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}
function createPaymentMethod(cardElement, customerId, priceId) {
return stripe
.createPaymentMethod({
type: 'card',
card: cardElement,
})
.then((result) => {
if (result.error) {
displayError(error);
} else {
//todo change this to call .net
createSubscription({
customerId: customerId,
paymentMethodId: result.paymentMethod.id,
priceId: priceId,
});
}
});
}
and added the following C# code to my Blazor component to render the card:
[Inject] IJSRuntime js { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await js.InvokeVoidAsync("mountCard");
}
I found a solution for this on Youtube that is super complicated, and I decided to create my own implementation following KISS.
I have streamlined youtube's implementation on a single StripeCard component that interacts with my custom Js StripeInterop. I hope this makes your life easier.
This will allow you to have different publishable keys for your different environments without hardcoding it and reuse the component in multiple pages if you wish. Also, it will destroy itself when the component is used.
Here is my solution for (blazor webassembly).
Add this to index.html
<!-- Stripe -->
<script src="https://js.stripe.com/v3/"></script>
<script src="stripescript.js"></script>
Here is stripescript.js
StripeInterop = (() => {
var stripe = null;
var elements = null;
var dotNetReference = null;
var card = null;
var style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
return {
init(dotnetHelper, publishableKey) {
stripe = window.Stripe(publishableKey);
elements = stripe.elements();
card = elements.create('card', { style: style });
dotNetReference = dotnetHelper;
card.mount('#card-element');
card.on('change', function (event) {
displayError(event);
});
},
createPaymentMethod(billingEmail, billingName) {
return stripe
.createPaymentMethod({
type: 'card',
card: card,
billing_details: {
name: billingName,
email: billingEmail
}
})
.then(function (result) {
if (result.error) {
displayError(result);
} else {
dotNetReference.invokeMethodAsync('ProcessPaymentMethod', result.paymentMethod.id);
}
});
},
destroy() {
dotNetReference.dispose();
card.destroy();
}
};
function displayError(event) {
var showError = document.getElementById('card-element-errors');
if (event.error) {
showError.textContent = event.error.message;
} else {
showError.textContent = '';
}
}
})();
Here is StripeCard.razor component
#namespace SmartApp.Components
<div id="card-element" style="display: block;
width: 100%;
padding: 0.52rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;">
</div>
<div id="card-element-errors" class="validation-message"></div>
Here is StripeCard.razor.cs
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace SmartApp.Components
{
public partial class StripeCard : IDisposable
{
[Inject] IJSRuntime JS { get; set; }
[Parameter] public string PublishableKey { get; set; }
[Parameter] public EventCallback<string> CardProcessedCallBack { get; set; }
private bool _firstTime;
protected override async Task OnInitializedAsync()
{
_firstTime = true;
await base.OnInitializedAsync();
}
public async void Dispose()
{
await JS.InvokeVoidAsync("StripeInterop.destroy");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (_firstTime)
{
_firstTime = false;
await JS.InvokeVoidAsync("StripeInterop.init", DotNetObjectReference.Create(this), PublishableKey);
}
}
[JSInvokable("ProcessPaymentMethod")]
public Task ProcessPaymentMethod(string paymentId)
{
return CardProcessedCallBack.InvokeAsync(paymentId);
}
}
}
Here is how we use StripeCard.razor in our Page.
#page "/subscription/payment"
#attribute [Authorize(Roles = "Admin,Organisation")]
#inject IJSRuntime JS
<div class="card card-custom card-shadowless rounded-top-0">
<div class="card-body p-0">
<div class="row justify-content-center py-8 px-8 py-lg-15 px-lg-10">
<div class="col-xl-12 col-xxl-7">
<!--begin: Wizard Form-->
<EditForm Model="#_model" OnValidSubmit="HandleValidSubmit" >
<DataAnnotationsValidator />
<h4 class="mb-10 font-weight-bold text-dark">Enter your Payment Details</h4>
<div class="row">
<div class="col-xl-6">
<!--begin::Input-->
<div class="form-group">
<label>Name on Card</label>
<InputText #bind-Value="_model.BillingName" name="ccname" class="form-control form-control-solid form-control-lg"
placeholder="Jane Doe" />
<ValidationMessage For="#(() => _model.BillingName)" />
</div>
<!--end::Input-->
</div>
<div class="col-xl-6">
<!--begin::Input-->
<div class="form-group">
<label>Notification Email</label>
<InputText #bind-Value="_model.BillingEmail" name="ccemail" class="form-control form-control-solid form-control-lg"
placeholder="jane.doe#domain.com" />
<ValidationMessage For="#(() => _model.BillingEmail)" />
</div>
<!--end::Input-->
</div>
</div>
<div class="row">
<div class="col-xl-12">
<!--begin::Input-->
<div class="form-group">
<label>Card Information</label>
<StripeCard PublishableKey="#_stripePublishableKey" CardProcessedCallBack="ProcessSubscriptionAsync"></StripeCard>
<span class="form-text text-muted">Powered by <strong>Stripe</strong>.</span>
</div>
<!--end::Input-->
</div>
</div>
<ValidationSummary />
<div class="d-flex justify-content-between border-top mt-5 pt-10">
<div>
<button type="button" class="btn btn-success font-weight-bolder text-uppercase px-9 py-4"
#onclick="HandleValidSubmit">
Submit
</button>
</div>
</div>
</EditForm>
<!--end: Wizard Form-->
</div>
</div>
</div>
</div>
#code{
private Model _model;
private string _stripePublishableKey;
protected override void OnInitialized()
{
_model = new Model();
_stripePublishableKey = "Here we put Development or Production publishableKey"; //Add publishable key here
base.OnInitialized();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("KTSubscriptionCheckout.init");
}
await base.OnAfterRenderAsync(firstRender);
}
private async Task HandleValidSubmit()
{
await JS.InvokeVoidAsync("StripeInterop.createPaymentMethod", _model.BillingEmail, _model.BillingName);
}
//Callback method will return stripe paymentId
private async Task ProcessSubscriptionAsync(string paymentId)
{
//We process paymentId here and continue with our backend process
await Task.CompletedTask;
}
public class Model
{
[Required]
public string BillingName { get; set; }
[Required]
public string BillingEmail { get; set; }
}
}
Youtube video if you want to have a look. https://youtu.be/ANYvFHHfyy8
Good luck...

React: Map does not give expected result

I am using React-typescript for my app. For styling I am using styled-components. I have created one global breadcrumbs components. When I am using the breadcrumbs components to parent components there will be couple links. after mapping the links it should display single links but I am getting combined links. This is how it looks like:
My expected result is:
This is my parent component where I am importing my Breadcrumbs
<BreadCrumb>
check
this
out
</BreadCrumb>
This is my global Breadcrumb component
import React from "react";
import styled from 'styled-components'
export interface IBreadCrumb {
direction?: "";
children: JSX.Element[];
onClick?: () => void;
}
export const BreadCrumb = ({ direction, children, onClick }: IBreadCrumb) => {
return (
<div>
<Breadcrumbs>
{
children.map(i => //In here I am doing my mapping.
<Crumb>
<a href="#" onClick={onClick}>{children}</a>
</Crumb>
)
}
</Breadcrumbs>
</div >
)
}
const Direction = {
right: "\\2192",
left: "\\2190 ",
slash: "/"
}
const Crumb = styled.li`
display: inline-block;
&:last-of-type:after {
content: "";
padding: 0;
}
a {
color: grey;
text-decoration: none;
&:hover,
&:active {
text-decoration: underline;
}
}
`
const Breadcrumbs = styled.ul`
list-style: none;
padding: 0;
& > li:after {
content: "${Direction.right}";
padding: 0 8px;
color: grey
}
`;
Should it not be e.g.:
children.map((child, index) =>
<Crumb>
<a key="{index}" href="#" onClick={onClick}>{child}</a>
</Crumb>
)

Access the child component prop inside the parent component

I'm facing a problem there is a styled component named Breadcrumb but that component depends upon 1 separate styled-components i.e BreadcrumbItem. Both components have different props.
BreadcrumbItem.js:
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const propTypes = {
/** Active the current BreadcrumbItem. */
active: PropTypes.bool,
/** Additional classes. */
className: PropTypes.string
};
const AbstractBreadcrumbItem = (props) => {
const { className, active, ...attributes } = props;
return <li {...attributes} className={className} />;
};
AbstractBreadcrumbItem.propTypes = propTypes;
const BreadcrumbItem = styled(AbstractBreadcrumbItem)``;
BreadcrumbItem.propTypes = propTypes;
export default BreadcrumbItem;
Breadcrumb.js:
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
const propTypes = {
/** Additional classes. */
className: PropTypes.string,
/** Primary content. */
children: PropTypes.node,
/** Custom separator */
separator: PropTypes.string,
/** Change the look and feel of the BreadcrumbItem. */
scheme: PropTypes.oneOf(["red", "purple"]).isRequired
};
const defaultProps = {
scheme: "red",
separator: "/"
};
const AbstractBreadcrumb = props => {
const { className, children, separator, scheme, ...attributes } = props;
return (
<ul {...attributes} className={className}>
{children}
</ul>
);
};
AbstractBreadcrumb.propTypes = propTypes;
AbstractBreadcrumb.defaultProps = defaultProps;
const Breadcrumb = styled(AbstractBreadcrumb)`
display: flex;
flex-wrap: wrap;
padding: 18px 26px;
margin-bottom: 1rem;
list-style: none;
background-color: #fbfbfb;
border-radius: 4px;
li + li:before {
content: "${props => props.separator}";
}
li + li {
padding-left: 8px;
}
li + li::before {
display: inline-block;
padding-right: 0.5rem;
}
li a {
font-size: 14px;
transition: color .4s linear;
color: ${props => (props.scheme === "red" ? "red" : "purple")};
&:hover {
color: black;
}
}
`;
Breadcrumb.propTypes = propTypes;
Breadcrumb.defaultProps = defaultProps;
export default Breadcrumb;
This is the main markup to create the Breadcrumb.
App.js:
import React from 'react';
import Breadcrumb from './Breadcrumb';
import BreadcrumbItem from './BreadcrumbItem';
export default function App() {
return (
<div className="App">
<Breadcrumb scheme="red">
<BreadcrumbItem>
Home
</BreadcrumbItem>
<BreadcrumbItem>
Shop
</BreadcrumbItem>
<BreadcrumbItem active>
Product
</BreadcrumbItem>
</Breadcrumb>
</div>
);
}
What problem I'm facing is I want to use the active prop of the BreadcrumbItem component inside the parent Breadcrumb component to change the look and feel of the item according to the scheme.
I found the first way which is to add the BreadcrumbItem styles inside the component itself and use something like this ${props => props.active ? css`` : css``}. But Is there a way in styled-component to access the child component prop inside the Parent component?
Please answer the question in the context of styled-components.
Live link: Codesandbox
I'd suggest to move the styling of list item, i.e. <li>, to its own component, i.e. BreadcrumbItem. In this scenario you won't need to access the state of child component instead you'll be handling active state in <li> styles. And it'll look more cleaner and separation of concern (which React recommends) will be there.
[EDIT]: Sample code to access props of children
const List = ({ children }) => {
return (
<ul>
{React.Children.map(children, x => {
console.log(x.props); // get props of children
return x;
})}
</ul>
);
};
const Item = ({ children }) => <li>{children}</li>;
export default function App() {
return (
<List>
<Item>Hello</Item>
<Item active>HI</Item>
</List>
);
}

How can I create an SVG Icon from an SVG image with material ui?

So, I'm trying to create a custom Svgicon, but all the source code uses the paths directly. Is there anyway I can add an SVG icon from an image like this?
import { SvgIcon } from '#material-ui/core';
import React from 'react';
const iconStyles = {
marginRight: 24,
};
export const LawyersIcon = props => (
<SvgIcon {...props}>
<img src="https://pictures-for-website.nyc3.digitaloceanspaces.com/icons/team.svg" />
</SvgIcon>
);
So, you actually don't need to use SvgIcon at all, you can just create your own icon. This is my final code:
import { withStyles } from '#material-ui/core';
import React from 'react';
#withStyles(theme => ({
svg_icon: {
width: '1em',
height: '1em',
display: 'inline-block',
fontSize: '24px',
transition: 'fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
flexShrink: 0,
userSelect: 'none',
}
}))
export class LawyersIcon extends React.Component {
render() {
const { classes } = this.props
return <div className={classes.svg_icon}>
<img src="https://pictures-for-website.nyc3.digitaloceanspaces.com/icons/team.svg" />
</div>
}
}

Incorrect rendering of a component when using Math.random in setState in React Component

The intention is to display an item from a list of objects but on every page refresh, the item should be randomly chosen from the list. Here, Testimonials is the list and I want to display any random item from this list. If I use a constant, it works fine. When I use, the random function, it does not display proper image with its associated item message.
I use React 16, Next.js, styled components as the tech.
The problem is in rendering of Employees section. The console displays a warning as
warning.js?6327:33 Warning: Propsrcdid not match. Server: "/static/images/testimonials/2.jpg" Client: "/static/images/testimonials/5.png"
Here is the piece of my code
import {Component} from 'react';
import Row from '../../../common-util/row';
import Col from '../../../common-util/col';
import {Container, Content, Image, StyledCol, Statement, Title, Designation, Heading, Arrow} from './styles';
const Testimonials = [{
name: 'ACX',
role: 'XYZ',
image: '/static/images/testimonials/1.jpeg',
message: 'KL DSAD E'
}, {
name: 'HJK',
role: 'Growth Hacker',
image: '/static/images/testimonials/2.jpg',
message: 'JKLASD ASDA'
}, {
name: 'ZXCV',
role: 'Product Manager',
image: '/static/images/testimonials/3.jpg',
message: 'KKK'
}, {
name: 'UIP',
role: 'Data Integrity',
image: '/static/images/testimonials/4.JPG',
message: 'LOPO'
}, {
name: 'NMa',
role: 'Sales Evangelist',
image: '/static/images/testimonials/5.png',
message: 'KK D D D'
}];
export default class Employees extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: parseInt((Math.random()*10))%5,
};
}
render() {
let {currentIndex} = this.state;
return (
<Container>
<Content>
<Title>Our employees say...</Title>
</Content>
<Row>
<Col xs={1} sm={1} md={2} lg={2}>
<Arrow className={currentIndex === 0 ? 'disabled' : ''} onClick={this.handleClick.bind(this, 'DECREMENT')}>{'<'}</Arrow>
</Col>
<Col xs={10} sm={10} md={8} lg={8}>{this.currentItem(currentIndex)}</Col>
<Col xs={1} sm={1} md={2} lg={2}>
<Arrow className={currentIndex === Testimonials.length - 1 ? 'disabled' : ''} onClick={this.handleClick.bind(this, 'INCREMENT')}>{'>'}</Arrow>
</Col>
</Row>
</Container>
);
}
currentItem(currentIndex) {
const item = Testimonials[currentIndex];
return (
<Row>
<StyledCol xs={4} md={4} lg={3}>
<Image src={item.image} alt={item.name} />
</StyledCol>
<Col xs={8} md={8} lg={9}>
<Statement>{item.message}</Statement>
<Heading className='font-Bold'>{item.name},</Heading>
<Designation className='font-DemiBold'>{item.role}</Designation>
</Col>
</Row>
);
}
handleClick(type: string) {
let {currentIndex} = this.state;
switch (type) {
case 'DECREMENT':
this.setState({
currentIndex: currentIndex - 1
});
break;
case 'INCREMENT':
this.setState({
currentIndex: currentIndex + 1
});
break;
default:
}
}
}
The corresponding style is
import styled from 'styled-components';
import Col from '../../../common-util/col';
import Grid from '../../../common-util/grid';
import H4 from '../../../common-util/headers/h4';
import H5 from '../../../common-util/headers/h5';
export const Container = styled(Grid)`
padding-bottom: 20px;
`;
export const Content = styled.div`
display: flex;
flex-flow: row wrap;
width: 100%;
justify-content: center;
margin-bottom: 20px;
`;
export const Title = styled.h1`
font-size: 42px;
line-height: 1.0;
letter-spacing: -0.3px;
text-align: justify;
font-weight: 500;
`;
export const Image = styled.img`
width: 100%;
`;
export const Statement = styled.p`
padding: 15px 20px;
background: url(/static/images/svg/top-left-bg.svg) top left no-repeat, url(/static/images/svg/bottom-right-bg.svg) bottom right no-repeat;
background-size: 20px;
line-height: 2.3;
letter-spacing: -0.2px;
font-size: 16px;
margin: 0
`;
export const Heading = H4.extend`
color: #4990e2;
text-align: left;
font-weight: bold;
line-height: 1.2;
margin-bottom: 5px;
margin-left: 20px;
`;
export const Designation = H5.extend`
text-align: left;
font-weight: 600;
line-height: 1.22;
letter-spacing: -0.2px;
margin-top: 0;
margin-left: 20px;
`;
export const Arrow = styled.div`
margin: auto;
color: grey;
font-size: 20px;
font-weight:lighter;
cursor: pointer;
&:hover {
font-size: 22px;
}
&.disabled {
pointer-events: none;
&:hover {
font-size: 20px;
}
}
`;
export const StyledCol = Col.extend`
margin: auto;
`;
That's the problem.
this.state = {
currentIndex: parseInt((Math.random()*10))%5,
};
This will be invoked on a server and in the browser causing a mismatch in rendered markup.
You could fix that by making sure random will only be called in a browser:
this.state = {
currentIndex: 0,
};
componentDidMount(){
this.setState({ currentIndex: parseInt((Math.random()*10))%5 })
}
Using React's useState and useEffect hook introduced in React 16.8.0:
const [selectedImage, setImage] = useState(<defaultImage>);
useEffect(() => {
setImage(randomImage());
}, [selectedImage]);

Resources