Next.js: document is not defined - node.js

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"
}}
/>

Related

NextJs mqtt app client does not update context in event handlers

I am developing mqtt web client for an IOT project using NextJs and mqtt package. In order to allow the client object to be shared among all components, I implemented a context API in which I defined some states as seen in the code below. The issue I am having here is, anytime I make update to msg state using setMsg function in the 'message' event handler, the msg does not get updated.
If I also try to publish a message by clicking a button, the message is not published. The only I was able to publish is by calling the client.publish intermittently inside setInterval
I am using the shiftr.io mqtt broker and I see an error stating that connection failed even though the shiftr.io dashboard indicated that connection is established by showing the client with its ID.
Thank you in advance.
** index.js file:**
import Car from '../components/Car';
import CarList from '../components/CarList';
import Board from '../components/Board';
import Notification from '../components/Notification';
import { useState, useEffect } from 'react';
import { useGlobalContext } from '../lib/context';
import mqtt from 'mqtt'
import Head from 'next/head'
export default function Home() {
// console.log(JSON.parse(client).connected)
const client = mqtt.connect('mqtt://tatwo:K1FADvdffhfff#tatwo.cloud.shiftr.io', {
clientId: 'client1'
})
const [freeSpace, setFreeState] = useState(1)
const {setSpaceStatus, setMqttClient, mqttClient, setMsg, msg} = useGlobalContext()
const [spaceMessageString, setSpaceMessageString] = useState(['0','0','0'])
const publishStatus = (msg, clt)=>{
client.publish('/reservation', msg)
}
if(client){
setMqttClient(client)
}
client.on('connect', function(){
console.log('connected')
client.subscribe('space')
})
client.on('message', function(topic, message){
console.log('receieved: ', message.toString().split(','))
// setSpaceMessageString(message.toString().split(','))
setMsg(message.toString())
// setSpaceStatus(message.toString().split(','))
})
useEffect(() => {
return ()=>{
if(mqttClient){
mqttClient.end()
}
}
}, [spaceMessageString])
return (
<div className='flex flex-col items-center justify-center'>
<h1 className='text-white text-3xl md:text-5xl font-extrabold text-center'>Parking without stress</h1>
<p className='text-amber-500 text-lg my-5'>Use smart parking system to check for parking space before you drive. </p>
<Board />
<p>{msg}: {mqttClient?.connected == true ? 'Onlined': 'offline'}</p>
{
freeSpace === 0 ?
// <Notification /> : <CarList spaceMessageString={spaceMessageString} />
<Notification /> : (
<div className='grid grid-cols-1 md:grid-cols-3 gap-4 my-5 w-full'>
{
spaceMessageString.map((space, index)=>
<Car
name={`Space ${index + 1}`}
message={spaceMessageString[index]}
key={index}
identity={index + 1}
publishStatus={publishStatus}
/>
)
}
</div>
)
}
</div>
)
}
** context.js: **
import React from 'react'
import { createContext, useContext, useState } from 'react'
const AppContext = createContext({});
const AppProvider = ({children}) => {
const [user, setUser] = useState(null)
const [reservations, setReservations] = useState([])
const [spaceStatus, setSpaceStatus] = useState([1, 0, 0])
const [connected, setConnected] = useState(false)
const [mqttClient, setMqttClient] = useState(null)
const [msg, setMsg] = useState('no message')
const client = null;
return (
<AppContext.Provider value={
{
user,
setUser,
spaceStatus,
setSpaceStatus,
reservations,
setReservations,
connected,
setConnected,
client,
setMqttClient,
mqttClient,
setMsg,
msg
}
}>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => useContext(AppContext);
export default AppProvider
** _app.js: **
import Layout from '../components/Layout'
import '../styles/globals.css'
import AppProvider from '../lib/context';
export default function App({ Component, pageProps }) {
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}

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?

Implementing a collaborative text editor using nodejs/react/socket but encountering problems because of slow heroku servers

I've tried making a collaborative editor using socket.io with reactjs frontend and node backend. Here's the piece of logic which I think is causing problems....
When a client starts typing on the editor, I've used onInput event to emit a socket response say "typing" which carries the complete text on the editor inside data object at the moment client presses a key. Now server catches this typing event and in response to that, emits another socket response called "typed" which contains the same data but the server sends this response to all the clients connected to the server.... Now all clients receive this event inside componentDidMount and then update the state variable "codeValue" which updates the editor content for all the clients.
There are two problems, first one that on one single typing event, server is emitting numerous typed events ( it happens only in heroku server and not on local host ) and the other problem is that heroku servers are slow and before the server sends response to update the state of clients, clients had already entered more text on the editor which simply vanishes when the state is updated.....
FRONTEND CODE:
import React from "react";
import { Dropdown } from "semantic-ui-react";
import languages from "../utils/languages";
//Styles
import "../styles/app.css";
//Editor
import * as ace from "ace-builds";
// import SocketIOClient from "socket.io-client";
import "ace-builds/src-noconflict/mode-c_cpp";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";
import AceEditor from "react-ace";
let check = true;
let ld;
// const endpoint = "http://localhost:4676";
// const socket = SocketIOClient(endpoint, { transports: ["websocket"] });
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
codeValue: languages[0].template,
currentLang: languages[0].key,
};
this.codeEditor = React.createRef();
this.fireTyping = this.fireTyping.bind(this);
this.onDDChange = this.onDDChange.bind(this);
this.runCode = this.runCode.bind(this);
this.handleOutput = this.handleOutput.bind(this);
}
componentDidMount() {
this.props.socket.on("typed", (data) => {
console.log(35, data.text)
this.setState({
codeValue: data.text,
});
check = true;
console.log(check)
});
this.props.socket.on('ans',(data) => {
console.log(data.output)
//handleOutput(data.output)
})
}
fireTyping = () => {
ld = this.codeEditor.current.editor.getValue()
//console.log(ld)
if(check) {
console.log(48, this.codeEditor.current.editor.getValue(), check);
this.props.socket.emit("typing", {
text: ld,
});
check = false;
}
console.log(check)
};
onDDChange = (e, data) => {
const selectedVal = languages.filter((v) => v.key == data.value)
this.setState({currentLang : data.value, codeValue: selectedVal[0].template})
}
runCode = () => {
this.props.socket.emit('run', {
code: this.codeEditor.current.editor.getValue(),
lang: this.state.currentLang,
input: ''
})
}
handleOutput = () => {
}
render() {
return (
<div>
<Dropdown
placeholder="Languages"
onChange = {this.onDDChange}
selection
value = {this.state.currentLang}
options={languages}
/>
<AceEditor
style={{
margin: "3rem auto",
width: "80vw",
height: "70vh",
}}
fontSize={18}
ref={this.codeEditor}
mode="c_cpp"
theme="github"
value={this.state.codeValue}
onInput={this.fireTyping}
showPrintMargin={false}
name="UNIQUE_ID_OF_DIV"
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
/>
<div className="container">
<button
onClick={this.runCode}
style={{
marginLeft: "40rem",
}}
className="large ui teal button"
>
Run
</button>
</div>
</div>
);
}
}
export default App;
BACKEND CODE:
const express = require("express");
const request = require("request");
const app = express();
const http = require("http");
const server = http.createServer(app);
const path = require('path')
const socket = require("socket.io");
const io = socket(server);
const port = process.env.PORT || 4676
app.use(express.static(path.join(__dirname, 'client/build')))
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})
io.on("connection", (socket) => {
let previousCode, currentCode;
console.log(socket.id);
socket.on("typing", (data) => {
currentCode = data.text
console.log('typing')
console.log(previousCode === currentCode)
if(previousCode !== currentCode){
console.log(1)
io.emit("typed", data);
previousCode = currentCode;
currentCode = ''
}
});
});
server.listen(port, () => {
console.log("server started at http://localhost:4676");
});
I've spent hours trying to fix this but I couldn't.... Any help would be appreciated ☺️
Let me know if you need code reference, I'll share the repository

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.

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),
);

Resources