How to adopt styles of one styled-components component to another? - styled-components

Given the following code:
import styled from 'styled-components';
const Link = () => /* ... */;
const PrimaryLink = styled(Link)`
color: #666;
`;
const ExternalLink = styled.a /* How to extend with PrimaryLink, but keeping styled.a */;
I want ExternalLink component (which is styled.a) to extend styles from PrimaryLink component. How do I do it?
I have attempted:
import styled from 'styled-components';
const Link = () => /* ... */;
const PrimaryLink = styled(Link)`
color: #666;
`;
const ExternalLink = styled.a`${PrimaryLink}`;
but that produces empty styles.
The only way I can think of to make this work is by adding a third variable css`` that would extend both components, e.g.
import styled, {css} from 'styled-components';
const BaseLinkStyle = css`
color: #666;
`;
const Link = () => /* ... */;
const PrimaryLink = styled(Link)`${BaseLinkStyle}`;
const ExternalLink = styled.a`${PrimaryLink}`;

I tend to do what you suggest, i.e. using a style function:
import styled, { css } from 'styled-components';
const baseStyle = css`
...
`;
const Link = () => { ... };
const PrimaryLink = styled(Link)`
${baseStyle}
...
`;
const ExternalLink = styled.a`
${baseStyle}
...
`;
I think this would also work:
https://styled-components.com/docs/api#as-polymorphic-prop
https://styled-components.com/docs/api#attrs
import styled from 'styled-components';
const Link = () => { ... };
const PrimaryLink = styled(Link)`
${baseStyle}
...
`;
const ExternalLink = styled(PrimaryLink).attrs({ as: 'a' })`
...
`;

Related

how to ensure data is not null before passing it as props?

I have created a generic component to be used in 2 cases. 1 case when dealing with single piece of data the other when dealing with an array of data. I am trying to plot this data on a react leaflet map. Right now it works for my landingPage component which deals with the single plots of data.
Previously I had it also working for my array of data before I was passing props to generic component to render. The issue is when I try to load the page responsible for displaying the map with the array of data it returns null when the getInitPosition() function is called as the props data seems to be null when component is rendered but not null after it, I checked this through logging to console. I am confused as to how it works in the single component and not the array of data component as the calls to retrieve the data are very similar. Can anyone see where I am going wrong. It seems to be that although my polyineArray is set with correct values I then print out the polylines state to check if it is set after the call to setPolylines(polylineArray) but it seems to be empty and I do not know why?
Map array of data component:
import react from 'react';
import { useState, useEffect} from 'react';
import { MapContainer, TileLayer, Popup, Polyline} from 'react-leaflet';
import axios from 'axios';
import polyline from '#mapbox/polyline';
import MapComp from './MapComp';
function Mapp() {
const [activities, setActivities] = useState([]);
const [polylines, setPolylines] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setActivitieData();
}, [])
useEffect(() => {
if(activities.length) {
setPolylineArray();
setIsLoading(false);
}
}, [activities])
const getActivityData = async () => {
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
};
const setPolylineArray = () => {
const polylineArray = []
for(let i = 0; i < activities.length; i++) {
const polylineData = activities[i].map.summary_polyline;
const activityName = activities[i].name;
const activityType = activities[i].type
polylineArray.push({positions: polyline.decode(polylineData), name: activityName, activityType: activityType });
} // should push activity type as well
setPolylines(polylineArray);
//setIsLoading(false);
console.log("Polyline array = ", polylineArray);
console.log("polylines = ", polylines) // this seems to be empty????
}
return (
!isLoading ?
<MapComp activityData={{polylines}} />
: <div><p>Loading...</p></div>
)
}
export default Mapp;
single data plotting component which works:
import react from 'react';
import { useState, useEffect } from 'react';
import Card from './Card';
import axios from 'axios';
import polyline from '#mapbox/polyline';
function LandingPage() {
const [cardActivites, setCardActivities] = useState([]);
const [polylines, setPolylines] = useState([]);
const [cards, setCards] = useState([]);
useEffect(() => {
setActivitieData();
}, [])
useEffect(() => {
if(cardActivites.length) {
setPolylineArray();
activityCards();
}
}, [cardActivites])
const getActivityData = async () => {
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setCardActivities(activityData);
};
const setPolylineArray = async () => {
const polylineArray = []
// right now activites refers to our button to show all activites
// we will eventually be using global state in redux
// for now we have a different state for card activties
for(let i = 0; i < cardActivites.length; i++) {
const polylineData = cardActivites[i].map.summary_polyline;
const activityName = cardActivites[i].name;
const activityType = cardActivites[i].type
polylineArray.push({positions: polyline.decode(polylineData), name: activityName, activityType: activityType });
} // should push activity type as well
setPolylines(polylineArray);
console.log("Polyline array = ", polylineArray);
}
const activityCards = async () => {
// set up polyline array
setCards(polylines.map((polyline, i) => {
return <Card key={i} activityData={{polyline}}/>
}))
console.log("cards = ", cards);
//return <Card activityData={{polylines}}/>
}
return (
<div>
<p>cards</p>
{cards}
</div>
)
}
export default LandingPage;
generic component: import react from 'react';
import { MapContainer, TileLayer, Popup, Polyline} from 'react-leaflet';
import polyline from '#mapbox/polyline';
import { useEffect, useState } from 'react';
function MapComp(props) {
function getInitPosition() {
console.log("props activity data = ", props);
if(!Array.isArray(props.activityData)) {
return [props.activityData.positions[0][0],props.activityData.positions[0][1]];
}
else {
return [props.activityData.poylines.positions[0][0],props.activityData.poylines.positions[0][1]];
}
}
return (
<MapContainer center={getInitPosition()} zoom={15} scrollWheelZoom={false} style={props.style}>
<TileLayer attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{!Array.isArray(props.activityData) && <Polyline positions={props.activityData.positions} >
<Popup>
<div>
<h2>{"Name: " + + props.activityData.name}</h2>
</div>
</Popup>
</Polyline>
}
{Array.isArray(props.activityData.polylines) && props.activityData.polylines.length > 1 &&
props.activityData.polylines.map((activity, idx) => (
<Polyline key={idx} positions={activity.positions}
<Popup>
<div>
<h2>{"Name: " + activity.name}</h2>
</div>
</Popup>
</Polyline>
))}
</MapContainer>
)
}
export default MapComp;
Can anyone see my issue why polylines isn't being set or how to ensure it is set before passing the data as props?

