How to dynamically import a component in React? - javascript

I am trying to optimize a component of mine with dynamic rendering, but I'm facing some issues, so this is my current situation:
I have an Alert component that renders an alert message, along with an icon.
I have a Icons module, which is a library of icons
I am currently rendering the icon as follows (this is not actual code, it's just to give the idea):
import * as icons from '../icons';
import DefaultIcon from '../icons';
function Alert(iconName='defaultIcon', message) {
function getIcon(iconName) {
if (iconName === 'defaultIcon') {
return DefaultIcon()
}
return icons[iconName]
}
return (
<div>
<span>{getIcon(iconName)}</span>
<span>{message}</span>
</div>
)
}
So, suppose Alert gets called without iconName most of the times, so the components doesn't need to import all of the icons at all.
I would like to avoid including all of the icons in the Alert component by default, but only doing so if iconName is specified
Is it even possible to do so?

I don't think it's possible this way.
Maybe create a component for the Icon that imports the icon libary. In the Alert component you could implement the Icon component as a child:
<Alert message="Alert!">
<Icon iconName="defaultIcon" />
</Alert>

You should import icons dynamically with type or name etc. something like below.
import React from 'react';
export default function Icon({ type, ...props }) {
const Icon = require(`./icons/${type}`).default;
return <Icon {...props} />
}
import Icon from './Icon';
<Icon type="addIcon" />

Ok, looks like I managed, and that's how I did it:
import DefaultIcon from '../icons';
function Alert(message, iconName="") {
const [icon, useIcon] = useState();
//componentDidMount
const useEffect(() => {
//if an icon name is specified, import the icons
if (iconName) {
import("./icons").then(icons => setIcon(icons[iconName]))
} else {
setIcon(DefaultIcon)
}
}
,[])
return (
<span>{icon}</span>
<span>{message}</span>
)
}

Related

Reload a page after the initial load with Next.js

I have a route called "./checkout" that renders embedded elements from Xola. The issue is I am using client side routing and the page needs a refresh to load the checkout page correctly (if not, Xola elements do not show up on the DOM 1). When I try to reload the page on the initial load I get an infinite reload loop. I can't use a href for specific reasons so I need to continue to use Next.js routing. Anyway I can go about this? EDIT: I have reached out to Xola support team for further assistance.
After refresh
checkout.js
import Head from "next/head";
import { useRouter } from "next/router";
import { Container, Button } from "#mui/material";
import { makeStyles } from "#mui/styles";
import { CheckoutCard } from "../components/layout/directory";
import useIsSsr from "#/config/useSsr";
function Checkout() {
const isSsr = useIsSsr();
const router = useRouter();
const classes = useStyles();
return (
<>
{isSsr ? null : window.location.reload()}
<Head>
<title>checkout</title>
</Head>
<Container className={classes.root}>
<Button
className={classes.btn}
onClick={router.back}
color="secondary"
variant={"contained"}
>
back
</Button>
<CheckoutCard />
</Container>
</>
);
}
const useStyles = makeStyles((theme) => ({
root: { marginTop: theme.spacing(10) },
btn: { marginBottom: theme.spacing(5) },
}));
export default Checkout;
CheckoutCard.js
function CheckoutCard() {
return (
<div
className="xola-embedded-checkout"
data-seller="5f3d889683cfdc77b119e592"
data-experience="5f3d8d80d6ba9c6b14748160"
data-version="2"
id="xola-checkout"
></div>
);
}
export default CheckoutCard;
Please add one more prop to CheckoutCard component calling in checkout.js.
You need to update
<CheckoutCard
url={`https://checkout.xola.com/index.html#seller/5f3d889683cfdc77b119e592/experiences/${
url && url.slice(1)
}?openExternal=true`}
/>
to
<CheckoutCard
url={`https://checkout.xola.com/index.html#seller/5f3d889683cfdc77b119e592/experiences/${
url && url.slice(1)
}?openExternal=true`}
key={new Date().getTime()}
/>
"key" prop is to identify the component and you are going to use external service ( like iframe, not sure correctly )
So in order to render the embedded elements from Xola, you should add "key" prop for CheckoutCard component calling.

React: Component name based on prop

I am trying to load a component in React via a prop. It is an icon that I want to pass from the parent component.
Dashboard (parent):
import { Button } from './components';
function App() {
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" />
<Button icon="FiSun" />
</div>
</div>
);
}
Button (child):
import React from 'react';
import * as Icon from "react-icons/fi";
import './button.scss';
function Button(props) {
return(
<button>
// Something like this
<Icon.props.icon />
</button>
)
}
Unfortunately, I can't find an easy way to make this work since I'm not allowed to use props in the component name.
Here is a working version :
import * as Icons from "react-icons/fi";
function Button(props) {
const Icon = Icons[props.icon];
return <button><Icon/></button>;
}
I added an example on stackblitz
I doubt this is the pattern you want.
If App knows the name of the component Button should render, you really aren't providing any abstraction by not passing the component reference itself. You might be able to get it to work passing the string like this, but I wouldn't recommend going that route.
Instead, I would pass the component reference to Button like this:
import FiSun from '...';
...
<Button icon={FiSun} />
function Button(props) {
const Icon = props.icon; // Alias as uppercase
return(
<button>
<Icon />
</button>
)
}
Or if you want only the Button component to know about the possible icon types, I would suggest using a normal conditional instead of trying to dynamically create the JSX tag:
function Button(props) {
function renderIcon() {
if (props.icon == 'FiSun') {
return <FiSun />;
} // else etc
}
return(
<button>
{renderIcon()}
</button>
)
}
To provide some stability while still keeping the functionality of allowing the component user to pass in any available icon name, you could do something like this:
function Button(props) {
function renderIcon() {
const I = Icon[props.icon];
if (I) {
return <I />;
}
// Icon is not valid, throw error or use fallback.
if (in_development) {
console.error('[Button]: Invalid prop `icon`. Icon '+props.icon+' does not exist.');
}
return <FallbackIcon />
}
return(
<button>
{renderIcon()}
</button>
)
}

