React - Import multiple images in given directory - reactjs

const importAll = require =>
require.keys().reduce((acc, next) => {
acc[next.replace("./", "")] = require(next);
return acc;
}, {});
const images = importAll(
require.context('./images', false, /\.(png|jpe?g|svg)$/)
);
console.log(images);
I have the above block of code that imports all files in a given directory given that they end in the required type however, I want to reuse this code so that I can import it into different modules across my app and specify the path for each individual module.
I've also tried the below block of code but am new to this syntax, any help is appreciated!
const importAll = require =>
require.keys().reduce((acc, next) => {
acc[next.replace("./", "")] = require(next);
return acc;
}, {});
const images = (path) => importAll(
require.context(path, false, /\.(png|jpe?g|svg)$/)
);
export default {
images,
}

Found out that It's because require.context will only take a literal regex in webpack 4 and therefore, not a variable.
See here https://webpack.docschina.org/guides/dependency-management/
However, mcm-ham found a work around which can be viewed here https://github.com/webpack/webpack/issues/4772?fbclid=IwAR3m6kmghtm4EoV8IspcqEfFItdeMnzQHGiEkXbwqgOywXSUFxcWXxbyjRI#issuecomment-344556837

Related

Creating relative URLs with `new URL()` behaves differently when first param is a variable. WHY?

