Load image from project in react chrome extension - reactjs

I am trying to build a react chrome extension. I started off with the simple project found here. In its structure, the popup is contained in Modal.js and I would like to include an image in it which is contained in the project. However, to access that I must use chrome.runtime.getURL("image_file.png"), which I cannot access in Modal.js, but only in content.js. How should I get the image properly to Modal.js?
This is all activated when the browser action button is pressed, which calls this function within content.js:
function main() {
const extensionOrigin = 'chrome-extension://' + chrome.runtime.id
if (!location.ancestorOrigins.contains(extensionOrigin)) {
fetch(chrome.runtime.getURL('index.html'))
.then((response) => response.text())
.then((html) => {
const styleStashHTML = html.replace(
/\/static\//g,
`${extensionOrigin}/static/`
)
$(styleStashHTML).appendTo('body')
})
.catch((error) => {
console.warn(error)
})
}
}
The content of index.html is:
<div id="modal-window"></div>
but when the html is returned from the fetch it has expanded in the build to:
<div id="modal-window"></div><script src="/static/js/runtime-main.0d1674f1.js"></script><script src="/static/js/2.021a85b4.chunk.js"></script><script src="/static/js/main.d80831e3.chunk.js"></script>
It is unclear to me how index.js is getting called but it is, which finds the div in index.html and replaces it with the modal object as follows:
import React from 'react'
import ReactDOM from 'react-dom'
import Modal from './Components/Modal'
ReactDOM.render(<Modal />, document.getElementById('modal-window'))
My current implementation of modal.js is as follows. Obviously the img src won't work as it is right now (needing to use chrome.runtime.getURL):
import React from 'react'
const Modal = () => {
return <img src="icon48.png" alt="icon48"></img>
}
export default Modal
How would I actually be able to get the image src from chrome.runtime.getURL?

Figured this out. I don't know if this is the best solution, but it is working for me.
Since I need to access chrome.runtime.getURL I need to do that from the content script. But I need the value of that in my component which doesn't have access to the chrome api. So I message between them through window events. Here is some example code:
ExampleComponent.js
import React, { Component } from 'react'
let imgSrc = 'file.png'
// Note that this listener is outside the Component
window.addEventListener('ToComponent', (msg) => {
imgSrc = msg.detail
})
class ExampleComponent extends Component {
constructor(props) {
super(props)
// Note that this is occurring inside the Component constructor
var event = new CustomEvent('FromComponent')
window.dispatchEvent(event)
this.state = {
imgSrc: imgSrc,
// etc.
}
}
render() {
return (
<img src={this.state.imgSrc} alt=""></img>
)
}
}
content.js:
window.addEventListener('FromComponent', () => {
const imgSrc = chrome.runtime.getURL('file.png')
// Send response
var event = new CustomEvent('ToComponent', { detail: imgSrc })
window.dispatchEvent(event)
})

Content scripts can use chrome.runtime.getUrl().
https://developer.chrome.com/docs/extensions/mv3/content_scripts/
You'll need to declare the images in your manifest.json file using web_accessable_resources.
For example, mine are all copied to a img folder:
"web_accessible_resources": [
{
"resources": ["/img/*"],
"matches": ["https://*"]
}
if using webpack, you'll need to copy them to your build as well:
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets/img',
to: 'img',
force: true,
},
],
}),
Then in the component call getUrl and include it in render's return:
const logo = chrome.runtime.getURL('img/logo.png');
return ( <img src={ logo } className="app-logo" alt="logo" /> );

Related

get a component of a function that returns an svg component react

I would like to convert the code of an svg into a react component knowing that the svg comes from a db table.
So I have a function that returns me a react component of my svg but I cannot use it in the rest of my code
import svgr from '#svgr/core'
const ColorPicker = (props) => {
svgr(props.svg, { icon: true }, { componentName: 'MyComponent' }).then(
(jsCode) => {
console.log(jsCode)
},
)
return (
<div className="colorpickerdisplay">
<button className="Color1"/>
</div>
)
}
export default ColorPicker;
jscode's console.log displays the react component well, but I cannot display it in the div, for example. could you help me? have a good day

How do I make a component to render only when API call is completed?

