How to dynamically import a MDX Component in React? - javascript

I created an Article component which gets its title through the route parameters like this:
const Article = () => {
const params = useParams()
const dashedTitle = params.title.replace(/ /g, '-')
return (
<article>
<MyMDX />
</article>
);
}
I want to return a MDX file with the same name as the provided title. Simply returning the <MyMDX /> component works fine if I manually import it at the top of the article with import MyMDX from '../markdowns/mymdx.mdx. However, I don't see a way to import this file dynamically, depending on the tile.
Is there a way to do this or could I do this in a better way?

I managed to find a solution to this:
const Article = () => {
const params = useParams()
const [article, setArticle] = useState()
dashedTitle = params.title.replace(/ /g, '-')
useEffect(() => {
import(`../markdowns/${dashedTitle}.mdx`).then(module => {
setArticle(module.default)
}).catch(err => {
console.log(err)
setArticle(undefined)
})
}, [dashedTitle])
return (
<article>
{article ? article : <NotFound />}
</article>
);
}
With useEffect, import the MDX module and then set my article variable to the module's default export (which is exactly the generated JSX component). Thus, I can use it in the return segment in case it's not undefined.
Additionally, I found a similar answer that could help.

Related

Data not being passed from parent to child. (ReactJS)

I am currently making a Trello Clone. It has been going well so far and I've had a lot of help from everyone here, so thank you!
My current issue is that I am trying to pass the state of modalData in App.js to <ModifyModal />.
I have tried researching and Googling, and even re-writing functions and creating new ones. However, nothing had worked. I know that the state is being updated with the correct text since I made the title from Trello Clone! to {modalData} and it worked. I want the data of modalData to be passed from App.js to <ModifyModal />.
Edit: Made a functional component and it is still showing undefined for the data.
App.js:
import React, { Component } from 'react';
import './App.css';
import Todobox from './Todobox';
import ModifyModal from './ModifyModal';
import Item from './Item';
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
const Widget2 = () => <ModifyModal />
class App extends Component {
constructor(props){
super(props);
this.handleCallback = this.handleCallback.bind(this);
this.state={
elements: [],
modal: [],
modalData: null
}
}
// Creates new element box
handleNewElement = () => {
const newElement = [...this.state.elements, Widget];
this.setState({
elements: newElement
});
}
handleCallback = (itemWidget, itemData) =>{
const newModal = [...this.state.modal, itemWidget];
const newData = itemData;
this.setState({
modal: newModal,
modalData: newData
});
}
render() {
const { elements, modal, modalData } = this.state;
return (
<>
<div className='page-container'>
<div className='header'>
<a className='header-title'>{modalData}</a>
<a className='header-button' onClick={this.handleNewElement.bind(this)}>Create a list</a>
</div>
<div className='element-field'>
{elements.length !== 0 &&
elements.map((Widget, i) => <Widget key={i} parentCallback2={this.handleCallback}/>)}
</div>
</div>
{modal.length !== 0 &&
modal.map((Widget2, i) => <Widget2 key={i} itemDataToChild={modalData} />)}
</>
);
}
}
export default App;
ModifyModal.jsx:
import React from "react";
import { useState } from "react";
import trash from './trash_can.png';
import './App.css'
function ModifyModal({ itemDataToChild }){
const [hideModal, setHideModal] = useState(false);
const [content, setContent] = useState(itemDataToChild);
const handleCancel = () =>{
setHideModal(true);
}
return(
<>
<div className={`modify-modal-container ${hideModal ? 'modify-modal-container-hide' : ''}`}>
<div className='modify-modal'>
<a className='modify-title'>{content}</a>
<textarea className='modify-input' />
<div className='modify-buttons'>
<a className='modify-btn' id='modify-update-btn'>Update</a>
<a className='modify-btn' id='modify-cancel-btn' onClick={handleCancel}>Cancel</a>
<img src={trash} id='modify-delete'/>
</div>
</div>
</div>
</>
)
}
export default ModifyModal;
Any help is appreciated since I am new to this. :)
The problem is when you declared and initialized Widget2.
const Widget2 = () => <ModifyModal />
What is actually happening under the hood is that Widget2 received a function which returns a JSX.Element, it didn't actually become ModifyModal, the functional component.If you look at the line above is actually doing right.
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
There is 2 solution for this.
you can do just as Widget.
const Widget2 = ({itemDataToChild}) => <ModifyModal itemDataToChild={itemDataToChild}/>
Which I think should be the best approach since you can just rename your imports if was exported as default, and deleting the line const Widget2 = () => <ModifyModal />
import Widget2 from './ModifyModal';
Keeping in mind that the second approach would result error if used for Named Exports. Imports Reference.
For broad your understanding of JSX element and functional component I recommend take a look at their official documentation.
JSX, Components and Props

