refactoring JS files in React - reactjs

I am repeating this code is a few components and I find it repetitive:
componentDidMount(){
let scripts = [
{src:"/lib/jquery/jquery.min.js"},
{src:"/lib/jquery/jquery-migrate.min.js"},
{src:"/lib/bootstrap/js/bootstrap.bundle.min.js"},
{src:"/lib/easing/easing.min.js"},
{src:"/lib/superfish/hoverIntent.js"},
{src:"/lib/superfish/superfish.min.js"},
{src:"/lib/wow/wow.min.js"},
{src:"/lib/waypoints/waypoints.min.js"},
{src:"/lib/counterup/counterup.min.js"},
{src:"/lib/owlcarousel/owl.carousel.min.js"},
{src:"/lib/isotope/isotope.pkgd.min.js"},
{src:"/lib/lightbox/js/lightbox.min.js"},
{src:"/lib/touchSwipe/jquery.touchSwipe.min.js"},
{src:"/lib/main.js"},
{src:"/contactform/contactform.js"}
]
//Append the script element on each iteration
scripts.map(item => {
const script = document.createElement("script")
script.src = item.src
script.async = false
script.defer = false
document.body.appendChild(script)
})
}
I am not sure how I can put all this into one JS file so that I could just use the import statement.
Many thanks in advance and greatly appreciated,

Put these scripts in the root of your app. Use react-inline-script to avoid manually manipulating the DOM yourself.
Or
Install the npm packages for the scripts you need (when available) and import the module you need using ES6 imports.

Related

Most optimal way of importing external libraries in ReactJS

The example is I have an external library such as Materialize.js, the components I need to render depend on it, what is the best way to include this library? The documentation of ReactJS refers to "Code Splitting" which I have done with my own JavaScript but cannot do with an external minimized script. What approach yields the highest performance?
I have tried the following
componentDidMount() {
const script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js";
script.async = true;
script.onload = () => this.scriptLoaded();
document.body.appendChild(script);
}
scriptLoaded = async () => {
this.setState({materializeJsLoaded: true});
}
I have also then also tried including it in the index.html page at the bottom of the body tag.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
Here is an article that discusses creating a custom css build and importing javascript on a per-component basis:
https://medium.com/#mattdlockyer/youre-using-materialize-css-wrong-470b593e78e9

react native (expo) load markdown files