Only one element of type cardNumber can be created

I am trying to display my stripe component, but I am getting this error:
IntegrationError: Only one element of type cardNumber can be created.
I don't know why, since I'm only using it once in my entire app
Any ideas?
This is my index
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import MyComponent from './components/StripeComponent';
const promise = loadStripe("xxx-xxx-xxx");
ReactDOM.render(
<React.StrictMode>
<Elements stripe={promise}>
<MyComponent/>
</Elements>
</React.StrictMode>,
document.getElementById('root')
);
And this is my component
import React from "react";
import {
useElements,
} from "#stripe/react-stripe-js";
const MyComponent: React.FC= (props)=>{
const elements = useElements();
const cardNumberElement = elements?.create('cardNumber', {
placeholder: ''
});
const cardExpiryElement = elements?.create('cardExpiry', {
placeholder: ''
});
const cardCvvElement = elements?.create('cardCvc', {
placeholder: ''
});
cardNumberElement?.mount('#card-number-element')
cardExpiryElement?.mount('#card-expiry-element')
cardCvvElement?.mount('#card-cvv-element')
const handleSubmit = async (ev: any) => {
ev.preventDefault();
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<div id="card-number-element"></div>
<div id="card-expiry-element"></div>
<div id="card-cvv-element"></div>
</form>
);
}
export default MyComponent
Seems it is because you create and mount the card components in the functional component body they are executed on every render of the component, i.e. as an inadvertent side-effect.
Place the creation and mounting logic in an useEffect hook with an empty dependency array so that it is invoked only once when the component mounts.
import React, { useEffect } from "react";
import { useElements } from "#stripe/react-stripe-js";
const MyComponent: React.FC = (props) => {
const elements = useElements();
// Effect hook to run once on component mount
useEffect(() => {
const cardNumberElement = elements?.create("cardNumber", {
placeholder: ""
});
const cardExpiryElement = elements?.create("cardExpiry", {
placeholder: ""
});
const cardCvvElement = elements?.create("cardCvc", {
placeholder: ""
});
cardNumberElement?.mount("#card-number-element");
cardExpiryElement?.mount("#card-expiry-element");
cardCvvElement?.mount("#card-cvv-element");
}, []); // <-- empty dependency array
const handleSubmit = async (ev: any) => {
ev.preventDefault();
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<div id="card-number-element"></div>
<div id="card-expiry-element"></div>
<div id="card-cvv-element"></div>
</form>
);
};
useEffect(() => {
if (elements) {
const cardNumberElement =
elements.getElement("cardNumber") || // check if we already created element
elements.create("cardNumber", defaultInputStyles); // create if dont
cardNumberElement.mount("#numberInput");
}
}, [elements]);
I had the same problem, in my case, I had a reference to CardNumberElement in another section of my code. After removing it, everything worked fine.