React child callback not being executed after being passed down twice

I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});

react fetching and mapping data

import React,{useState, useEffect} from 'react'
import { useParams } from 'react-router-dom'
import Home from './Home'
import './detailpage.css'
function DetailPage({name,
info,
genre,
_id,
episodeNumber,
poster}) {
const [shows, setShows]= useState([{name:'',
info:'',
airingDate:'',
_id:'',
genre:'',
episodeNumber:'',
poster:''
}])
const params= useParams();
useEffect(()=>{
fetch("/home")
.then(res => res.json())
.then(jsonRes => setShows(jsonRes))
}, [])
const b = JSON.stringify(params);
const newShows = shows.filter(a=>a._id===b)
console.log(newShows)
return (
<div>
<h2>.</h2>
<h2>.</h2>
<h2>.</h2>
<h2>{JSON.stringify(params)}</h2>
<h2>{shows.genre}</h2>
{newShows.map(a=>
<div>
<div className='container'>
<img className='showImg' src={a.poster} alt=''></img>
<h2 className='showTitle'>{a.title}</h2>
<h3>{a.genre}</h3>
<p className='showInfo'>{a.info} </p>
</div>
</div>
)}
<h2>{episodeNumber}</h2>
<h2>{shows.info}</h2>
</div>
)
}
export default DetailPage
I have tv shows on my Home page and after clicking the image I want it to load the detail page about the clicked show however I couldn't manage to do it. I tried 'filter' method in the code but it didn't work I also tried like this
const newShows = shows.filter(a=>a.genre.length>5)
it works but this is not what I want. I would be really happy if someone could've helped. Thank you so much.
If I were you, I wouldn't use this fetch, as when you click on the image from your home you already know which tv show you want to display more details about.
I would use something like useLocation from react-router-dom, and while changing pages (home -> detail page about a tv show) carry a state variable with the specific tv show details.
https://v5.reactrouter.com/web/api/Hooks/usehistory
const handleClick = (state) => {
history.push({ pathname: "/detail-page", state })
}
<YourTvShowImage onClick={() => handleClick(TvShowData)} />
Then on your detail page class you use something like
https://v5.reactrouter.com/web/api/Hooks/uselocation
const location = useLocation()
const [tvShowData, setTvShowData] = useState()
useEffect(() => {
if (location.state) {
setTvShowData(location.state)
}
}, [location])

React-Markdown Custom Component Declaration, How Can I Declare in the Renderer to Use a Custom Component?

