How to dynamically import react component inside file based on prop - javascript

I have a file (in this case Test.js) that will have many small components in the future (at the moment just Test and SuperTest).
I don't want to import the whole file in order to optimize performance. I try to import just the component I need. Not the whole file.
In the example, prop can be either "Test" or "SuperTest".
It throws an error Unexpected token (7:26)
Is there any way to accomplish that? and then render that into App?
App.js
import { useState } from 'react';
// import Test from './Test';
function App({prop}) {
const [Comp, setComp] = useState(null);
import('./Test').then(({`${prop}`: newComp}) => { // This is an ERROR
console.log(typeof newComp); // Object
setIcon(() => newComp);
});
return (
<div className="App">
<Comp />
</div>
);
}
export default App;
Test.js
export const Test = () => {
return (
<div>
Hello People
</div>
);
}
export const SuperTest = () => {
return (
<div>
Hello People 2
</div>
);
}

If you want to use many functions/components in single file and have to call the function dynamically then try below code.
Add getData() function to invoke the function in Test.js file.
Test.js
const Test = () => {
return (
<div>
Hello People
</div>
);
}
const SuperTest = () => {
return (
<div>
Hello People 2
</div>
);
}
export function getData(fnName) {
switch (fnName) {
case "Test":
return Test();
default:
return SuperTest();
}
}
Call getData() function and pass your prop as parameter
App.js
import("./Test").then((fn) => {
let newComp = fn.getData({prop}));
// use above newComp value
});

don't wrap props in this {props}. try this one:
function App(prop) {
const [Comp, setComp] = useState(null);

Related

queryByTestId is null after waitFor in unit test

In my unit test, I want to click an element in my wrapper component that affects the child component. The queryByTestId works before the await waitFor call, but the 2nd queryByTestID returns "null". I'm trying to test what happens in the child component when the language changes.
In my test I have the following:
const { queryByTestId, container } = render(
<TestIntlWrapper>
<MyComponent />
</TestIntlWrapper>
);
expect(queryByTestId("test-intl-wrapper")).toBeInTheDocument;
await waitFor(() => expect(mockedAxios.get).toBeCalledTimes(expectedNumOfAPICalls));
expect(mockedAxios.get).toBeCalledWith(expectedURL1);
expect(mockedAxios.get.mock.calls[1][0]).toBe(expectedURL2);
expect(mockedAxios.get.mock.calls[thirdCall][0]).toBe(expectedURL3);
expect(queryByTestId("test-intl-wrapper")).toBeInTheDocument; //queryByTestId returns null here
TestIntlWrapper.tsx
import React, { useEffect, useState } from "react";
import { IntlProvider } from "react-intl";
interface TestIntlWrapperProps {
children: JSX.Element
}
export default function TestIntlWrapper({children}: TestIntlWrapperProps) {
const languages = ["en", "es", "fr"]
const [currentLanguage, setCurrentLanguage] = useState(languages[0]);
const [clickCount, setClickCount] = useState(0);
const setClick = () => {
setClickCount(clickCount + 1)
}
useEffect(() => {
setCurrentLanguage(languages[clickCount % languages.length]);
},[clickCount] )
return (
<div data-testid="test-intl-wrapper" onClick={setClick}>
<IntlProvider locale={currentLanguage}>
{children}
</IntlProvider>
</div>
)
}
Any help is appreciated
The issue was the application was throwing an uncaught error in the waitFor which is why it was running an empty div and the data-testid was disappearing.

Importing React Autosuggest as Functional Component from Another JSX File

I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange

ReactDom createPortal() doesn't work but render() does, and only once not if trigger is repeated - why is this?

Newbie to react here.
TLDR: I have a helper function called createNotification which when called inserts a <ToastNotification /> component into a container element using render(). If I use createPortal() nothing is appended. If I use render, the component is only added once despite multiple triggers.
Can anyone help me figure out whats happening please?
Thank you
helpers.js
import { ToastNotification } from "carbon-components-react";
import { render, createPortal } from "react-dom";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return render(<ToastNotification />, container); //works but only once, not on multiple triggers
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
the function above is called from other components as needed:
login.js
import { createNotification } from "../../helpers";
const Login = () => {
const validateLogin = async (event) => {
createNotification();
// validation logic
performLogin();
};
const performLogin = async () => {
//axios call here
};
// main component content
return (
<>
<!-- validateLogin() called on form submit -->
</>
);
};
export default Login;
app.js
//imports
function App() {
return (
<div>
<div className="App"></div>
</div>
);
}
export default App;
Thank you
Solved this myself by adding the createPortal() within the render().
If anyone can provide an explanation, it would be much appreciated.
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container);
return render(createPortal(<ToastNotification />, container), document.createElement("div"));
};
createNotification aren't mounted in component in app Virtual Dom... when you use render(createPortal) then you just create spearted app.
import { createNotification } from "../../helpers";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
const Login = () => {
const [validate, setValidate] = useState(false);
const validateLogin = async (event) => {
if('some logic')
return setValidte(true)
setVAlidte(false)
};
useEffect(() => {
if(!valite)
return;
//axios heare
}, [validate])
// main component content
return (
<>
{!validate && <CreateNotfication/>}
<!-- validateLogin() called on form submit -->
</>
);
};