Next.js: document is not defined

I am trying to create a payment form where people can pay but I keep getting this error.
document is not defined
I'm using Next.js. Please see my code below:
import React from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';
var stripe_load = () => {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
};
function Payment({host}) {
const key = host.includes('localhost') ? 'test' : 't';
stripe_load();
const router = useRouter();
return (
<div className="Payment Main">
<StripeProvider apiKey={key}>
<Elements>
<CheckoutForm planid={router.query.id}/>
</Elements>
</StripeProvider>
<br/>
<br/>
<p>Powered by Stripe</p>
</div>
);
};
Payment.getInitialProps = async ctx => {
return { host: ctx.req.headers.host }
};
export default Payment
I think, in server rendering mode, the document is undefined.
You should be able to use it inside class lifecycle methods or useEffect
import React, {useEffect} from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';
var stripe_load = () => {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
};
function Payment({host}) {
const key = host.includes('localhost') ? 'test' : 't';
useEffect(() => {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
}, [])
//stripe_load();
const router = useRouter();
return (
<div className="Payment Main">
<StripeProvider apiKey={key}>
<Elements>
<CheckoutForm planid={router.query.id}/>
</Elements>
</StripeProvider>
<br/>
<br/>
<p>Powered by Stripe</p>
</div>
);
};
Payment.getInitialProps = async ctx => {
return { host: ctx.req.headers.host }
};
export default Payment
You need to wrap your document using validator process.browser, because this document is belong to client side, and the error occured when nextjs render in server side.
var stripe_load = () => {
if (process.browser) {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
}
};
For example, when the module includes a library that only works in the browser. Some times dynamic import can solve this issue.
Take a look at the following example:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
for me, this error occurs from lots of mistakes:
first, you have to use
if (typeof window !== "undefined") {
for every window. and document. and especially for localStorage. functions (my self forgot to use this if clause for localStorage.getItem and the error didn't fix)
another problem was the import of the line below which was totally wrong!:
import {router} from "next/client";
To access the document in Next.js you need await the page render first then get it in a variable, like so:
const [_document, set_document] = React.useState(null)
React.useEffect(() => {
set_document(document)
}, [])
You should read the difference between JS in node.js and JS in Browser. In node.js, you don't have DOM APIs (window, document, document.getElementById,...), the thing can be only have when your HTML is rendered in a thing called windows of Browsers. So next.js use node.js to run JS code and take result to render HTML file. But in node.js, nothing is windows of Browser.
My English is quite bad. But I hope this answer will be helpful for you.
you can use a new feature called dynamic imports with ssr off. this is a workaround for rerendering the component.
You have to make sure you have two things setup.
DOM render is completed.
Then load the function.
Step 1: create hook isMounted this will make sure that your DOM is rendered.
import React, {useEffect, useState} from 'react';
function RenderCompleted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
return () => {
setMounted(false)
}
});
return mounted;
}
export default RenderCompleted;
Inside function Payment load the hook :
import React, {useEffect} from "react";
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from '../../components/Payment/CheckoutForm';
import { useRouter } from 'next/router';
import RenderCompleted from '../hooks/isMounted';
var stripe_load = () => {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
};
function Payment({host}) {
const[key,setKey] = useEffect('');
//will help you on re-render or host changes
useEffect(()=>{
const key = host.includes('localhost') ? 'test' : 't';
setKey(key);
},[host])
useEffect(() => {
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = " https://js.stripe.com/v3/";
document.head.appendChild(aScript);
aScript.onload = () => {
};
}, [isMounted])
const router = useRouter();
const isMounted = RenderCompleted();
return (
<div className="Payment Main">
<StripeProvider apiKey={key}>
<Elements>
<CheckoutForm planid={router.query.id}/>
</Elements>
</StripeProvider>
<br/>
<br/>
<p>Powered by Stripe</p>
</div>
);
};
Payment.getInitialProps = async ctx => {
return { host: ctx.req.headers.host }
};
export default Payment
Another way to do this is, to use head component of next.js: https://nextjs.org/docs/api-reference/next/head
<Head>
<title>My page title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
</Head>
This might be helpful to some else, I got this error when Material UI anchorEl is true
const [anchoeEl, setAnchorEl] = useState(null)
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top"
horizontal: "right"
}}
/>