Problem
Using React-Markdown I can fully use my custom built components. But this is with specific pre-built keywords in the markdown. Like paragraph or images. That works PERFECTLY. But the problem is that these seem to all be pre-built words/conditions like paragraphs, headers, or images.
I can't find a way to add something new key word in my markdown like "CustomComponent" to be used. That's all I need at this point ><
This works just fine for me to make the markdown's image into a custom "footer" component I made elsewhere. I know it's ridiculous but it works. But I have no idea how to make this renderer accept/create a new keyword like "emoji" or "customComponent" or "somethingSilly".
let body =
`![Fullstack React](https://dzxbosgk90qga.cloudfront.net/fit-in/504x658/n/20190131015240478_fullstack-react-cover-medium%402x.png)`;
const renderers = {
image: () => <Footer/>
};
<ReactMarkdown source={body} renderers={renderers} />;
Some past work I did:
Some documentation:
https://reposhub.com/react/miscellaneous/rexxars-react-markdown.html
https://github.com/rexxars/commonmark-react-renderer/blob/master/src/commonmark-react-renderer.js#L50
Examples:
https://codesandbox.io/s/react-markdown-with-custom-renderers-961l3?from-embed=&file=/src/App.js
But nothing indicates how I can use "CustomComponent" to indicate to inject a custom component.
Use Case / Background
I'm trying to retrieve an article from my database that is formatted like so in markdown (basically a giant string). I'm using regular react with typescript and redux-- this is the only portion of my application that needs this.
"
# Title
## Here is a subtitle
Some text
<CustomComponentIMade/>
Even more text after.
<CustomComponentIMade/>
"
I know its most likely a little late for your purposes, but I've managed to solve this issue using a custom remark component.
Essentially you'll need to use the remark-directive plugin as well as a small custom remark plugin (I got this plugin straight from the remark-directive docs)
Then in react markdown you can specify the plugins, custom renderers and custom tags for eg.
import React from 'react'
import ReactMarkdown from 'react-markdown'
import {render} from 'react-dom'
import directive from 'remark-directive'
import { MyCustomComponent } from './MyCustomComponent'
import { visit } from "unist-util-visit"
import { h } from "hastscript/html.js"
// react markdown components list
const components = {
image: () => <Footer/>,
myTag: MyCustomComponent
}
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform
function transform(tree) {
visit(tree, ['textDirective', 'leafDirective', 'containerDirective'], ondirective)
}
function ondirective(node) {
var data = node.data || (node.data = {})
var hast = h(node.name, node.attributes)
data.hName = hast.tagname
data.hProperties = hast.properties
}
}
render(
<ReactMarkdown components={components} remarkPlugins={[directive, htmlDirectives]}>
Some markdown with a :myTag[custom directive]{title="My custom tag"}
</ReactMarkdown>,
document.body
)
So in your markdown wherever you have something like :myTag[...]{...attributes} you should render the MyCustomComponent with attributes as props.
Sorry I haven't tested the code, but hopefully it communicates the gist of things, if you need a working example let me know and I'll do my best to set one up.
I have tried this way and it worked for me
import CustomNextImage from '#commonComponentsDependent/CustomNextImage';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import { Options } from 'react-markdown/lib/ast-to-react';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
import remarkGfm from 'remark-gfm';
const SyntaxHighlighterDynamic = dynamic(() => import('./SyntaxHighlighter'));
import classes from './styles.module.css';
interface Props {
content: string;
}
type TCustomComponents = Options['components'];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const MdToHTML: FC<Props> = ({ content }) => {
const customComponents: TCustomComponents = {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
img(image) {
if (!image.src) return <></>;
return (
<div className={classes['img-container']}>
<CustomNextImage
src={image.src}
alt={image.alt}
/>
</div>
);
},
a({ href, children, node }) {
if (!href) return <></>;
if (
href.startsWith('/') ||
href.startsWith('https://lognmaze.com')
) {
return (
<Link href={href} prefetch={false} passHref>
<a>{children}</a>
</Link>
);
}
return (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
>
{children}
</a>
);
},
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighterDynamic language={match[1]} PreTag='div' {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighterDynamic>
) : (
<code className={className} {...props} data-code-inline='true'>
{children}
</code>
);
},
};
return (
<ReactMarkdown
components={customComponents}
remarkPlugins={[remarkGfm]}
>
{content}
</ReactMarkdown>
);
};
export default MdToHTML;

Warning Prop `href` did not match. using react server-side-rendering