I have a 1-year React application that uses Server-Side Rendering and now we're developing a page that will be indexed by Googlebot.
The problem is: we need the response of an async api call to render a page within that data for SEO purposes. Googlebot (view-page-source) must not have a Loading... component or anything else. Its content MUST be that api data.
However all the examples/solutions that I found about it told to use componentDidMount, componentWillMount (deprecated) or even constructor, but all of them renders the page without the data first and Googlebot won't wait for it to finish.
Example:
API response:
{ trip: { description: 'It was great', title: 'The Trip! 2.0' } }
The component:
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
render() {
return (
<div>
{
this.state.data ? <h1>{this.state.data.title}</h1> : <p>none</p>
}
</div>
);
}
};
export default HomePage;
Desired source code on all renders:
<h1>The Trip! 2.0</h1>
NEVER: <p>none</p>
PS: the api call is not being simulated on this example cause idk where to put it =x
What could I do to solve this problem considering that the component must NOT render without the api response? Is that possible? Thank you all!
Inside of each component you have to define a function, let's name it as loadData. this function will do async work of your component.
when your server gets a request, you have to look at that Url and then you have to decide which components to render.
For each component that needs to be rendered, we will call that loadData function that is attached to each of components to initiate the data loading process. The key here is that we are not doing some initial render of the application. We just have a set of components. Each one says “here is a resource that I need”. Then whenever someone makes a request, we look at the set of components that we should need to render to get the page to show up. Then we will take all those components, we will take these little data loading requirement functions that are attached to them and we will call each of those. All these dataLoad functions return promise, so we have to detect all of them resolved before we render our app.
Let's say we have Users.js so we define our function as follow:
const loadData = store => {
return store.dispatch(fetchUsers());
};
when we import our component, we import inside an object.
export default {
loadData:loadData,
component: connect(mapStateToProps, { fetchUsers })(UsersList)
};
We have to set up our Routes.js file with the help of react-router-config.
Routes.js
import React from "react";
import Home from "./pages/Home";
import Users from "./pages/UsersList";
import App from "./App";
import NotFoundPage from "./pages/NotFoundPage";
export default [
{
...App,
//App component will be shown inside each component.
//array of routes will be used inside the App.js down below
routes: [
{
path: "/",
...Home,
exact: true
},
{ ...UsersList, path: "/users" },
{ ...AdminsListPage, path: "/admins" },
{ ...NotFoundPage }
]
}
];
Here is the App.js
import React from "react";
import Header from "./components/Header";
import { renderRoutes } from "react-router-config";
import { fetchCurrentUser } from "./actions";
//this component is to render components that each component uses in common
//props.route is the child components that are passed from Routes.js
const App = ({ route }) => {
return (
<div>
<Header />
{renderRoutes(route.routes)}
</div>
);
};
export default {
component: App,
//this function is get called by redux so store is passed in
//as you might know Header should always know about authentication status
//we populate the store with the authentication info so Header can use it.
loadData: ({ dispatch }) => dispatch(fetchCurrentUser())
};
now we set up all the loadData functions, now it is time to invoke them when our server gets request. for this we will be using matchRoutes function from react-router-config.
app.get("*", (req, res) => {
const store = createStore(req);
//express does not touch routing. it delegates everything to react.
//I will just place the logic behind invoking loadData functions here.
//matchRoutes will look at the path and will return the array of components to be loaded.
//this is what matchRoutes function show `{ route: { loadData: [Function: loadData], path: '/users', component: [Object] },//component that we show
match: { path: '/users', url: '/users', isExact: true, params: {} } }
`
//
const promises = matchRoutes(Routes, req.path)
.map(({ route }) => {
return route.loadData ? route.loadData(store) : null;
}) //we got the array of loadData functions now we are invoking them
.map(promise => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve);
});
}
});
//Promise.all takes an array of promises and resolves when all of the items resolve
Promise.all(promises).then(()=>{
//here is when it is time to render your server-side code.
}).catch(e => console.log(e.message));
}

Import image dynamically in React component

I have an Articles component that shows a blog page with listed articles.
render() {
const articles = {
...this.state.articles
}
const article = Object.keys(articles).map(cur => {
return <Article
key={this.state.articles[cur].id}
imgName={this.state.articles[cur].thumb}
title={this.state.articles[cur].title}
meta={this.state.articles[cur].meta}
clicked={() => this.detailedHandler(this.state.articles[cur].id)}
detailed={this.state.articles[cur].detailed} />
});
As you can see I pass image name with props to Article component.
I want then to display the appropriate image for each article.
How do I import an image in Article component based on the props I receive (props.imgName) from Articles component?
I used context.
const images = require.context('../../../assets/img', true);
loadImage = imageName => (assets(`./${imageName}`).default);
<img src={loadImage("someimage.png")} alt="" />
I don't know if this is an optimal solution, but it works.
You can load images dynamically from the API response with dynamic imports that is Stage 3 proposal as of now.
The resulting code should look something like:
loadImage = imageName => {
import(`./assets/${imageName}.jpg`).then(image => {
this.setState({
image
});
});
};
render() {
const { image } = this.state;
return (
<Fragment>
{image && <img src={image} alt="" />}
</Fragment>
);
}
View Codesandbox Demo
This feature is supported out of the box in create-react-app, If using other systems, you can use the Babel plugin
For anyone looking for a modern approach using async-await and custom react hooks, I found a pretty slick solution. Create a file called useImage.js and paste the following code:
import { useEffect, useState } from 'react'
const useImage = (fileName) => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [image, setImage] = useState(null)
useEffect(() => {
const fetchImage = async () => {
try {
const response = await import(`../assets/img/${fileName}`) // change relative path to suit your needs
setImage(response.default)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
}
fetchImage()
}, [fileName])
return {
loading,
error,
image,
}
}
export default useImage
Then just import the custom hook into your image component, mine looks something like this:
import useImage from '../../hooks/useImage'
import Typography from './Typography' // simple plain-text react component
const Image = ({ fileName, alt, className, ...rest }) => {
const { loading, error, image } = useImage(fileName)
if (error) return <Typography>{alt}</Typography>
return (
<>
{loading ? (
<Typography>loading</Typography>
) : (
<img
className={`Image${
className
? className.padStart(className.length + 1)
: ''
}`}
src={image}
alt={alt}
{...rest}
/>
)}
</>
)
}
export default Image
The nice thing about this solution is that no matter where your component is in relation to your assets folder, the react hook is always in the same place, so the relative path stays the same.
there are my way, works nicely:)
import React, {useState} from "react"
const getFavorites = (props) => {
const {item, favouritesNote} = props;
const [image, setImage] = useState("");
(function (imageName) {
import(
`../../../../assets/images/chart/favorite${imageName ? "_active" : ""}.png`
).then((image) => setImage(image.default));
})(favouritesNote[item.id]);
return (
<div>{image && <img alt="" className="img-responsive" src={image} />}</div
)
}
Note: All these comments work complementary with the #jadenRose hook solution which is a great abstraction.
You can import the images dynamically with the native js dynamic import('') , there's no need for React Lazy.
However, you have to be aware of the path/content you pass to the import(path) because depending on how you are planning to include the images to import in the final bundle there could be restrictions. There are two main ways:
Note: This is for Rollup, in Webpack i didn't try it but should be similar
a)- Making rollup automatically include the possibles files to import while creating the bundle using the official plugin https://www.npmjs.com/package/#rollup/plugin-dynamic-import-vars please read the doc and notice how there are important restrictions in the 'path' string, essentially you should just set the file name in the variable, the rest of the path have to be fixed in the import('') in order to provide rollup a restricted scope to import. eg:
OK
import(../assets/img/${fileName}.svg)
Wrong
import(filePath)
b)- Include in the bundle programmatically the files you can dynamically import example
//rollup.config.js
import copy from 'rollup-plugin-copy';
plugins: [
copy({
targets: [ { src: 'src/assets/icons/*', dest: 'lib/assets/icons' },],
}),
…
],
With this option b) you have no restrictions on the variable content but have to be careful with what you included in the bundle.
Conclusion: You can use dynamic import(...) but if you not properly handle the files inclusion on the bundle, it can be possible they are excluded and then the dynamic import will fail in the consumer.
I found this worked best for me:
I created an index file inside the images folder. there I imported all the images I have and created a class component with the variables assigned to each image import. this is because when we import an image in react using the import statement, that is, import someImage from './somewhere' react assigns the 'someImage' variable to a module with a 'static media' address, my terminology there might be wrong. Here is the example:
import image13_1 from './image13_1.png';
import image13_2 from './image13_2.png';
import image13_3 from './image13_3.png';
import image13_4 from './image13_4.png';
import image13_5 from './image13_5.png';
import image13_6 from './image13_6.png';
import image13_7 from './image13_7.png';
export class IMG{
1= image13_1
2 = image13_2
3 = image13_3
4 = image13_4
5 = image13_5
6 = image13_6
7 = image13_7
}
export default IMG;
from here I just import the IMG class and create an instance of it and call the image number a property:
var img = new IMG()
console.log('img',img[1]

How can I replace Markdown-rendered HTML tags with React components?

At the moment, I'm converting the Markdown to HTML code using marked, then I replace some parts of it with React elements. This results in an array of HTML strings and React elements, which can be rendered indeed:
const prepareGuide = markdown => replaceToArray(
marked(markdown),
/<a href="SOME_SPECIAL_HREF".*?>(.*?)<\/a>/,
(match, label, slug) => <a href={`/${slug}`}>{label}</a>
)
const Guide = ({ guide }) =>
prepareGuide(guide.fields.text).map(
n => typeof n === 'object'
? n
: <span dangerouslySetInnerHTML={{ __html: n }} />
)
The problem with this, let's just call it workaround, is that every piece of HTML needs a wrapper element, like span (and uses dangerouslySetInnerHTML).
What I basically need is the ability to replace the rendered HTML elements with React components to add React functionality like Router links and other, custom elements.
Any other approaches?
Edit: The replaceToArray function I used is like String.prototype.replace, but returns an array (so any type can be returned)
Edit: Another approach I had was to render the HTML directly to the DOM (using dangerouslySetInnerHTML), and using the container element's ref to query all elements I want to replace. But, next problem: To render React components inside the HTML ref I have, I'd need another React root, which is possible, but unpractical, because I'd lose all the contexts (like Router), so I can't even properly use Router Links that way.
I was able to solve this as follows:
I kept using marked and dangerouslySetInnerHTML to directly set the HTML. Now, as described in the second approach, I used the ref to query the elements I want to replace. Now to be able to render React elements to the HTML, I just used the ReactDOM.render function.
The biggest problem with this was that components didn't have access to the app's context, since I now had multiple React roots. To solve this, I figured out that we can copy the context from one component to another: Is it possible to pass context into a component instantiated with ReactDOM.render?
So to be able to access the context in the component that renders the HTML, we need to set the component's contextTypes for the contexts we need to copy.
class Article extends Component {
static contextTypes = {
router: PropTypes.any
}
static propTypes = {
markdown: PropTypes.string
}
prepare(ref) {
const Provider = createContextProvider(this.context)
const links = Array.from(ref.querySelectorAll('a'))
links.forEach((link) => {
const span = document.createElement('span')
const { pathname } = url.parse(link.href)
const text = link.innerText
link.parentNode.replaceChild(span, link)
ReactDOM.render(
<Provider>
<Link to={pathname}>{text}</Link>
</Provider>,
span
)
})
}
render() {
return (
<article
ref={this.prepare}
dangerouslySetInnerHTML={{ __html: marked(this.props.markdown) }}
/>
)
}
}
The above code requires the snipped which I copied from the question linked above. The method I called prepare replaces specific HTML nodes with React roots.
function createContextProvider(context) {
class ContextProvider extends React.Component {
getChildContext() {
return context
}
render = () => this.props.children
static propTypes = { children: PropTypes.node }
}
ContextProvider.childContextTypes = {}
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = PropTypes.any.isRequired
})
return ContextProvider
}
So we basically have a function that creates a Provider component. That function needs to be able to dynamically adapt to the required context types, that's why the loop sets them to be required.
If you simply want to have links work with React Router, you can render the markdown as usual with dangerouslySetInnerHTML then intercept internal link clicks to make them go through react-router.
Full example of loading .md externally, then catching links to process with react-router:
import React from "react"
import { withRouter } from 'react-router'
import catchLinks from 'catch-links'
import marked from "marked"
import styles from './styles.scss'
class ContentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
markdown: '',
title: ''
}
}
componentDidMount() {
if (typeof window !== 'undefined') {
catchLinks(window, (href) => {
this.props.history.push(href);
});
}
const page = location.pathname.replace('/', '');
fetch(`/assets/content/${page}.md`)
.then(response => response.text())
.then(text => {
this.setState({ markdown: marked(text, {}) })
});
const title = page_titles[page] || capitalize(page);
if (title) {
document.title = title;
this.setState({title})
}
}
render() {
const {
markdown,
title
} = this.state;
return (
<div class={styles.contentPage}>
<div class={styles.pageTop}>
{title}
</div>
<div class={styles.page}>
<div class={styles.content} dangerouslySetInnerHTML={{__html: markdown}}></div>
</div>
</div>
);
}
}
export default withRouter(ContentPage);

How can I access global variables from outside of electron main.js in React component?

I'm trying to access a global variable from main.js, this is where my electron app is launched.
My structure is like this...
main.js (launches electron app.on)
app (folder)
components (folder)
Landing.js (I want to access a global variable from main.js here!)
Here is what I have...
main.js
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
app.on('ready', () => {
mainWindow = new BrowserWindow({
height: 725,
width: 1100,
icon: __dirname + '/app/img/sawIcon.ico',
title: 'My App'
});
// CODE TO GRAB THE ARGUMENT passed in from the commandline
global.shareObject = {
hlpString: process.argv[2]
}
console.log(global.sharedObject.hlpProp); // prints ok
let url = require('url').format({
protocol: 'file',
slashes: true,
pathname: require('path').join(__dirname, 'index.html')
})
console.log(url)
mainWindow.loadURL(url)
});
app.on('window-all-closed', function() {
if (process.platform != 'darwin') {
app.quit();
}
});
Landing.js
import React, { Component } from 'react'
import sass from '../scss/application.scss'
import PropTypes from 'prop-types'
import Header from './Header'
import Menu from './Menu'
import HelpFile from './HelpFile'
class Landing extends Component {
constructor(props) {
super(props);
this.state = {
helpFileName: 'Mainmenu',
menuName: '',
}
}
handleHelpChange(helpFileName) {
this.setState( {helpFileName} );
}
handleMenuClick(menuName) {
//CONSOLE LOGS that are NOT working...
console.log(global.shareObject.hlpString); // prints nothing undefined
console.log(require('remote').getGlobal('shareObject').hlpString); // can't build error w/cant find remotes
this.setState( {menuName} );
}
render() {
return (
<div>
<div>
<Header handleMenuClick={this.handleMenuClick.bind(this)}/>
</div>
<br /><br />
<div className="mainMenuDiv">
<Menu handleHelpChange={this.handleHelpChange.bind(this)}/>
</div>
<div className="mainContainerDiv">
<HelpFile name={this.state.helpFileName}/>
</div>
</div>
)
}
}
export default Landing;
I can't seem to access my global.shareObject from Landing.js no matter what type of console log I try. Is my code wrong? Should I be trying to export default a const from main.js and importing that into Landing.js to access the variable? Help would be much appreciated.
You're getting this issue because the React context does not have access to the 'fs' module, which is a NodeJS module, which is what Electron is built on top of.
So here's my code and the answer to your
index.html
<script>
var electron = require('electron');
var serverHost = electron.remote.getGlobal('serverHost')
</script>
This is my root HTML, with the markup to be passed onto App.js
main.js
global.serverHost = 'http://localhost:7000/';
if (process.env.NODE_ENV === 'PROD') {
mainWindow.loadURL(`file://${__dirname}/dist/index.html`);
} else {
mainWindow.loadURL('http://localhost:8080/dist/');
}
And then, from the React component, everytime I want to access this variable, I simply do
console.log(window.serverHost);
> "http://localhost:7000/"
Sorry for the late reply, hope this helps someone...
In your Landing.js
console.log(require('electron').remote.getGlobal('sharedObject').hlpString)
Source
But there is several mispelled names
In main.js
global.shareObject = {
hlpString: process.argv[2]
}
d is missing sharedObject
console.log(global.sharedObject.hlpProp); // prints ok
Can't print ok, hlpString is the correct name

Resources