How to get the links in Draft js in read only mode?

I am creating a simple blog writing application. I am using Draft.js as an editor. I am able to create the link while writing the blog but when I go into read mode all the links are missing. Here are the React code for writing and reading the blogs. For simplicity I am storing the editorState/data in localStorage. Here is WriteBlog.js file
import React, { Component } from "react";
import Editor, { createEditorStateWithText } from "draft-js-plugins-editor";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import createLinkPlugin from "draft-js-anchor-plugin";
import createToolbarPlugin, { Separator } from "draft-js-static-toolbar-plugin";
import {
convertFromRaw,
EditorState,
RichUtils,
AtomicBlockUtils,
convertToRaw
} from "draft-js";
import { ItalicButton, BoldButton, UnderlineButton } from "draft-js-buttons";
import editorStyles from "./editorStyles.css";
import buttonStyles from "./buttonStyles.css";
import toolbarStyles from "./toolbarStyles.css";
import linkStyles from "./linkStyles.css";
import "draft-js-alignment-plugin/lib/plugin.css";
const staticToolbarPlugin = createToolbarPlugin();
const linkPlugin = createLinkPlugin({
theme: linkStyles,
placeholder: "http://…"
});
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: { buttonStyles, toolbarStyles }
});
const { Toolbar } = staticToolbarPlugin;
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [staticToolbarPlugin, linkPlugin];
const text =
"Try selecting a part of this text and click on the link button in the toolbar that appears …";
export default class WriteBlog extends Component {
state = {
editorState: createEditorStateWithText(text)
};
onChange = editorState => {
let contentRaw = convertToRaw(this.state.editorState.getCurrentContent());
const stringyfyRawContent = JSON.stringify(contentRaw);
localStorage.setItem("blogcontent", JSON.stringify(contentRaw));
this.setState({
editorState
});
};
handleSave = async e => {
e.preventDefault();
// await this.setState({
// saveblog: 1,
// publish: 0
// });
// this.props.create_post(this.state);
// console.log("save state", this.state);
localStorage.setItem(
"blogsaveblog",
JSON.stringify(this.state.editorState)
);
};
focus = () => this.editor.focus();
render() {
return (
<div className={editorStyles.editor} onClick={this.focus}>
<form onSubmit={this.handleSave}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
ref={element => {
this.editor = element;
}}
/>
<Toolbar>
{// may be use React.Fragment instead of div to improve perfomance after React 16
externalProps => (
<div>
<BoldButton {...externalProps} />
<ItalicButton {...externalProps} />
<UnderlineButton {...externalProps} />
<linkPlugin.LinkButton {...externalProps} />
</div>
)}
</Toolbar>
<button
type="submit"
className="btn btn-primary"
style={{ margin: "10px" }}
>
Save
</button>
</form>
</div>
);
}
}
and here is ReadBlog.js file
import React, { Component } from "react";
import Editor, { createEditorStateWithText } from "draft-js-plugins-editor";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import createLinkPlugin from "draft-js-anchor-plugin";
import createToolbarPlugin, { Separator } from "draft-js-static-toolbar-plugin";
import { convertFromRaw, EditorState, convertToRaw } from "draft-js";
import { ItalicButton, BoldButton, UnderlineButton } from "draft-js-buttons";
import editorStyles from "./editorStyles.css";
import buttonStyles from "./buttonStyles.css";
import toolbarStyles from "./toolbarStyles.css";
import linkStyles from "./linkStyles.css";
import "draft-js-alignment-plugin/lib/plugin.css";
const staticToolbarPlugin = createToolbarPlugin();
const linkPlugin = createLinkPlugin({
theme: linkStyles,
placeholder: "http://…"
});
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: { buttonStyles, toolbarStyles }
});
const { Toolbar } = staticToolbarPlugin;
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [staticToolbarPlugin, linkPlugin];
const text =
"Try selecting a part of this text and click on the link button in the toolbar that appears …";
export default class ReadBlog extends Component {
state = {
editorState: createEditorStateWithText(text)
};
componentDidMount = () => {
const rawContentFromdb = convertFromRaw(
JSON.parse(localStorage.getItem("blogcontent"))
);
const initialEditorStatedb = EditorState.createWithContent(
rawContentFromdb
);
this.setState({ editorState: initialEditorStatedb });
};
focus = () => this.editor.focus();
render() {
return (
<div className={editorStyles.editor} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
plugins={plugins}
readOnly={true}
ref={element => {
this.editor = element;
}}
/>
</div>
);
}
}
I know this is super late, but you are not adding the decorator which is why this is not working. In this case, you'll want to use compositeDecorator to build your decorator object, and initialize the react state with it.
const decorator = new CompositeDecorator([
{
strategy: linkStrategy,
component: LinkDecorator,
},
]);
const [editorState, setEditorState] = useState(() =>
EditorState.createWithContent(initialContentState, decorator),
);