maintaining state between two components in a js package

Let's say I want to create a React package that will have two components, one to preload assets, and another to play/use those assets. Usage would look like this:
// Usage
import { PreloaderComponent, NotificationComponent } from 'module';
const Consumer: React.FC = () => {
render (
<>
<PreloaderComponent />
...
{ condition && <NotificationComponent />}
</>
)
}
I believe I'll need to persist state in my package... something like
// package
const assetStore = () => {
const path = 'path.mp3';
const loadedAsset;
const preload = () => {
loadedAsset = new Asset(path);
}
const getAsset = () => {
// check if preloaded
// if not, load
return loadedAsset;
}
return {
preload,
getAsset
};
}
const PreloaderComponent: null = () => {
const store = assetStore();
assetStore.preload();
return null;
}
const NotificationComponent: React.FC = () => {
// if (already instantiated)
// get access to previously instantiated store
const assetObject = assetStore.getAsset();
assetObject.play()
render (
<div> // or whatever
)
}
export {
PreloaderComponent,
NotificationComponent
};
But the above code won't work, since the NotificationComponent doesn't have access to the previously instantiated store. I considered a factory pattern but then you'd need to instantiate that factory somewhere.
How would you preload the assets by calling one component, then use those assets in another? Thanks.
A context might be the way to go. The docs describe when to use contexts like this:
Context is designed to share data that can be considered “global” for a tree of React components
So an example would be an AssetContext with a useContext-hook to simplify things:
import React, { useCallback, useContext, useState } from "react";
const AssetContext = React.createContext();
const AssetProvider = (props) => {
const [assets, setAssets] = useState([]);
const value = {
assets,
addAsset: (asset) => {
setAssets([...assets, asset]);
},
clear: () => setAssets([])
};
return <AssetContext.Provider value={value} {...props} />;
};
const useAssets = () => useContext(AssetContext)
You can use the data provided by the context with useAssets():
const Preloader = () => {
const {addAsset} = useAssets();
useCallback(() => {
addAsset({play: () => console.log('sth')})
})
return <div>
{/* */}
</div>
}
const Notifier = () => {
const {assets} = useAssets();
// example usage based on your code
const [firstAsset] = assets
if(firstAsset) {
firstAsset.play();
}
return <div>
{/* */}
</div>
}
Don't forget to encapuslate those components within the AssetProvider. It's not required to put them directly as the children of the provider but somewhere bellow it.
export default function App() {
return (
<AssetProvider>
<Preloader />
<Notifier />
</AssetProvider>
);
}

Importing data from api to new module - .map() not a function

Below is the code for my biggest nightmare yet. I keep on getting the error that the apiData.map is not a function. Any body that can help please.
I also need to know why ApiGetData do not use react please.
I do get the api data but seems that I'm importing it incorrectly to ClassFilmData and I get the .map error. All help will be appreciated.
Tried to export films, ApiGetData in various way. Help received from other platforms was implemented but did not solve the problem. Searches - other swapi projects, import data react, sandbox, repo and other platforms
// import React from 'react';
import { ApiToGet } from "./ApiToGet";
const ApiGetData = async function() {
try {
const films = await Promise.all(
ApiToGet.map(url => fetch(url).then(resp => resp.json()))
);
console.log("film title - ", films.results);
return films;
} catch (err) {
console.log("oooooooops", err);
}
};
ApiGetData();
export default ApiGetData;
import React from "react";
import FilmsInfo from "./FilmsInfo";
const FilmsLoop = ({ apiData }) => {
return (
<div className="tc f1 unknown">
{apiData.map((answers, i) => {
return (
<FilmsInfo
key={i}
// title={ apiData.films.results[i].title }
/>
);
})}
</div>
);
};
export default FilmsLoop;
import React, { Component } from "react";
import FilmsLoop from "./FilmsLoop";
import ApiGetData from "./ApiGetData";
class ClassFilmData extends Component {
render() {
return (
<div>
<p className="tc f1">Wim - classfilmdata</p>
<FilmsLoop apiData={ApiGetData} />
</div>
);
}
}
export default ClassFilmData;
import React from "react";
const FilmsInfo = () => {
return (
<div className="tc bg-light-blue dib br3 pa3 ma3 grow bw2 shadow-5">
<p>Planet</p>
<p>FilmsInfo.js</p>
</div>
);
};
export default FilmsInfo;
That is because apiData is really ApiGetData which is a promise.
If you're trying to use the array returned by resolving this promise, you'll have to do something like this:
class ClassFilmData extends Component {
componentDidMount() {
const apiData = await ApiGetData();
this.setState({ apiData });
}
render() {
return(
<div>
<p className="tc f1">Wim - classfilmdata</p>
{this.state.apiData && <FilmsLoop apiData={ this.state.apiData }/> }
</div>
);
}
}

Categories