I'm having some troubles loading markdown files (.md) into my react native (non-detached expo project).
Found this awesome package that allows me to render it. But can't figure out how to load the local .md file as a string.
import react from 'react';
import {PureComponent} from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class Page extends PureComponent {
static propTypes = {};
static defaultProps = {};
render() {
return (
<Markdown>{copy}</Markdown>
);
}
}
BTW: I tried googling, but can't get the suggestions to work
https://forums.expo.io/t/loading-non-media-assets-markdown/522/2?u=norfeldtconsulting
I tried the suggested answers for reactjs on SO, but the problem seems to be that it only accepts .js and .json files
Thanks to #Filipe's response, I got some guidance and got a working example that will fit your needs.
In my case, I had a .md file on the assets/markdown/ folder, the file is called test-1.md
The trick is to get a local url for the file, and then use the fetch API to get its content as a string.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: copy
}
}
componentDidMount() {
this.fetchLocalFile();
}
fetchLocalFile = async () => {
let file = Expo.Asset.fromModule(require("./assets/markdown/test-1.md"))
await file.downloadAsync() // Optional, saves file into cache
file = await fetch(file.uri)
file = await file.text()
this.setState({copy: file});
}
render() {
return (
<Markdown>{this.state.copy}</Markdown>
);
}
}
EDIT: In order to get get rid of the error
Unable to resolve "./assets/markdown/test-1.md" from "App.js"
you would need to add the packagerOpts part of #Filipe's snippet into your app.json file.
app.json
{
"expo": {
...
"assetBundlePatterns": [
"**/*"
],
"packagerOpts": {
"assetExts": ["md"]
},
...
}
}
EDIT 2:
Answering to #Norfeldt's comment:
Although I use react-native init when working on my own projects, and I'm therefore not very familiar with Expo, I got this Expo Snack that might have some answers for you: https://snack.expo.io/Hk8Ghxoqm.
It won't work on the expo snack because of the issues reading non-JSON files, but you can test it locally if you wish.
Using file.downloadAsync() will prevent the app making XHR calls to a server where your file is hosted within that app session (as long as the user does not close and re-open the app).
If you change the file or modify the file (simulated with a call to Expo.FileSystem.writeAsStringAsync()), it should display the updated as long as your component re-renders and re-downloads the file.
This will happen every time your app is closed and re-open, as the file.localUri is not persisted per sessions as far as I'm concerned, so your app will always call file.downloadAsync() at least once every time it's opened. So you should have no problems displaying an updated file.
I also took some time to test the speed of using fetch versus using Expo.FileSystem.readAsStringAsync(), and they were on average the same. Often times Expo.FileSystem.readAsStringAsync was ~200 ms faster, but it 's not a deal breaker in my opinion.
I created three different methods for fetching the same file.
export default class MarkdownRenderer extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: ""
}
}
componentDidMount() {
this.fetch()
}
fetch = () => {
if (this.state.copy) {
// Clear current state, then refetch data
this.setState({copy: ""}, this.fetch)
return;
}
let asset = Expo.Asset.fromModule(md)
const id = Math.floor(Math.random() * 100) % 40;
console.log(`[${id}] Started fetching data`, asset.localUri)
let start = new Date(), end;
const save = (res) => {
this.setState({copy: res})
let end = new Date();
console.info(`[${id}] Completed fetching data in ${(end - start) / 1000} seconds`)
}
// Using Expo.FileSystem.readAsStringAsync.
// Makes it a single asynchronous call, but must always use localUri
// Therefore, downloadAsync is required
let method1 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
})
} else {
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
}
}
// Use fetch ensuring the usage of a localUri
let method2 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
fetch(asset.localUri).then(res => res.text()).then(save)
})
} else {
fetch(asset.localUri).then(res => res.text()).then(save)
}
}
// Use fetch but using `asset.uri` (not the local file)
let method3 = () => {
fetch(asset.uri).then(res => res.text()).then(save)
}
// method1()
// method2()
method3()
}
changeText = () => {
let asset = Expo.Asset.fromModule(md)
Expo.FileSystem.writeAsStringAsync(asset.localUri, "Hello World");
}
render() {
return (
<ScrollView style={{maxHeight: "90%"}}>
<Button onPress={this.fetch} title="Refetch"/>
<Button onPress={this.changeText} title="Change Text"/>
<Markdown>{this.state.copy}</Markdown>
</ScrollView>
);
}
}
Just alternate between the three to see the difference in the logs.
From what I know, this cannot be done within expo. I use react-native and run it on my mobile for development.
react-native use Metro as the default bundler, which also suffers from similar problems. You have to use haul bundler instead.
npm install --save-dev haul
npx haul init
npx haul start --platform android
In a seperate terminal run react-native run-android. This would use haul instead of metro to bundle the files.
To add the markdown file, install raw-loader and edit the haul.config.js file. raw-loader imports any file as a string.
Customise your haul.config.js to look something like this:
import { createWebpackConfig } from "haul";
export default {
webpack: env => {
const config = createWebpackConfig({
entry: './index.js',
})(env);
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader'
})
return config;
}
};
Now you can import the markdown file by using const example = require('./example.md')
Haul supports webpack configuration so you can add any custom babel transform you want.
I don't know exactly where the problem lies, but I added html files to the project, and I'd imagine it would be very similar.
Inside your app.json, try adding these fields:
"assetBundlePatterns": [
"assets/**",
],
"packagerOpts": {
"assetExts": ["md"]
},
The packagerOpts makes it so the standalone will bundle the .md files. I'd imagine you already have an assets folder, but just in case you don't, you will need one.
Then, on AppLoading, loading the assets with Asset.loadAsync might not be needed, but it's a good idea to rule out. Check out the documentation on how to use it.
When importing the file, there are three ways you might want to do so, that change depending on the environment. I'll copy this excerpt from my Medium article:
In the simulator, you can access any file in the project. Thus, source={require(./pathToFile.html)} works. However, when you build a standalone, it doesn’t work quite in the same way. I mean, at least for android it doesn’t. The android webView doesn’t recognise asset:/// uris for some reason. You have to get the file:/// path. Thankfully, that is very easy. The assets are bundled inside file:///android_asset (Careful, don’t write assets), and Expo.Asset.fromModule(require(‘./pathToFile.html')).localUri returns asset:///nameOfFile.html. But that’s not all. For the first few times, this uri will be correct. However, after a while, it changes into another file scheme, and can’t be accessed in the same way. Instead, you’ll have to access the localUri directly. Thus, the complete solution is:
/* Outside of return */
const { localUri } = Expo.Asset.fromModule(require('./pathToFile.html'));
/* On the webView */
source={
Platform.OS === ‘android’
? {
uri: localUri.includes('ExponentAsset')
? localUri
: ‘file:///android_asset/’ + localUri.substr(9),
}
: require(‘./pathToFile.html’)
}
(A constant part of the uri is ExponentAsset, that’s why I chose to check if that was part of it)
That should probably solve your problem. If it doesn't, comment what's going wrong and I'll try to help you further. Cheers!
If you want to load .md file with react-native cli (without expo). I've got a solution for you)
Add https://github.com/feats/babel-plugin-inline-import to your project
Add config .babelrc file with code inside:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
[
"inline-import",
{
"extensions": [".md", ".txt"]
}
],
[
"module-resolver",
{
"root": ["./src"],
"alias": {}
}
]
]
}
Add to your metro.config.js such code
const metroDefault = require('metro-config/src/defaults/defaults.js');
...
resolver: {
sourceExts: metroDefault.sourceExts.concat(['md', 'txt']),
}
....
Reload your app