react-stockchart: cannot convert JSX to TSX

I have an example of the react-stockchart chart in JSX:
JSX plunker: http://plnkr.co/edit/gist:b993d5fcc5c09dd66a6e?p=preview
I want to add JSX to an existing TypeScript project, so I changed the file extension from JSX to TSX and did other convertions from this site:
http://slidedeck.io/thewazir/Using-Typescript-with-JSX
But the problem still remains, this code doesn't compile:
CandleStickChartWithBollingerBandOverlay = fitWidth(CandleStickChartWithBollingerBandOverlay);
ReactDOM.render(<CandleStickChartWithBollingerBandOverlay data={data} type="hybrid"/>, document.getElementById("chart"));
fitWidth source: https://github.com/rrag/react-stockcharts/blob/master/src/lib/helper/fitWidth.jsx
If I remove fitWidth it is drawn with incorrect width:
ReactDOM.render(<CandleStickChartWithBollingerBandOverlay data={data} type="hybrid" width={800}/>, document.getElementById("chart"));
I tried this code, it doesn't work (nothing is drawn at all):
var StockChartComponent = fitWidth(CandleStickChartWithBollingerBandOverlay);
ReactDOM.render(<StockChartComponent data={data} type="hybrid"/>, document.getElementById("chart"));
And this doesn't work too:
var StockChartComponent = fitWidth(new CandleStickChartWithBollingerBandOverlay());
ReactDOM.render(<StockChartComponent data={data} type="hybrid"/>, document.getElementById("chart"));
This is the main tsx file:
export function initialize(data, element) {
var StockChartComponent = fitWidth(CandleStickChartWithBollingerBandOverlay);
ReactDOM.render(<StockChartComponent data={data} type="hybrid" height={800} />, element);
}
This is the fixed fitWidth.tsx:
import React = require('react');
import ReactDOM = require('react-dom');
export function fitWidth(WrappedComponent, withRef = true) {
class ResponsiveComponent extends React.Component<any, any> {
static getDisplayName(Series) {
var name = Series.displayName || Series.name || "Series";
return name;
}
static defaultProps = {
displayName: `fitWidth(${ResponsiveComponent.getDisplayName(WrappedComponent)})`
}
constructor(props) {
super(props);
this.handleWindowResize = this.handleWindowResize.bind(this);
this.getWrappedInstance = this.getWrappedInstance.bind(this);
}
componentDidMount() {
window.addEventListener("resize", this.handleWindowResize);
var el = ReactDOM.findDOMNode(this);
var w = (el.parentNode as Element).clientWidth;
/* eslint-disable react/no-did-mount-set-state */
this.setState({
width: w
});
/* eslint-enable react/no-did-mount-set-state */
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleWindowResize);
}
handleWindowResize() {
var el = ReactDOM.findDOMNode(this);
var w = (el.parentNode as Element).clientWidth;
this.setState({
width: w
});
}
getWrappedInstance() {
return (this.refs as any).component;
}
render() {
var ref = withRef ? { ref: "component" } : {};
if (this.state && this.state.width) {
return <WrappedComponent width={this.state.width} {...this.props} {...ref} />;
} else {
return <div />;
}
}
}
return ResponsiveComponent;
}

Resources