React: Import svg as component dynamically - javascript

In my application I got 8 svg files which I want to import dynamically. I have tried the following:
First I created a Floor component that has one name prop. This is the name of the floor that the user wants to see. In my dynamic import, the path to the svg is specified but it always seems to return undefined.
I know the path to the svg is correct because if I fill in a wrong path, React complains it can't find the svg file.
import React, { useEffect, useState } from "react";
const Floor = ({ name }) => {
const [floor, setFloor] = useState(null);
useEffect(() => {
/*
This is where it goes wrong. If I log the result It shows 'undefined'.
*/
import(`./floors/${name}.svg`)
.then((module) => {
setFloor(module);
})
.catch((error) => {
console.error(`Icon with name: ${name} not found!`);
});
}, [name]);
const renderFloor = () => {
if (!floor) return null;
const Floor = floor.ReactComponent;
return <Floor />;
};
return <>{renderFloor()}</>;
}
export default Floor;
And this is how I import the Floor
import Floor from './Floor'
const Floorplan = () => {
return (
<Floor name="floor-4" />
)
}
export default Floorplan;
I have found something similar but I still have the same error 'undefined'
These are the errors Im getting.
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
And if I log the dynamic import response:
Object { loaded: Getter, id: Getter, exports: undefined, webpackPolyfill: 1 }
Icon.js:14

The below worked for me. I had my svgs in a folder inside src, and was using the companyName prop for the svg filename. I also used a regex to format companyName to all lower case and no space (like the svg filenames)
const [iconUrl, setIconUrl] = useState(false)
const { companyName } = job.companyByCompany
useEffect(() => {
iconFetch(companyName.toLowerCase().replace(/\s+/g, ''))
}, [companyName])
const iconFetch = async (iconName) => {
await import(`../assets/logos/${iconName}.svg`)
.then(data => setIconUrl(data.default))
.catch(err => console.log(err))
}
I could then access the svg's via the src attribute in an img tag.
<img src={iconUrl} alt={`${companyName} svg`}/>