Exclude node_module/package from production build webpack

I am working on a project that uses "webpack": "^2.4.1",, it is a ReactJS project, I have installed the module airbnb/prop-types-exact, I am using this package for development purposes, where I would not want a user of a component I wrote to pass non-existing properties to that component.
I would like to remove this package when I build the app for production. I am using the Webpack Bundle Initializer to see the bundle size of airbnb/prop-types-exact, it is not that big, but I would like to have it removed from the production build, Is this achievable? With the webpack version that I am using or with a latter one?
I would appreciate any resources or ideas regarding this, thanks.
Following through an example from this Blog by Mark
And more references on these plugins:
IgnorePlugin and DefinePlugin
I have used the plugins as he did, which are IgnorePlugin and UglifyJsPlugin and then in the component where I am using the airbnb/prop-types-exact package, I am doing a check on which environment I am in like..
let exactProps ;
if (process.env.NODE_ENV === "development") {
exactProps = require("prop-types-exact");
}
And depending on whether the exactProps has a value, meaning the require function has ran, and ,meaning the exactProps has the function from the prop-types-exact package,
I am wrapping the my prop types with this function, .eg.
const propTypes = {
someProp: PropTypes.iRequired
}
if (exactProps && typeof exactProps === "function") {
MyComponent.propTypes = exactProps(propTypes);
} else {
MyComponent.propTypes = propTypes;
}
And finally I export the MyComponent component
export MyComponent
I am planning to move the wrapping of the component's prop types into a generic module, so that it is re-usable

create react app Configuration file after build app

I want a Config File (JSON) in root folder after build to config my app.
like Translation and API Urls and ...
Can I do this with create react app?
Create config.js or json file outside src directory and include it in index.html like
<script src="%PUBLIC_URL%/config.js" type="text/javascript"></script>
configure parameters in config.js
config.js
var BASE_URL = "http://YOUR-URL";
you can get paramenters like
const BASE_URL = window.BASE_URL;
You can store you JSON file in the public/ folder and it'll automatically provide this file when you host your Create React App.
Something like: /public/my-configuration-file.json
then when you restart your application:
localhost:3000/my-configuration-file.json
will provide you this json file.
You could create a custom hook that reads a "public" config file using fetch.
// This path is relative to root, e.g. http://localhost/config.json
const configFile = './config.json'
export function useConfig() {
const [config, setConfig] = useState(initialConfig);
useEffect(() => {
(async function fetchConfig() {
try {
const response = await (await fetch(configFile)).json();
setConfig(response);
} catch (e) {
console.log(e);
}
}());
}, []);
return config;
}
Then use it anywhere in you app
function App() {
const config = useConfig();
return (
<div>{config.foo}</div>
);
}
You'll always have an up to date non-cached version of it's data.
updating this topic with a brand new package that is available now that brings the joys of .Net Configuration to the JavaScript world: wj-config.
This package is pretty much an exact answer to what you need. Read this blog post for more information.
It is incredible to me how during over 6 years nobody filled in this gap in React (and JavaScript in general). Anyway, give wj-config a try. I think it will be a positive experience.

How to use CDN Imports in a React-Project

My project is based on create-react-app and now I want to use Here Maps. Their documentation recommends loading the modules with CDN and I cant find any NPM packages for it. My question now is: how can I load the CDN properly?
I know there is the possibility to just put the CDN link inside my index.html file but this seems not to be the right solution I think.
After trying some things out, I found a solution for this use case.
I installed this package "html-webpack-externals-plugin".
All you have to do is read the documentation for your use case. The "CDN-Use-Case" is also described.
For accessing the functions from the external JS-API you have to put a "window." in front of the function for example like this:
const map = new window.H.Map();
Hope this helps somebody!
You can programmatically add JS script tags. Here's an example
function loadScript( {src, id, callback} ) {
if(id && document.getElementById(id)){
return; // don't accidentally re-add
}
const script = document.createElement( 'script' );
if(callback){
script.onload = callback;
}
if(id){
script.setAttribute( 'id', id );
}
script.setAttribute( 'src', src );
document.body.appendChild( script );
}
Usage example
componentDidMount(){
loadScript({
src: 'http://js.api.here.com/v3/3.0/mapsjs-core.js',
id: 'script-mapsjs-core',
callback: () => this.setState({mapsjsCoreLoaded: true})
});
}

Resources