I'm trying to implement web workers in NextJs, I've followed their example but It really bugs me that I cannot pass the worker relative URL as a variable to new URL(url, baseUrl).
The following snippet is where the worker gets called:
import { useEffect, useRef, useCallback } from 'react'
export default function Index() {
const workerRef = useRef()
useEffect(() => {
const workerUrl = '../worker.js';
console.log({
URL: new URL('../worker.js', import.meta.url),
meta: import.meta.url
});
console.log({
URL: new URL(workerUrl, import.meta.url),
meta: import.meta.url
});
workerRef.current = new Worker(new URL('../worker.js', import.meta.url))
workerRef.current.onmessage = (evt) =>
alert(`WebWorker Response => ${evt.data}`)
return () => {
workerRef.current.terminate()
}
}, [])
const handleWork = useCallback(async () => {
workerRef.current.postMessage(100000)
}, [])
return (
<div>
<p>Do work in a WebWorker!</p>
<button onClick={handleWork}>Calculate PI</button>
</div>
)
}
This strangely logs:
{
"URL":"/_next/static/media/worker.3c527896.js",
"meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}
{
"URL":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/worker.js",
"meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}
How in the world is this any different:
const workerUrl = '../worker.js';
console.log({
URL: new URL('../worker.js', import.meta.url),
meta: import.meta.url
});
console.log({
URL: new URL(workerUrl, import.meta.url),
meta: import.meta.url
});
The problem is that I cannot pass the URL as a prop, to some generic worker caller. I get the annoying error:
SecurityError: Failed to construct 'Worker': Script at 'file:///home/omar/CODE/NextJs/lullo/client/src/utils/WebWorkers/postErrorToServer.ts' cannot be accessed from origin 'http://localhost:3000'.
This is probably happening because in the first case:
const workerUrl = '../worker.js';
const url = new URL(workerUrl, import.meta.url);
webpack sees the URL as dynamic and is unable to properly bundle the web worker at compile-time. Something similar happens if you define the worker as follows:
const url = new URL('../worker.js', import.meta.url);
const worker = new Worker(url);
This comment on a discussion in webpack's GitHub repo might help in your case. I don't think the worker URL can be truly dynamic, due to the above reason - webpack needs to know the url of the worker script at compile-time.

Load different JS library files for different components

I have a website made in ReactJS. In public/index.html, I have
<head>
<script src="/lib/analyzejs-v1.js"></script>
<script src="/lib/analyzejs-v2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
where analyzejs-v1.js has 6Mo, and analyzejs-v2.js has 3Mo; they are all fixed files that I could not much modify.
These two files are not modules; their functions are declared (e.g., declare function f1(address: string): string; in src/defines/analyzejs-v1.d.ts). So some components call functions of analyzejs-v1.js by using a function name like f1(...) directly without any namespace, import, or export. And the rest of the components call functions of analyzejs-v2.js by using a function name like f2(...) directly without any namespace, import, or export.
It takes time to load these two js library files. So I'm looking for a way to load either analyzejs-v1.js or analyzejs-v2.js according to the component (or URL).
So does anyone know a conventional way to load different JS library files for different components?
If you don't need two script at the same time, you can add the script tag in the runtime when necessary.
I can provide you a hook which I have used to load the script on the fly.
export function useScript(url: string, clean: boolean = false, cleanJob: () => void = () => undefined): boolean {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
let create = false;
let script = document.querySelector(`script[src="${url}"]`) as HTMLScriptElement | null;
if (!script) {
script = document.createElement('script');
script.src = url;
script.async = true;
if (type (document as any).attachEvent === 'object') {
(script as any).onreadystatechange = () => {
if ((script as any).readyState === 'loaded') {
setLoaded(true);
}
}
} else {
script.onload = () => {
setLoaded(true);
}
}
document.body.appendChild(script);
create = true;
} else {
setLoaded(true);
}
// For a special library, you can do the clean work by deleting the variable it exports.
return () => {
if (create && script && clean) {
setLoaded(false);
document.body.removeChild(script);
cleanJob && cleanJob();
}
}
}, [url]);
return loaded;
}
To use it:
export const Comp = (props: ICompProps) => {
const loaded = useScript('https://path/to/the/script.js');
// if you want to do some clean work, Suppose the external script introduces the variable A, And A can be reasigned.
// const loaded = useScript('https://path/to/the/script.js', true, () -> { A = undefined; });
useEffect(() -> {
if (loaded) {
// Suppose the external script introduces the variable A. Now it is available.
const a = new A();
// do something with a.
}
}, [loaded]);
if (loaded) {
return XXX;
} else {
return null;
}
}
If the script is not a module, just add a typescript declare file without import statements, and declare the global variable the script export. Such as:
declare interface XXX {
YYY
}
declare const ScriptValue: XXX;
When you import a script using the <script> tag, the library can only be used client side and therefore not with node. However, if you mark it as a module, another script can use it like this:
index.html:
<script src="test.mjs" type="module"></script>
<script type="module">
import {hello} from "./test.mjs"
hello()
</script>
test.mjs:
export function hello(text) {
console.log("hello from test")
}
The only thing is the communication between your react scripts and that inline script. The only way, I figured out, how to achieve this is using window.
DISCLAIMER
I'm really not sure, if anyone should use it this way. I have only tested this once and it might very well break... Maybe someone else can tell me their opinion on my approach.
index.tsx
... // imports
(window as any).importStuff = (a: any) => {
a.hello()
}
...
index.html
<script src="test.mjs" type="module"></script>
<script type="module">
import {hello} from "./test.mjs"
window.importStuff({
hello: hello
})
</script>
If you are looking for better performance of the page and not let the scripts block the DOM content loading, then you may want to look at adding defer to the script https://javascript.info/script-async-defer#defer.
If you are looking for dynamic loading of script, and load the script only when its first used, check this doc https://javascript.info/script-async-defer#dynamic-scripts
You can create <script> tag when your component is loaded.
At the first, We create a function to create the script tag
const scriptGenerator = (options = {}) => {
const s = document.createElement("script");
for (const option in options) {
s[option] = options[option]
}
document.querySelector("head").appendChild(s);
}
We can use two attributes to load scripts
defer
async
defer: The defer attribute tells the browser not to wait for the script. Instead, the browser will continue to process the HTML, build DOM. The script loads “in the background”, and then runs when the DOM is fully built.
async: The async attribute is somewhat like defer. It also makes the script non-blocking. But it has important differences in the behavior.
async & defer Docs
After all these steps, we can define our script in the head. You can use useEffect
useEffect(() => {
// V1
scriptGenerator({
src: "...",
async: 1,
});
// V2
scriptGenerator({
src: "...",
async: 1,
});
}, []);
Do not forget to delete them after exiting the component
useEffect(() => {
// Generate script
return () => {
document
.querySelector("head")
.querySelectorAll('script[src="..."]')
.remove();
}
});
If we want to reach a conclusion, it is as follows:
import { useEffect } from 'react';
const Component = () => {
const scriptGenerator = (options = {}) => {
const s = document.createElement("script");
for (const option in options) {
s[option] = options[option]
}
document.querySelector("head").appendChild(s);
}
useEffect(() => {
// V1
scriptGenerator({
src: "...",
async: 1,
});
// V2
scriptGenerator({
src: "...",
async: 1,
});
return () => {
document
.querySelector("head")
.querySelectorAll('script[src="..."]')
.remove();
}
});
}

Why do I get undefined value from async function?