How should I traverse through a folder of .gifs to display them in a list using Gatsby.js

I am trying to figure out how to best display a list of gifs, for starters, located locally in a directory on my machine using Gatsby.js. I did a long Gatsby tutorial that went through using gatbsy-remark and gatsby-image-remark, etc and found that those plugins did not like .gifs... Specifically, anything that required the gatsby plugin 'gatsby-plugin-sharp' and 'gatsby-transformer-sharp' did not work when trying to display gifs. GrahpQL even warns you that targeting childImageSharp on an object that is a .gif will return null and it does...
So I have this gatsby code I'm using in a navigation component...
import React from "react"
import Logo from "../images/glitch-logo.gif"
import Menu from "./menu"
import * as headerStyles from './header.module.scss'
class Navigation extends React.Component {
render() {
return (
<>
<img className={headerStyles.logo} src={Logo} alt="NFT Category Filter" onClick={() => this.toggleMenu()} onKeyDown={this.handleClick}></img>
<Menu ref={el => (this.childMenu = el)} />
</>
)
}
toggleMenu() {
this.childMenu.open()
}
}
export default Navigation
can't I just do something similar in my index file if, for example, I wanted to display an array of .gifs? Something like this?
import * as ImageList from '../images/categories' //folder with .gifs!!!
import Layout from '../components/layout'
import * as layoutStyles from '../components/layout.module.scss'
const IndexPage = () => {
const ImageList = (props) => {
const nfts = props.nfts;
const listImages = nfts.map((nft) => {
<li>{nft}</li>
}
)
}
or is there a plugin for gatsby worth downloading that makes using .gifs easier?
Your approach, assuming all the notation-related stuff (paths, naming, etc) is correct. However, your IndexPage should look like:
const IndexPage = (props) => {
const nfts = props.nfts;
return <ul>
{nfts.map((nft) => {
return <li>{nft}</li>
}}
</ul>
}
.map loops need to return an array, you were missing the statement.
The props are inherited from the top-level components, not from their inner functions. So props, should be got by IndexPage and used in the JSX loop in the return statement of the component.

How to create a link on an image in react-native (web/android/ios)?

I want to display two clickable image with Available on App Store and Available on Google Play on my landing page which I also make with react-native-web.
I am able to create an <ExternalLink /> component, but it does not really look like a normal <a> yet (no hover or effect at the moment => ugly).
import React from 'react';
import { Text } from 'react-native';
const ExternalLink = (props) => (
<Text
{...props}
accessibilityRole="link"
target="_blank"
/>
);
export default ExternalLink;
I have tried to use that component around an <Image /> as you would normally do in web with <a target="_blank" href="https://www.google.com"><Image src={availableOnGooglePlay} /></a>.
More generally, how can I create a link on an image in react-native for all devices (iOS, Android, Web)?
I would use any of the Touchable components to achieve this, I checked the RN Web docs and they've mostly been ported. Here's a small example:
export default class ExternalLink extends Component {
_openLink = async () => {
const { link } = this.props;
if (await Linking.canOpenURL(link)) {
Linking.openURL(link);
}
}
render() {
const { children } = this.props;
return (
<TouchableOpacity accessibilityRole='link' onPress={this._openLink}>
{children}
</TouchableOpacity>
);
}
}
Which you can then use like so:
<ExternalLink link='YOUR_LINK_HERE'>
<Image source='YOUR_IMAGE_URL_HERE' />
</ExternalLink>

Javascript - React Native. Child Component (Dynamic List) does not render when Render() function is called

I'm working on an app and one of its parts consists on a list of players. Each item on the list contains the name of the player, the points that he/she has and a IOS stepper to change de points.
See the app screen I'm talking about.
Every time I modify the points of a player using the IOS stepper the state of the App changes according to that value. However, the text containing the points of the player does not get modified. The render function is called and the state of the app has not mutated so the text component should change as well. I have found out that when the render function is called it "jumps" over the List component so this isn't rendered and renderRow function isn't called.
The text only shows the current player's points when I change to the second tab of the TabBar and then I go back to the first one. In that case the List component gets rendered.
I think this is a really weird situation and I have tried everything to solve it but I haven't found any solution. I leave the code below.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {View, Text} from 'react-native';
import {Container, Content, Picker, Header, Title, Subtitle, Button, Left,
Right, Body, Icon, List, ListItem, Input } from 'native-base';
import {ferLlistaRondes, canviDeRonda, crearObjectePPR, modificarPunts,
canviarColorPunts} from '../actions';
import SimpleStepper from 'react-native-simple-stepper';
class SetPoints extends Component {
constructor() {
super()
this.modifyPunts = this.modifyPunts.bind(this);
this.changeDeRonda = this.changeDeRonda.bind(this);
this.renderRow = this.renderRow.bind(this)
}
modifyPunts(value, nomJugador) {
this.props.modificarPunts(nomJugador, value, this.props.currentRound)
}
changeDeRonda(novaRonda) {
this.props.canviDeRonda(novaRonda);
}
renderRow(data) {
return(
<ListItem icon>
<Left>
<Icon name = 'md-person'/>
</Left>
<Body>
<Text>{data}</Text>
</Body>
<Right>
<View style = {styles.pointsViewStyle}>
<Text>
{this.props.ObjRondaJugadorPunts[data][this.props.currentRound-1]}
</Text>
</View>
<SimpleStepper
initialValue = {this.props.ObjRondaJugadorPunts[data]
[this.props.currentRound-1]}
stepValue = {5}
minimumValue = {-100}
maximumValue = {100}
padding = {2}
valueChanged = {(value) => this.modifyPunts(value,data)}
/>
</Right>
</ListItem>
)
}
render() {
return (
<Container>
<Content>
<List
dataArray={this.props.llistaFinal}
renderRow={(data) => this.renderRow(data)}
/>
</Content>
</Container>
)
}
}
const styles = {
subtitleStyle: {
fontSize: 12,
fontFamily:'Noteworthy'
},
pointsViewStyle: {
paddingRight:10
},
pickerViewStyle: {
alignItems: 'center'
}
}
const mapStateToProps = (state) => {
return {
numJugadors: state.appRender.jugadors,
numRondes: state.appRender.rondes,
llistaFinal: state.appRender.llistaNomsAcabada,
llistaRondes: state.appRender.llistaRondes,
currentRound: state.appRender.currentRound,
ObjRondaJugadorPunts: state.appRender.roundPlayerPointsObj,
colorPunts: state.appRender.colorPunts
}
}
export default connect(mapStateToProps, {ferLlistaRondes, crearObjectePPR,
canviDeRonda, modificarPunts, canviarColorPunts})(SetPoints);
I hope you can help me fix that, thanks!
I finally made it to solve the problem. Native Base dynamic list component is based on react native list view. List view only rerenders if its prop dataSource changes. As I could not change native base dynamic list dataSource prop (because it does not directly have this prop, it has a similar one called dataArray which does not produce a rerender when it changes), I ended up using a react native list view component. Therefore I only needed to change the data source prop every time I want it to rerender, so items inside every row were refreshing as well as spected.
For what I read in some posts, native base is working on a solution in order to offer the possibility to control the rerender of its list component (like react native offers through dataSource prop).

Categories