Dynamically import React Components based on input - javascript

I want to render different Components based on some checkboxes selection pattern without having to import components that may not be used.
I have an Array which contains the Component names (I used numbers as an example) and I want to import each component based on the values of the array.
I came up with something like this:
import {Suspense} from 'react'
export default function CreationForm() {
const docs = [1,3,5]
return (
<Suspense fallback={<div>Loading...</div>}>
{
docs.map(val => React.lazy(() => import(`documents/${val}.jsx`)))
}
</Suspense>
)
}
I know this solution does not work but I think it explains what I am trying to do.
I could try using state but the actual "docs array" is an state variable in the real application so it could cause duplicated state.
I did this as a test and worked:
const A = React.lazy(() => import(`documents/1.jsx`))
...
*** SNIP ***
...
<Suspense fallback={<div>Loading...</div>}>
{
docs.map((val) => <A/>)
}
</Suspense>
But I cannot dynamically import each component like this.

Ok, so you don't need conditional imports, you just want to do conditional rendering. That's waaaaay simpler.
Example:
import { FormA } from "./FormA";
import { FormB } from "./FormB";
const MyComponent = ({ which }) => {
return <>
{which === "form-a" && <FormA />}
{which === "form-b" && <FormB />}
<>;
};

Related

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

React Hooks - Preventing child components from rendering