I have been using Google firestore as a database for my projet.
In the collection "paths", I store all the paths I have in my app, which are composed of 2 fields : name, and coordinates (which is an array of objects with coordinates of points).
Anyway, i created a utility file in utils/firebase.js
In the file, i have this function which gets all the paths in my collection and return an array of all documents found :
export const fetchPaths = () => {
let pathsRef = db.collection('paths');
let pathsArray = []
pathsRef.get().then((response) => {
response.docs.forEach(path => {
const {nom, coordonnees } = path.data();
pathsArray.push({ nom: nom, coordonnees: coordonnees})
})
console.log(pathsArray)
return pathsArray;
});
};
In my react component, What i want to do is to load this function in useEffect to have all the data, and then display them. Here is the code I use :
import { addPath, fetchPaths } from './Utils/firebase';
//rest of the code
useEffect(() => {
let paths = fetchPaths()
setLoadedPaths(paths);
}, [loadedPaths])
//.......
The issue here is if I console log pathsArray in the function it's correct, but it never gets to the state.
When i console log paths in the component file, i get undefined.
I am quite new with react, i tried different things with await/async, etc. But I don't know what i am doing wrong here / what i misunderstand.
I know that because of my dependency, i would be supposed to have an infinite loop, but it's not even happening
Thank you for your help
Have a nice day
fetchPaths does not return any result. It should be:
export const fetchPaths = () => {
let pathsRef = db.collection('paths');
let pathsArray = []
return pathsRef.get().then((response) => {
response.docs.forEach(path => {
const {nom, coordonnees } = path.data();
pathsArray.push({ nom: nom, coordonnees: coordonnees})
})
console.log(pathsArray)
return pathsArray;
});
};
note the return statement.
Since the fetchPaths returns a promise, in the effect it should be like following:
useEffect(() => {
fetchPaths().then(paths =>
setLoadedPaths(paths));
}, [loadedPaths])

How to access the functional component's name from a React hook?

I am trying to write a custom React hook, useLogging, where I would like to contextualize the log message based upon the name of the component which is doing the logging.
For example:
const Login: React.FunctionComponent<IProps> = (props) => {
log = useLogging();
log.info("Hello!")
[...]
Should produce [Login] Hello!
My custom hook, then, needs the name Login:
export const useLogger = () => {
// "this" is undefined
const loggerName = ??????
return logManager.getLogger(loggerName);
};
In the context of a class, what I'm looking for is something like this.constructor.displayName. However, a React hook does not have this set, and I cannot seem to find documentation on obtaining reference to the functional component's context.
—-
Edit: I would prefer not to pass any arguments and not to add a bunch of boiler plate. My goal is that the useLogging() function will survive component refactoring, and not rely upon the developer to provide a "correct" name.
There's a couple other ways that I can think of that you could use. The first and the simplest the one Drew suggested in the comment, which is to simply pass the logging name as an argument:
const useLogger = (name: string) => {
return logManager.getLogger(name)
}
const Login: React.FC<Props> = () => {
const log = useLogger('Login')
// ...
}
You could also obtain the name via .displayName or .name. Notice that .name refers to Function.name, which, if you're using webpack, will probably be minified in a production build so you'll end up with names like "t" or "s" etc. If you need the same name as in your component, you can assign displayName and let the hook take care of it:
const useLogger = (component: React.ComponentType<any>) => {
const name = useLogger(component.displayName || component.name);
return logManager.getLogger(name);
}
const Login: React.FC<Props> = () => {
const log = useLogger(Login)
}
Login.displayName = 'Login';
If you're okay with passing a name to useLogger, but don't want to set displayName every time, you could use something like ts-nameof which aims to give you a nameof operator like in C#:
const useLogger = (name: string) => {
return logManager.getLogger(name)
}
const Login: React.FC<Props> = () => {
const log = useLogger(nameof(Login))
// ...
}
The upside here is that the name will survive auto-renames. This requires some bundler or Babel configuration. I haven't tested how minification affects this, but there's three different flavors of ts-nameof (at the time of writing) which you can use:
a compile-time transform for the TypeScript compiler: ts-nameof
a Babel macro: ts-nameof.macro
a Babel plugin: babel-plugin-ts-nameof
Pick the first one that matches your build pipeline.
Alternatively, if the logger isn't component-specific, but module-specific, you could make a factory for the hook, and initialize it once at the top of your module:
const makeUseLogger = (name: string) => () => {
return logManager.getLogger(name)
}
// in your module
const useLogger = makeUseLogger('Module name')
const Login: React.FC<Props> = () => {
const log = useLogger()
// ...
}
As an extension of this, if the logger itself doesn't actually need to be a hook (doesn't use other hooks or need props etc.), just make a logger for your module at the top level directly:
const log = logManager.getLogger('Module name')
const Login: React.FC<Props> = () => {
log.info('hello')
}
Additionally, if you don't mind your project's directory structure leaking into a production build, you can use a webpack trick:
// webpack.config.js
module.exports = {
// ...
node: {
__filename: true
}
}
and then
const log = logManager.getLogger(__filename)
In a file whose path is /home/user/project/src/components/Login.ts, and the webpack context is /home/user/project, the __filename variable will resolve to be src/components/Login.ts.
Although, this will probably require you to create a typedef e.g. globals.d.ts where you declare the __filename global for Typescript:
declare global {
__filename: string;
}
Note: this will not work if your build target is umd.
As a side-note, technically, if you for some reason don't want to pass any args to useLogging, you could use the deprecated Function.caller property, e.g.
function useLogging() {
const caller = (useLogging.caller as React.ComponentType<any>);
const name = caller.displayName || caller.name;
console.log(name);
return logManager.getLogger(name);
}
const Login: React.FC<Props> = () => {
const log = useLogging()
// ...
}
However, that property is deprecated so you'll have to clean that up sooner or later, so don't do that in production code.