I am using react-router-dom and I am guessing that this is causing the problem, but I have no clue where to even start looking or how to fix it. I also am getting errors like Warning: Did not expect server HTML to contain a <nav> in <div>.
As I stated, I'm not really sure where to look so if you think there is certain code that would be helpful please let me know and I will post it. Otherwise, I can post my code that I use to do SSR.
EDIT: Exact error: Warning: Prophrefdid not match. Server: "/profile/5a073dc44cb45b00125e5c82" Client: "profile/5a073dc44cb45b00125e5c82"
I have checked the client and it has /profile/:id so not sure where it says there is not a /, as for the other error with the <nav> in <div> , I have a nav inside my header , but I'm not really sure how to go about "fixing" that.
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import serialize from 'serialize-javascript';
import { Helmet } from 'react-helmet';
import { matchRoutes } from 'react-router-config';
import routes from './src/routes';
import createStore from './src/stores';
function handleRender(req, res) {
let initial = {};
if (req.vertexSession != null && req.vertexSession.user != null) {
initial.user = { currentUser: req.vertexSession.user };
}
const store = createStore.configure(initial); // create Store in order to get data from redux
const promises = matchRoutes(routes, req.path)
.map(({ route, match }) => {
// Matches the route and loads data if loadData function is there
return route.loadData
? route.loadData(store)
: route.loadDataWithMatch ? route.loadDataWithMatch(store, match) : null;
})
.map(promise => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve); // lets all data load even if route fails
});
}
});
Promise.all(promises).then(() => {
const context = {};
if (context.url) {
return res.redirect(301, context.url); // redirect for non auth users
}
if (context.notFound) {
res.status(404); // set status to 404 for unknown route
}
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>{renderRoutes(routes)}</div>
</StaticRouter>
</Provider>
);
// console.log(store.getState());
const initialState = serialize(store.getState());
const helmet = Helmet.renderStatic();
res.render('index', { content, initialState, helmet });
});
}
module.exports = handleRender;
Did you fix this already? I had the similar problem with my react app and fixed it. Here was my problem:
<Link to="./shop">Shop</Link>
my fix:
<Link to="/shop">Shop</Link>
Whatever you are rendering with the server is the issue. I suggest to comb through your routes module and see if you forgot a forward slash somewhere. If that doesn't work look at through the components your importing in the routes file.
To add to Kevorkian answer:
Make sure to prefix all the routes with a /
Note the /update
<Link href={{ pathname: '/update', query: { id: product.id } }}>
This happened to me in nextjs. It was because i had something like
Link=/auth/${isLogin?"sign-up":"login"}. This was an issue because there is no reference at compile time. The should have been isLogin ? "/auth/sign-up" : "/auth/login"}
This happened to me in a React/Next project because of next links.
This is the component
const Breadcrumb = (props) => {
return (
<Row>
<Col xs="12">
<div className="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 className="mb-0 font-size-18">{props.breadcrumbItem}</h4>
<div className="page-title-right">
<ol className="breadcrumb m-0">
<BreadcrumbItem>
<Link href="#">{props.title}</Link>
</BreadcrumbItem>
<BreadcrumbItem active>
<Link href="#">{props.breadcrumbItem}</Link>
</BreadcrumbItem>
</ol>
</div>
</div>
</Col>
</Row>
);
};
Here we have Link href="#" (This is the server side)
<li class="breadcrumb-item">
<a href="/search?searchTerm=a00&codeSets=1%2C2%2C3%2C4%2C5%2C6%2C7#">Home
</a>
</li>
After the page loaded, it becomes something like that above (This is the client side)
So we have a mismatch here in the DOM and we get this warning
SOLUTION
I was rendering this component on my pages and getting this warning. As a solution, i have implemented dynamic content approach checking some state values before rendering the element.
For instance in my code detail page, I have done that like below.
const CodeDetail = (props) => {
const [code, setCode] = useState<any>(null);
useEffect(() => {
if (props.router.query.code) {
if (codesData.filter(codeData => codeData.code == props.router.query.code).length > 0) {
setCode(codesData.find(codeData => codeData.code == props.router.query.code));
}
else {
setCode({});
}
}
}, [props.router]);
let pageContent;
if (code !== null) {
pageContent = (<Container fluid={true}><Breadcrumbs title="Codes" breadcrumbItem="Code Detail" /></Container>)
}
else {
pageContent = null;
}
return(
<React.Fragment>{pageContent}</React.Fragment>
);
};
I just want to chip in that with Next's Link component it NOT will accept an empty string for an href property like it does in native HTML. If you really want a link that links to nowhere you HAVE to use #. That's how I got my error.

Categories

Resources