As a newbie in React, it seems that re-rendering of components is the thing not to do.
Therefore, for example, if I want to create a menu following this architecture :
App is parent of Menu, which have a map function which creates the MenuItem components
menu items come from a data source (here it's const data)
when I click on a MenuItem, it updates the state with the selected MenuItem value
for now it's fine, except that all the components are re-rendered (seen in the various console.log)
Here's the code :
App
import React, { useState} from "react"
import Menu from "./menu";
function App() {
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
const [selectedItem, setMenuItem] = useState(null)
const handleMenuItem = (menuItem) => {
setMenuItem(menuItem)
}
return (
<div className="App">
<Menu items = {data} handleMenuItem = {handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Menu
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key = {index} handleMenuItem = {props.handleMenuItem} value = {item}></MenuItem>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
MenuItem
import React from "react";
const MenuItem = (props) => {
return (
<>
<div onClick={() => props.handleMenuItem(props.value)}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);
as you might see, I've used the React.memo in the end of MenuItem but it does not work, as well as the PureComponent
If someone has an idea, that'd be great to have some advice.
Have a great day
Wrap your handleMenuItem function with useCallback to avoid rerendering when the function changes. This will create a single function reference that will be used in the MenuItem as props and will avoid rereading since it's the same function instance always.
I have used an empty dependency array in this case which is correct for your use case. If your function has any state references then they should be added to the array.
const handleMenuItem = useCallback((menuItem) => {
setMenuItem(menuItem);
}, []);
There's a lot to unpack here so let's get started.
The way hooks are designed to prevent re-rendering components unnecessarily is by making sure you use the same instance of any unchanged variables, most specifically for object, functions, and arrays. I say that because string, number, and boolean equality is simple 'abc' === 'abc' resolves to true, but [] === [] would be false, as those are two DIFFERENT empty arrays being compared, and equality in JS for objects and functions and arrays only returns true when the two sides being compared are the exact same item.
That said, react provides ways to cache values and only update them (by creating new instances) when they need to be updated (because their dependencies change). Let's start with your app.js
import React, {useState, useCallback} from "react"
import Menu from "./menu";
// move this out of the function so that a new copy isn't created every time
// the App component re-renders
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
function App() {
const [selectedItem, setMenuItem] = useState(null);
// cache this with useCallback. The second parameter (the dependency
// array) is an empty array because there are no items that, should they
// change, we should create a new copy. That is to say we should never
// need to make a new copy because we have no dependencies that could
// change. This will now be the same instance of the same function each
// re-render.
const handleMenuItem = useCallback((menuItem) => setMenuItem(menuItem), []);
return (
<div className="App">
<Menu items={data} handleMenuItem={handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Previously, handleMenuItem was set to a new copy of that function every time the App component was re-rendered, and data was also set to a new array (with the same entries) on each re-render. This would cause the child component (Menu) to re-render each time App was re-rendered. We don't want that. We only want child components to re-render if ABSOLUTELY necessary.
Next is the Menu component. There are pretty much no changes here, although I would urge you not to put spaces around your = within your JSX (key={index} not key = {index}.
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key={index} handleMenuItem={props.handleMenuItem} value={item}/>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
For MenuItem, let's cache that click handler.
import React from "react";
const MenuItem = (props) => {
// cache this function
const handleClick = useCallback(() => props.handleMenuItem(props.value), [props.value]);
return (
<>
<div onClick={handleClick}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);

Learning React: how to useRef in custom component?

I'm learning React and I don't think I understand the concept of useRef properly. Basically, I want to include some tags in tagify input field when a user clicks on a chip that is rendered outside the input box.
My idea is to do something like this (App.js):
import Chip from '#material-ui/core/Chip';
import Tagify from "./Tagify"
...
class App extends React.Component {
...
const { error, isLoaded, quote, tags } = this.state; //tags comes from the server
var tagify = <Tagify tags={tags} />
const addTagOnChipClick = (tag) => {
tagify.addTag(tag)
};
const chips = tags.map(tag => (
<span key={tag.name} className="chips">
<Chip
label={tag.name}
variant="outlined"
onClick={addTagOnChipClick(tag)}
clickable
/>
</span>
))
...
}
The tagify documentation says that
To gain full access to Tagify's (instance) inner methods, A custom ref can be used: <Tags tagifyRef={tagifyRef} ... />
My attempt to gain access to these inner methods was to use useRef (Tagify.js):
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}
However, tagifyRef.current is undefined. What I'm doing wrong? There's another way to access the inner methods?
Thank you very much!
When are you accessing the ref? Make sure you access the ref only after the component has mounted i.e. in a useEffect:
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
React.useEffect(() => {
console.log(tagifyRef.current)
}, [])
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}

dynamically import a React Component if that file exists, otherwise show a default message

I want to conditionally import a React Component if the file exists and if not do something else. For example show a default view or message.
I tried this:
let Recipe;
try {
Recipe = require(`docs/app/Recipes/${props.componentName}`);
} catch (e) {
Recipe = () => <div>Not found</div>;
}
However the linter is complaining that I should not try to dynamicaly require a file, but use a string literal instead.
Is there a cleaner approach to to what I'm trying to achieve?
The problem is this approach is that it kills bundle optimizations and includes all files from docs/app/Recipes/ into a bundle, even if they aren't used.
A better way to write this is to use <React.Suspense> and React.lazy:
const Recipe = React.lazy(() =>
import(`docs/app/Recipes/${props.componentName}`)
.catch(() => ({ default: () => <div>Not found</div> }))
);
Which is used as:
<React.Suspense fallback={'loading...'}><Recipe/></React.Suspense>
A cleaner way to do this and avoid linter error is to have a map of possible components:
import Foo from 'docs/app/Recipes/Foo';
import Bar from 'docs/app/Recipes/Bar';
...
const componentsMap = { Foo, Bar };
...
const Recipe = componentsMap[props.componentName] || () => <div>Not found</div>;
In this case props.componentName can be validated if needed.
in fact there is. With the recent release of React v16.6.0 "lazy code splitting" was introduced. This is how it works, it makes sense to use it together with reacts' 'suspense':
import React, {lazy, Suspense} from 'react';
const Recipe = lazy(() =>import(`./docs/app/Recipes/${props.componentName}`));
function SomeComponent() {
return (
<Suspense fallback={<Spinner/>}>
<Recipe />
</Suspense>
);
}
To handle the case that the component isn't found you can use Error Boundaries. You would wrap your component with it like this:
<ErrorBoundary>
<Suspense fallback={<Spinner/>}>
<Recipe />
</Suspense>
</ErrorBoundary>
Best you read more about it directly on the react docs I linked above.
I have been troubled by this problem all afternoon, and now I have solved it:
If "../views/dev/dev.tsx" exists, import it, otherwise import '../views/not-found/not-found'
const requireCustomFile = require.context('../views/dev/', false, /dev.tsx$/);
let Dev = React.lazy(() => import('../views/not-found/not-found'));
if (requireCustomFile.keys()?.length) {
const keys: string[] = requireCustomFile.keys();
if (keys.includes('./dev.tsx')) {
const str = '/dev';
Dev = React.lazy(() => import(`../views/dev${str}`));
}
}
if dev.tsx not exit :
// ⬇️webpack report an error: Can't resolve module
import(`../views/dev/dev.tsx`))
// ⬇️webpack will not report an error until the load the module
const str = '/dev';
import(`../views/dev${str}`)

React inline functions rerender issue

I have a component that uses two nested components that are based on render prop pattern. I need to combine props from both of them to be sent to the innermost function.
<Component1>
{(...props1) => (
<Component2>
{(...props2) => <MyComponent {...props1} {...props2} />}
</Component2>
)}
</Component1>
Now, I wanted to refactor the above inline functions into class functions, so as to avoid creating new functions on every render.
First attempt:
render() {
return <Component1>{this._render1}</Component1>;
}
_render1 = (...props1) => <Component2>{this._render2}</Component2>;
_render2 = (...props2) => <MyComponent {...props1} {...props2} />;
But now, in render2, I don't have access to props1, so I did:
render() {
return <Component1>{this._render1}</Component1>;
}
_render1 = (...props1) => <Component2>{this._render2(...props1)}</Component2>;
_render2 = (...props1) => (...props2) => <MyComponent {...props1} {...props2} />;
But here, I am back again to original problem of recreating inline functions on each render (inside _render2).
Please suggest a way to mitigate this problem. How can I best send the combined data down? What am I doing wrong here?
Did you got a chance to take a look on React.Context (https://reactjs.org/docs/context.html)?
You can create something like:
SomeContext.jsx
import React from "react";
export const SomeContext = React.createContext();
index.jsx
<SomeContext.Provider value={this.state.contextState}>
<div>
....
<Component2 />
<MyComponent />
...
</div>
</SomeContext.Provider>
Component2.jsx / MyComponent.jsx
import React from "react";
import { SomeContext } from "./SomeContext";
export default () => (
<SomeContext.Consumer>
your jsx with access to the props from parent.
</SomeContext.Consumer>
);
Hope it helps you.

Categories

Resources