How to generate a menu based on the files in the pages directory in Next.js

I am trying to create a menu component that reads the contents of the pages folder at build time. However I haven't had any success. Here is what I have tried:
import path from "path";
import * as ChangeCase from "change-case";
export default class Nav extends React.Component {
render() {
return (
<nav>
{this.props.pages.map((page) => (
<a href={page.link}>{page.name}</a>
))}
</nav>
);
}
async getStaticProps() {
let files = fs.readdirSync("../pages");
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(file);
return stat.isFile();
});
const pages = files.map((file) => {
if (file == "index.js") {
const name = "home";
const link = "/";
} else {
const link = path.parse(file).name;
const name = ChangeCase.camelCase(link);
}
console.log(link, name);
return {
name: name,
link: link,
};
});
return {
props: {
pages: pages,
},
};
}
}
This does not work, the component does not receive the pages prop. I have tried switching to a functional component, returning a promise from getStaticProps(), switching to getServerSideProps(), and including the directory reading code into the render method.
The first two don't work because getStaticProps() and getServerSideProps() never get called unless the component is a page, and including the code in the render method fails because fs is not defined or importable since the code might run on the front end which wouldn't have fs access.
I've also tried adding the code to a getStaticProps() function inside _app.js, with the hopes of pushing the pages to the component via context, but it seems getStaticProps() doesn't get called there either.
I could run the code in the getStaticProps function of the pages that include the menu, but I would have to repeat that for every page. Even if I extract the logic into a module that gets called from the getStaticProps, so something like:
// ...
export async function getStaticProps() {
return {
props: {
pages: MenuMaker.getPages(),
// ...
}
}
}
and then pass the pages to the navigation component inside the page via the Layout component:
export default function Page(props) {
return (
<Layout pages={props.pages}></Layout>
)
}
then that's still a lot of boilerplate to add to each page on the site.
Surely there is a better way... It can't be that there is no way to add static data to the global state at build time, can it? How do I generate a dynamic menu at build time?
I managed to get this working by exporting a function from next.config.js and setting an environment variable that contains the menu structure. I abstracted the menu loading code into it's own file. After seeing the result, I understand better why I was not able to find an example of anyone doing something similar:
The menu is not ordered the way I would like. I could sort it alphabetically, or by the modification date but realistically it almost always needs to be manually sorted in relation to the subject of the pages. I could use an integer, either tacked on to the filename or somewhere in the file (perhaps in a comment line). But in retrospect I think that just hard coding the links in a component is probably the best way after all since it offers much more flexibility and probably isn't going to be much more work even in the very long run.
That being said I am sharing my solution as it is a way to initialize an app wide static state. It's not ideal, you will have to restart the dev server if you wish to recalculate the variables here, which is why I'm still interested in other possible solutions, but it does work. So here it is:
next.config.js
const menu = require("./libraries/menu.js");
module.exports = (phase, { defaultConfig }) => {
return {
// ...
env: {
// ...
menu: menu.get('pages'),
// ...
},
// ...
};
};
libraries/menu.js
const fs = require("fs");
const path = require("path");
const ccase = require("change-case");
module.exports = {
get: (pagePath) => {
if (pagePath.slice(-1) != "/") pagePath += "/";
let files = fs.readdirSync(pagePath);
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(pagePath + file);
return stat.isFile();
});
return files.map((file) => {
if (file == "index.js") {
return {
name: "Home";
link: "/";
};
} else {
link = path.parse(file).name;
return {
link: link;
name: ccase.capitalCase(link);
};
}
});
},
};
Then the actual menu is generated from the environment variable in a component that can be included in the layout:
components/nav.js
import Link from "next/link";
export default class Nav extends React.Component {
render() {
return (
<nav>
{process.env.menu.map((item) => (
<Link key={item.link} href={item.link}>
<a href={item.link}>
{item.name}
</a>
</Link>
))}
</nav>
);
}
}
You can try this:
const fg = require('fast-glob');
const pages = await fg(['pages/**/*.js'], { dot: true });

Resources