I am using CkEditor5 for implementing CKEditor in my React application. I want to update the config wrt my state. But it seems like the editor component isn't updating even after the state change.
Here is my CKEditor component
<CKEditor
editor={ ClassicEditor }
data={this.state.content}
onInit={ editor => {
console.log( 'Editor is ready to use!', editor );
} }
config={{
simpleUpload: {
uploadUrl: uploadUrl,
headers: {
'X-CSRFToken': csrftoken,
}
}}}
onChange={ ( event, editor ) => {
const data = editor.getData();
this.setState({
content: data
})
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
I want to update the upload URL based on the state.
It is stored in a variable like shown below -
const csrftoken = getCookie('csrftoken');
const uploadUrl = `https://example.com/api/blog/posts/images/${this.state.id}`
I solved this by using ClassicEditor instead of the CKEditor component.
I did this by writing two function for this. First getEditorConfig which would give me the changed config based on other parameters.
getEditorConfig = () => {
const csrftoken = getCookie('csrftoken');
const debug = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"
const uploadUrl = debug ? `http://127.0.0.1:8000/api/blog/posts/images/${this.state.id}`
: `https://example.com/api/blog/posts/images/${this.state.id}`
return {
simpleUpload: {
uploadUrl: uploadUrl,
headers: {
'X-CSRFToken': csrftoken,
}
}}
}
Next a helper function which creates a new editor instance and adds it to the DOM. I am storing the editor instance in the state.
addEditor = () => {
const config = this.getEditorConfig();
ClassicEditor
.create( document.querySelector( '#editor' ), config)
.then(newEditor => this.setState({editor: newEditor}))
.catch( error => {
console.log( error );
});
}
In one of the issues its suggested to destroy the old editor and create a new one when you want to update the config. So for that use the following two lines to destroy and use addEditor to create a new editor.
const { editor } = this.state;
editor.destroy();
My solution consists in adding ref and custom states to the editor e using a custom upload adapter. Now, I'm able to send the current state data to server and without using headers to that.
<CKEditor name="content" ref={editorRef}
editor={Editor}
config={customEditorConfig}
data={article.content}
customPropArticle={article}
onReady={editor => {
editor.editorRef = editorRef;
}}
onChange={onEditorChangeHandle}
/>
From UploadAdapter:
_sendRequest() {
const editorRef = this.editor.editorRef;
const { customPropArticle } = editorRef.current.props;
this.loader.file.then(file => {
const data = new FormData();
data.append('id', customPropArticle.Id);
data.append('upload', file);
this.xhr.send(data);
});
Related
I added options to the existing toolbar in Gutenberg. I have a lot of options, so I thought that they would be automatically registered. The tasks are easy; every task has to call OpenAI based on the selected block, but every time it calls, it should use the latest text. I am not Frontend developer so I have little problem with understand where I made mistake, how it should be improve?
Component:
const AiBlockEdit = (props) => {
const [registered, _] = useState(() => {
let localRegistered = [];
FORMAT_LIST.forEach((format) => {
if (!localRegistered.includes(format)) {
let result = register(new OpenAI(globalOpenAi), wrappProps(format, props));
localRegistered.push(result);
}
});
return localRegistered;
});
return (
<>
<BlockControls>
<ToolbarDropdownMenu
icon={ AI }
label="Select a direction"
controls={ registered }
/>
</BlockControls>
</>
);
};
Logic:
export const wrappProps = (item, props) => {
return {
...item,
props: props
}
}
export function register(api, item) {
const {value, onChange} = item.props;
// Build message
let promptFormatType = new BuildPrompt()
.addText(item.input)
.addText(" ")
.addText(value.text)
.build();
// Prepare openai settings, use default
let requestParams = new OpenAIParams().setPrompt(promptFormatType).build();
let request = new item.Query(requestParams);
// Build settings
return new SettingsFormatType()
.setIcon(item.icon)
.setTitle(item.title)
.setOnclick(() => {
api.send(request, (response) => {
let text = response.data.choices[0].text;
onChange(insert(value, value.text + text));
// let xd = create({
// text: response.data.choices[0].text,
// })
// onChange(item.fn(value, xd));
});
}).build();
}
I am working on an Electron app that uses React on the front end. The app will display files from a couple folders. Then it will allow the user to delete files or copy files to another folder with some buttons. I have figured out how to get the files deleted or copied using buttons. But I can't figure out how to update the GUI after the files have been deleted or copied. I've been following this tutorial: https://medium.com/jspoint/working-with-files-i-o-in-an-electron-application-b4d2de403f54
Here is the relevant portion of my backend electron.js:
// Module to control the application lifecycle and the native browser window.
const {
app,
BrowserWindow,
protocol,
nativeTheme,
ipcMain,
} = require("electron");
const path = require("path");
const url = require("url");
const io = require("../electron/io");
// Create the native browser window.
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
autoHideMenuBar: true,
resizable: false,
// Set the path of an additional "preload" script that can be used to
// communicate between node-land and browser-land.
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
nativeTheme.themeSource = "dark";
// return list of current front end files
ipcMain.handle("app:get-current-FE-files", () => {
return io.getFiles("C:/test");
});
// return list of previous front end files
ipcMain.handle("app:get-previous-FE-files", () => {
return io.getFiles("C:/test-previous-files");
});
ipcMain.handle("app:delete-all-folder-contents", (event, folder) => {
// console.log({ event });
// console.log({ folder });
let folderWithFiles = '';
if (folder === 'prodorchtest-previous-folder') {
folderWithFiles = 'C:/test-previous-files';
}
return io.deleteAllFilesInFolder(folderWithFiles);
});
ipcMain.handle("app:copy-all-folder-contents", (event, fromTo) => {
console.log({ fromTo })
if (fromTo === 'prodorchtest-to-previous-folder') {
return io.copyFilesBetweenFolders('C:/test', 'C:/test-previous-files');
}
});
// In production, set the initial browser path to the local bundle generated
// by the Create React App build process.
// In development, set it to localhost to allow live/hot-reloading.
const appURL = app.isPackaged
? url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
: "http://localhost:3006";
mainWindow.loadURL(appURL);
io.watchFiles(mainWindow);
Then in io.js, this is what watchFiles looks like:
// watch files from the application's storage directory
exports.watchFiles = (win) => {
const prevFEFolder = 'C:/test-previous-files';
// chokidar.watch(prevFEFolder).on("unlink", (filepath) => {
// console.log('unlink called with: ', filepath)
// //win.webContents.send("app:delete-file", path.parse(filepath).base);
// });
chokidar.watch(prevFEFolder).on('add', (path) => {
console.log({ path });
win.webContents.send("app:add-file", path);
});
};
Here is my preload.js:
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require("electron");
// As an example, here we use the exposeInMainWorld API to expose the browsers
// and node versions to the main window.
// They'll be accessible at "window.versions".
process.once("loaded", () => {
contextBridge.exposeInMainWorld("versions", process.versions);
});
contextBridge.exposeInMainWorld("fsInfo", {
getCurrentFEDirContents: () => ipcRenderer.invoke("app:get-current-FE-files"),
getPreviousFEDirContents: () =>
ipcRenderer.invoke("app:get-previous-FE-files"),
});
contextBridge.exposeInMainWorld("fsActions", {
deleteAllFolderContents: (folder) => ipcRenderer.invoke("app:delete-all-folder-contents", folder),
copyAllFolderContents: (fromTo) => ipcRenderer.invoke("app:copy-all-folder-contents", fromTo),
});
I'm not sure if that is where I should be adding my frontend functionality to watch for the file changes or if it should be in App.js.
And here is the first part my App.js:
import React, { useEffect, useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faArrowRight, faFileImage } from '#fortawesome/free-solid-svg-icons';
import folder from './img/folder.png';
import ErrorBoundary from './ErrorBoundary';
// this import causes errors
// const { ipcRenderer } = require('electron');
const App = () => {
// these state arrays are what I would want to update when changes are made with deleting
// or copying files
const [currentFEFiles, setCurrentFEFiles] = useState([]);
const [previousFEFiles, setPreviousFEFiles] = useState([]);
// useEffect(() => {
// // Listen for the event
// ipcRenderer.on('app:add-file', (event, arg) => {
// console.log({event, arg})
// });
// // Clean the listener after the component is dismounted
// return () => {
// ipcRenderer.removeAllListeners();
// };
// }, []);
const getTheFiles = async () => {
try {
const currentFilelist = await window.fsInfo.getCurrentFEDirContents();
console.log({ currentFilelist });
currentFilelist.sort((a, b) =>
a.isDir < b.isDir ? 1 : b.isDir < a.isDir ? -1 : a.name - b.name
);
setCurrentFEFiles(currentFilelist);
const previousFileList = await window.fsInfo.getPreviousFEDirContents();
previousFileList.sort((a, b) =>
a.isDir < b.isDir ? 1 : b.isDir < a.isDir ? -1 : a.name - b.name
);
setPreviousFEFiles(previousFileList);
} catch (e) {
console.log({ e });
setCurrentFEFiles([]);
}
// setCurrentFEFiles([]);
};
useEffect(() => {
getTheFiles();
//window.fsInfo.getDirContents().then(r => console.log({r}));
// console.log({ filelist });
}, []);
const deleteFolderContents = async (folder) => {
await window.fsActions.deleteAllFolderContents(folder);
};
const copyFolderContents = async (fromTo) => {
await window.fsActions.copyAllFolderContents(fromTo);
// maybe this is where I would add some code to update the state?
};
The problem should be just in the React part.
After reviewed your code App.js, it seems that your code does not inform React to rerender after files change.
The useEffect here
useEffect(() => {
getTheFiles();
//window.fsInfo.getDirContents().then(r => console.log({r}));
// console.log({ filelist });
}, []); // '[]' here means run only once
runs only once. It won't refresh your UI everytime your file changes.
Even though you have state arrays changed in getTheFiles, the getTheFiles got no chance to get reinvoked.
you could try just add getTheFiles() in both deleteFolderContents, copyFolderContents below the await line, because those setXXX in getTheFiles will trigger UI rerender.
const deleteFolderContents = async (folder) => {
await window.fsActions.deleteAllFolderContents(folder);
getTheFiles();
};
const copyFolderContents = async (fromTo) => {
await window.fsActions.copyAllFolderContents(fromTo);
getTheFiles();
};
I would like to parse a pdf file in a React app. The pdf will be provided through a html input.
I used pdf-parse - a wrapper around pdf.js in node - without any problem. But when it comes to React, I only receive this error:
MissingPDFException {message: 'Missing PDF "http://localhost:3000/myfile.pdf".', name: 'MissingPDFException'}
I upload the file like this:
export default function Home() {
const [data, setData] = useState();
const handleFile = (e) => {
const file = e.target.files[0];
const fileReader = new FileReader();
fileReader.onload = (d) => {
setData(new Uint32Array(d.target.result));
};
};
return (
<>
<h1>hello!</h1>
<input
type="file"
accept="application/pdf"
placeholder="insert PDF here"
onChange={(e) => handleFile(e)}
/>
<PDFViewer pdfFile={data} />
</>
);
}
And The file is supposed to be read here:
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, []);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I doesn't seem to work at all. I have a custom config with webpack, typescript and SWC-loader. I have read all the related stackoverflow threads.
How to properly parse a PDF with PDF.js in React? If there is a better library, I'm open to any suggestions. My goal is not to display the pdf, but to get its content.
Your component only runs getPDFDoc on mount since pdfFile is missing in the usecallback deps, so when the file changes, it probably doesn't even notice as your effect won't re-run since getPDFDoc is referentially stable when it shouldn't be.
Try
import * as PDFJS from "pdfjs-dist/build/pdf";
import * as pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
window.PDFJS = PDFJS;
export default function PDFViewer({ pdfFile }) {
PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const getPDFDoc = useCallback(async () => {
if (!pdfFile) return
const doc = await PDFJS.getDocument(pdfFile);
doc.promise.then(
(loadedPdf) => {
setPdfRef(loadedPdf);
},
function (reason) {
console.error(reason);
}
);
}, [pdfFile]);
useEffect(() => {
getPDFDoc();
}, [getPDFDoc]);
I think the reason for the weird "myfile.pdf" thing is probably because when it first runs pdfFile is not defined and this might be some internal library default. So I also added a guard to not do anything when it's not set.
Ok I am at my wits end here. I am using FilePond with React in a functional component using Hooks. I am not using FilePond to handle the actual upload, just setting the state with the files, my simplified version is this:
The Filepond:
<FilePond
onupdatefiles={fileItems => handleFilepondUpdate(fileItems)}
/>
</Form.Field>
The handle update:
const handleFilepondUpdate = fileItems => {
if (fileItems.length === 0) {
addAttachment({
...picture,
bugAttachment: null
});
} else {
addAttachment({
...picture,
bugAttachment: fileItems[0].file
});
}
};
The state:
const [picture, addAttachment] = useState({
bugAttachment: ""
});
const { bugAttachment } = picture;
And finally my upload and clear the input state:
const onSubmit = e => {
e.preventDefault();
const fd = new FormData();
fd.append("email", props.user[0].email);
fd.append("bugDescription", bugDescription);
fd.append("bugAttachment", bugAttachment);
addBug(fd).then(() => {
setBug({
bugDescription: ""
});
});
};
So how would I go about removing the FilePond file after the form is sent through?
Try clearing the bugAttachment property inside onSubmit using addAttachment hook
const onSubmit = e => {
e.preventDefault();
const fd = new FormData();
fd.append("email", props.user[0].email);
fd.append("bugDescription", bugDescription);
fd.append("bugAttachment", bugAttachment);
addBug(fd).then(() => {
setBug({
bugDescription: ""
});
addAttachment({
...picture,
bugAttachment:""
});
});
};
Update:
It seems like that you have not used the files prop with your picture state,Try something like this.
<FilePond
files={bugAttachment}
onupdatefiles={fileItems => handleFilepondUpdate(fileItems)}
/>
I'm working on a raise invoice page, in which user can raise a invoice on clicking of a button, I would call a api call and after getting the response I want to send some data to a page(RaisedInvoice.jsx) which should open in a new tab, how can i do it. The thing which I am not getting is how to open a page in new tab on click of a button in ReactJs.
RaiseInvoice.jsx:
import React from 'react';
import Links from './Links.jsx';
import history from './history.jsx';
import axios from 'axios';
class RaiseInvoice extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.state = {projects: [], searchParam : ''};
this.raiseInvoiceClicked = this.raiseInvoiceClicked.bind(this);
}
raiseInvoiceClicked(){
// here i wish to write the code for opening the page in new tab.
}
render() {
return (
<div>
<Links activeTabName="tab2"></Links>
<div className="container">
<div className = "row col-md-4">
<h1>Raise Invoice...</h1>
</div>
<div className = "row col-md-4"></div>
<div className = "row col-md-4" style ={{"marginTop":"24px"}}>
<button type="button" className="btn btn-default pull-right" onClick={this.raiseInvoiceClicked}>Raise Invoice</button>
</div>
</div>
</div>
)
}
}
export default RaiseInvoice;
Since you were going to send big data, appending them to your target URL looks shabby. I would suggest you use 'LocalStorage' for this purpose. So your code looks like this,
raiseInvoiceClicked(){
// your axios call here
localStorage.setItem("pageData", "Data Retrieved from axios request")
// route to new page by changing window.location
window.open(newPageUrl, "_blank") //to open new page
}
In your RaisedInvoice.jsx, retrieve the data from Local Storage like this,
componentWillMount() {
localStorage.pagedata= "your Data";
// set the data in state and use it through the component
localStorage.removeItem("pagedata");
// removing the data from localStorage. Since if user clicks for another invoice it overrides this data
}
You can just use plain JS to do it and append some query perimeters with it
raiseInvoiceClicked(){
const url = 'somesite.com?data=yourDataToSend';
window.open(url, '_blank');
}
Instead of calling raiseInvoiceClicked() function inside onclick method, you can try
onClick="window.open('your_url')"
in your code.
Simply do this!
const openLinkInNewTab = ( url ) => {
const newTab = window.open(url, '_blank', 'noopener,noreferrer');
if ( newTab ) newTab.opener = null;
}
//...
return (
//...
<button onClick={ () => openLinkInNewTab('your.url')}> Click Here </button>
//...
)
You can open it in a new window using the following code.
Please note that for props you can pass any child components that should be rendered inside new window.
const RenderInWindow = (props) => {
const [container, setContainer] = useState(null);
const newWindow = useRef(null);
useEffect(() => {
// Create container element on client-side
setContainer(document.createElement("div"));
}, []);
useEffect(() => {
// When container is ready
if (container) {
// Create window
newWindow.current = window.open(
"",
"",
"width=600,height=400,left=200,top=200"
);
// Append container
newWindow.current.document.body.appendChild(container);
// Save reference to window for cleanup
const curWindow = newWindow.current;
// Return cleanup function
return () => curWindow.close();
}
}, [container]);
return container && createPortal(props.children, container);
};
Pass this data with props:
let href = '...url_to_redirect...'; let data_to_send = [...];
let api_href = '...url_api_where_sent_data.../?data_to_send';
export const DictDefaultOptions = (url=(api_href), method='GET') => {
let defaultOptions = {
url : url,
method : method,
mode : 'cors',
headers : {'Access-Control-Allow-Origin':'*'}
};
return defaultOptions };
let sentData = {
method: defaultOptions.method,
mode: defaultOptions.mode
};
send_data_to_api = () => {
let api_return = await fetch(api_href, sentData)
.then(response => response.json())
.then(responseText => {
data = (JSON.parse(JSON.stringify(responseText)))
})
.catch(error => {
console.log(`${requestURL} error: `, error)
});
do { await this.wait(100) } while(Object.keys(api_return).length <= 0);
if (Object.keys(api_return).length > 0) {
return window.open(href, "_blank")
}
};