I believe the problem is webpack cannot determine what your ${name}.svg will look like in runtime and does not include them inside any chunk. To force this behavior you should include some magic comments inside your dynamic import. Something like:
...
useEffect(() => {
import(
/* webpackInclude: /\.svg$/ */
/* webpackChunkName: "svg-imgs-chunk" */
/* webpackMode: "lazy" */
`./floors/${name}.svg`)
.then((module) => {
setFloor(module);
})
...

Related

Reading JSON file into React Context Provider with Typescript

My React Typescript app has a Context Provider DataProvider that is to read a value from a JSON file and provide it in the Context useData(). I am trying to do the read synchronously to avoid having to deal with a isLoading since this is a tiny local JSON file.
Is there a recommended way to read the JSON file synchronously inside a Context Provider?
I tried the following using node-sync, but its giving a Typescript error
Object is possibly 'undefined'.ts(2532)
on data at line
return data.find(...
Tried changing it to
return data?.find(...`
but now the error is
Property 'find' does not exist on type 'never'.ts(2339)
import React, {
createContext,
useContext,
Consumer,
Context,
ReactNode,
useMemo,
} from 'react';
import Sync from 'sync';
export interface DataProviderProps {
children: ReactNode;
}
export interface Data {
secretNumber?: string;
}
// #ts-ignore
const DataContext: Context<Data> = createContext<Data>();
export function DataProvider({ children }: DataProviderProps) {
const secretNumber = useMemo(() => {
// Read from JSON file
const contractFile =
process.env.REACT_APP_WORLD === 'main'
? '../main.json'
: '../test.json';
let data;
Sync(function () {
data = import(contractFile);
});
return data.find( // <=== TS error: Object is possibly 'undefined'. ts(2532)
({ name }: { name: string }) => name === 'elonmusk',
)?.secretNumber;
}, []);
const states = useMemo<Data>(() => {
return {
secretNumber,
};
}, [secretNumber]);
return (
<DataContext.Provider value={states}>
{children}
</DataContext.Provider>
);
}
export function useData(): Data {
return useContext(DataContext);
}
export const DataConsumer: Consumer<Data> = DataContext.Consumer;
array.find() returns undefined If no values satisfy the testing function, from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find, so just add (!) after the array.find()! fxn to ascertain a value would be returned.
sample code stub
data.find(todoCodeStubhere)!

How to properly import custom icons created with IcoMoon in react native component (expo)

I have created custom icons. If I try to use the icons in App.js it works fine. I want to use them in my other files. What is the proper way of doing it?
import { createIconSetFromIcoMoon } from "#expo/vector-icons";
const Icon = createIconSetFromIcoMoon(
require("./components/SVGComponents/selection.json"),
"IcoMoon",
"icomoon.ttf"
);
const fetchFonts = () => {
return Font.loadAsync({
...
//icons font
IcoMoon: require("./components/SVGComponents/icomoon.ttf"),
});
};
export default function App() {
const [isFontLoaded, setIsFontLoaded] = useState(false);
if (!isFontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setIsFontLoaded(true)}
}
/>
);
}
...
}
I tried creating an Icon component, importing it in the App.js file and then using it in other component of mine but I am getting the following errors :
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
AppLoading threw an unexpected error when loading:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
//Icon.js
import { createIconSetFromIcoMoon } from "#expo/vector-icons";
const fetchFonts = () => {
return Font.loadAsync({
...
//icons font
IcoMoon: require("./components/SVGComponents/icomoon.ttf"),
});
};
export default Icon = createIconSetFromIcoMoon(
require("./components/SVGComponents/selection.json"),
"IcoMoon",
"icomoon.ttf"
);
//in App.js
import Icon from "./components/SVGComponents/Icon";
Also tried with export const Icon and import {Icon} , got the same error
in the component i want to use icons:
<Icon name="Back" color="green" size={44}></Icon>
What did the job - exporting Icon from App.js and importing it in the component I want to use it like that:
import { Icon } from "../../App";
but I am not sure it's a good idea to use it this way. How would you suggest I use it?
Solved it like this:
//Icon.js
import { createIconSetFromIcoMoon } from "react-native-vector-icons";
export default createIconSetFromIcoMoon(
require("./selection.json"),
"IcoMoon",
"icomoon.ttf"
);
In the file I want to use the icon:
import Icon from "../SVGComponents/Icon";
Then
//App.js
const fetchFonts = () => {
return Font.loadAsync({
//icons font
IcoMoon: require("./components/SVGComponents/icomoon.ttf"),
});
};
export default function App() {
const [isFontLoaded, setIsFontLoaded] = useState(false);
if (!isFontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setIsFontLoaded(true)}
onError={(err) =>
console.log("App => fetchFonts, <AppLoading> => " + err)
}
/>
);
...
}
in a folder called SVGComponents I have placed the icon font file - icomoon.ttf, the icons - selection.json, the custom icon component - Icon.js

Element type is invalid. Expected a string, found undefined

This one occurred just after a next-auth update. Suddenly it throws this error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of `MyApp`.
So I checked the MyApp file, but there doesn't seem to be anything wrong about it, nothing that I have seen at least.
_app.tsx (_app file in next.js)
import React from "react";
import { start, done } from "nprogress";
import "nprogress/nprogress.css";
import router from "next/router";
import "./styles/patch.css";
import { Provider } from "next-auth/client";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../apollo/apolloClient";
router.events.on("routeChangeStart", () => start());
router.events.on("routeChangeComplete", () => done());
router.events.on("routeChangeError", () => done());
const MyApp = ({ Component, pageProps }) => {
const apolloClient = useApollo(pageProps.initialApolloState);
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<ApolloProvider client={apolloClient}>
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
</ApolloProvider>
);
};
export default MyApp;
Did I miss something that could've caused this error?
EDIT:
Apparently, it was fine when I used webpack 4. It threw this error upon using webpack 5. But it would be good if there was a solution for webpack 5.
EDIT 2:
When I updated next to version 10.2, it worked perfectly fine. I must've been hasty on things perhaps so I guess there wasn't any problem in the first place. Other than that, I appreciate the answers that had been put in here.
do not use query like this :
React.useEffect(() => {
// this is wrong !!!
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, [])
try using useRef hook instead ex:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

TypeError: Webpack imported module is not a function

I have a backend that calculates work shifts.
I am trying to post some required user input with a module in services/shifts.
The getAll method works fine, but posting throws an error
TypeError: _services_shifts__WEBPACK_IMPORTED_MODULE_2__.default.postData is not a function
Shiftservice module:
import axios from 'axios'
const baseUrl = '...'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const postData = newObject => {
const request = axios.post(baseUrl, newObject)
return request.then(response => response.data)
}
export default {getAll, postData}
I have a button that triggers the following calling code on click:
import shiftService from './services/shifts'
const postData = (event) => {
event.preventDefault()
const sampleObject = {
sampleField: sample
}
shiftService
.postData(sampleObject)
.then(returnedData => {
console.log(returnedData)
})
}
When execution reaches shiftService.postData, the error is thrown.
I am really confused since I am basically copying some older project of mine which works, but here I just don't find the problem. Thank you in advance for helping a newcomer!
Modules provide special export default (“the default export”) syntax to make the “one thing per module” way look better.There may be only one export default per file.And we may neglect the name of the class in the following example.
//Module1
export default class{
}
And then import it without curly braces with any name:
//Module2
import anyname from './Module1'
Your scenario is different having two functions.You can either export default one function
export default getAll
and normal export the other function.
export postData
and when importing
import{ default as getAll,postData} from './yourModule'
OR
Remove default here in Shiftservice module and export normally:
import axios from 'axios'
const baseUrl = '...'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const postData = newObject => {
const request = axios.post(baseUrl, newObject)
return request.then(response => response.data)
}
export {getAll, postData}
Importing in your module
import {getAll,PostData} from './Module1'
or
import * as shiftService from './Module1'
and then use shiftServer.postData().....
Okay, I am embarrassed of the solution. I was just editing an earlier version of shiftService from a wrong folder, and the imported service only had the get method in it...
So my code actually works if placed correctly. Thank you for your time, and thanks for sharing alternative ways that must work aswell.
I think its becuse your declaring the function as arrow functions
and export it this way:
export default {getAll, postData}
you need to declare them as a normal functions
function postData(){
}
that should work

React dynamic import does not accept string variable

I have a component which enables me to import images dynamically in react. This is the component I am using:
import React from "react";
import PropTypes from "prop-types";
// Lazily load an iamge
class LazyImageLoader extends React.Component {
constructor() {
super();
this.state = {
module: null,
};
}
async componentDidMount() {
try {
const { resolve } = this.props;
const { default: module } = await resolve();
this.setState({ module });
} catch (error) {
this.setState({ hasError: error });
}
}
componentDidCatch(error) {
this.setState({ hasError: error });
}
render() {
const { module, hasError } = this.state;
if (hasError) return <div>{hasError.message}</div>;
if (!module) return <div>Loading module...</div>;
if (module) return <img src={module} alt="Logo" />;
return <div>Module loaded</div>;
}
}
LazyImageLoader.propTypes = {
resolve: PropTypes.func.isRequired,
};
export default LazyImageLoader;
Now, if I try to use this compoent like this with a string to the image which should get imported it works perfectly fine:
<LazyImageLoader resolve={() => import("assets/images/os/netboot.svg")} />
But as soon as I extract the URL into a seperate variable it no longer works and I get the error message "cannot find module ...":
const path = "assets/images/os/netboot.svg";
<LazyImageLoader resolve={() => import(path)} />
Is there a way I can use variables for a dynamic import?
According to the answer in:
Dynamic imports in ES6 with runtime variables
"The rules for import() for the spec are not the same rules for Webpack itself to be able to process import()".
So no you can't use variables with webpack dynamic import statements.
It doesn't work directly to use the variable but could be used as follows -
const path = './test.jpg';
import(`${path}`);
You can do it with Vite. The example they cite uses a template string literal, but that shouldn't obligate you to use a prefix or something. You probably would anyway. Here is the docs and their example.
https://vitejs.dev/guide/features.html#dynamic-import
const module = await import(`./dir/${file}.js`)
It also seems like, with glob imports also described on that docs page, you have lots of other options to do it lots of ways.

Categories

Resources