Render Custom Component Inside NextJS Link - reactjs

Issue:
I have a component that should redirect users to a new page on click. I want to use Next's <Link> component to take care of the route change.
However, I've noticed that the Link component refuses to render if any of its children are not standard HTML elements (e.g., <div>, <a>, ...).
In the example below, the component I want to render is an icon from the heroicons library.
MyPage.js
import Link from 'next/link'
import { ArrowSmRightIcon } from 'heroicons-react'
export default function MyPage() {
return(
<Link href='/targetPage'>
<ArrowSmRightIcon />
</Link>
)
}
Running the above renders the following error:
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
undefined.
Attempted solution:
I've found some advice online about having to create a custom component that using forwardRef. I've implemented my custom component as:
IconLink.js
import NextLink from 'next/link'
import { forwardRef } from "react";
import { ArrowSmRightIcon } from 'heroicons-react'
const IconLink = forwardRef(({ href, prefetch, replace, scroll, shallow, locale, ...props }, ref) => {
return(
<NextLink
href={href}
replace={replace}
scroll={scroll}
shallow={shallow}
locale={locale}
passHref
>
<ArrowSmRightIcon ref={ref} {...props} />
</NextLink>
)
})
export default IconLink
MyPage.js
import IconLink from './IconLink';
export default function MyPage() {
return(
<IconLink
passHref
href='/targetPage'
/>
)
}
However, I still get the same error.
What should I do render a custom child component inside Next's <Link> component?

The problem is not from the next/link, the problem is from the import, This error indicates that there is no named export "ArrowSmRightIcon" in the package heroicons-react I believe what you're looking for is ArrowSmRight
import { ArrowSmRight } from 'heroicons-react'
Plus the package is deprecated as stated by the maintainers:
This package has been deprecated
Author message:
Official Heroicons V1 released. Please use the following for future >
projects: https://www.npmjs.com/package/#heroicons/react
the currently maintained version is:
https://www.npmjs.com/package/#heroicons/react

Related

React Draggable component is undefined

I have a draggable component in my project, but it throws an error whenever I start the dev server -
react-jsx-dev-runtime.development.js:87 Warning: React.jsx: 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 your code at draggable.js:9.
Here is my code for draggable.js:
import React, { Component } from "react";
import ReactDOM from 'react-dom';
import { Draggable } from "react-draggable";
console.log(Draggable); //undefined
export class Board extends Component {
render() {
return (
<div>
<Draggable position={{ x: 0, y: 0 }} handle=".handle">
<div>
<div className="handle">Drag me</div>
<div>I will move with the handle</div>
</div>
</Draggable>
</div>
);
}
}
Using the console.log statement I have found that the problem is the Draggable component on line 9 is undefined. But after reading the documentation for react-draggable I can't figure out why that is.
I ran npm install react-draggable and checked package.json. The project has react-draggable version 4.4.5 installed
The solution was to change the import statement
import { Draggable } from "react-draggable";
to
import Draggable from "react-draggable";
Note that DraggableCore import statement goes in curly brackets, that was the cause of my confusion

Link returns undefined when mapping array to JSX component

The following code gives an error when I visit the page this component is rendered on:
import React from 'react'
import Layout from '../components/layout'
import SEO from '../components/seo'
import {
Divider,
Card,
Link,
} from '#blueprintjs/core'
const TagPage = ({ pageContext }) => {
const { name, description, tagsCount, questions, } = pageContext
return (
<Layout>
<SEO title={name} description={description} />
<h1>{name}</h1>
<p>{description}</p>
<Divider />
<br/><br/>
{questions.map((q) => {
return (
<>
<Link
to={`tagsQuestion-${q.id}`}
style={{
color: `inherit`,
textDecoration: `none`,
}}
>
<Card interactive='true'>
<p>{q.id}</p>
<p>{q.prompt}</p>
</Card>
</Link>
<br/>
</>
)
})}
</Layout>
)
}
export default TagPage
However if I change:
<Link to={`/question/${q.id}`}>...</Link>
into
<a href={`/question/${q.id}`} >...</a>
then the page will render and be useable without any errors.
This is the current error message I am getting:
(AppContainer, in main (at layout.js:33)) 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 `TagPage
Question:
Why can I not use the Link component in this situation?
Info:
questions is an array
TagPage is a page created in gatsby-node.js
Thanks you, for your time, feel free to ask questions as needed.
replace
import {
Divider,
Card,
Link,
} from '#blueprintjs/core'
with
import { Link } from 'gatsby'
import {
Divider,
Card,
} from '#blueprintjs/core'

Sending a React.FunctionComponent<React.SVGProps<SVGSVGElement>> as a prop to another component

I'm attempting to import a React functionComponent from an SVG and then send that to another component as a prop to render that svg. With the setup below, this compiles fine, but eventually crashes when trying to render the svg in browser with:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
Classes below are simplified. But the gist of what I'm trying to do is:
In overlay.tsx:
import { ReactComponent as icon } from "/icon.svg";
import CustomItem from "/customItem";
const Overlay: React.FC<OverlayProps> = () => {
return (
<div>
<CustomItem icon={icon}/>
</div>
);
export default Overlay;
}
and in customItem.tsx:
import React from "react";
export interface CustomItemProps {
icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = ({icon}) => {
return (
<div>
{icon}
</div>
);
};
export default ApplicationsDropdownItem;
I assume my problem is somewhere around the syntax of {icon}, but I can not for the life of me find out what I'm suppose to use instead.
Answer
The icon you are importing is a component, therefore it must be called to render the JSX.
<Icon {...props}/> (correct) or {Icon(props)} (not recomended)
Since it is a component, you should also name it Icon and not icon.
Take a look at this blog post that explains SVGR.
TL;DR - Best approach for rendering components
A. Call the component in your render method with component syntax <MyComponent/> not MyComponent().
B. Instantiate your component as a variable, and pass that to your render method's JSX block.
More info
#DustInCompetent brought to light the issue of calling a component as a function inside a JSX block.
As explained here and here, that will lead to react not registering a components hooks and lead to state and other problems.
If you are implementing a High Level Component (HOC), then you should not call a component within the render method (return statement in functional components), as this leads to problems for similar registration issues of the component.
import React from "react";
import { ReactComponent as SampleIcon } from "/sample_icon.svg";
export interface CustomItemProps {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = (props) => {
const Temp = props.Icon as React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
return (
<div>
<Temp/>
</div>
);
};
<CustomItem Icon={SampleIcon}/>
I think you should use <Icon /> instead of {icon} because it's a component.

React not rendering multiple components through factory function

The problem is the following, i have a factory:
import React from 'react';
export default (componentFactory) =>
{
return Component =>
{
class Factory extends React.Component
{
render()
{
let { state, props } = this;
return componentFactory({ state, props }, Component);
}
}
return Factory;
};
}
and then a simple Page component wrapper:
import React from 'react';
const Page = ({children, appState}) =>
(
<section>
{React.cloneElement(children, {appState})}
</section>
);
export default Page;
So, in order to create a HomePage, i use HomeFactory likewise:
import React from 'react';
import Factory from '../Factory';
import HeroBanner from '../Banner/HeroBanner';
const HomeFactory = ({state, props}, HomePage) =>
{
return <HomePage {...props} {...state} >
<HeroBanner />
</HomePage>;
};
export default Factory(HomeFactory);
The final code for the HomePage component that is being mounted, is then as follows:
import React from 'react';
import Page from '../Page';
import HomeFactory from '../HomeFactory';
const HomePage = ({children, appState}) =>
{
return (
<Page appState={appState}>
{children}
</Page>
);
};
export default HomeFactory(HomePage);
The problem seems to be the HomeFactory component, in which i put:
return <HomePage {...props} {...state} >
<HeroBanner />
</HomePage>;
as per example given, and this works 100% correctly, however, when i try to put multiple components in form of array or just many components nested in, like:
return <HomePage {...props} {...state} >
<HeroBanner />
<HeroBanner />
<HeroBanner />
</HomePage>;
I am getting the following error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
Basically, the Page wrapper component is getting properties passed in as children of its parent component HomePage, and these are taken from the factory method.
The question is what is happening implicitly with JSX that I cannot do the following?
In your Page component you need to use cloneElement along with Children.map when you pass multiple children (this.props.children is then a special array-like object)
See this answer: stackoverflow.com/a/38011851/3794660 for more details.

Unable to implement Leftnav with AppBar component in material-ui

I am having a lot of trouble trying to implement material-ui's AppBar with Leftbar as there seems to be lots of different variations on component declaration/imported dependencies syntax and components. I can't even find proper documention on http://www.material-ui.com/ about Leftnav in the first place, all they give with Appbar is a static hamburger menu example. My code is as follows:
import React, { Component } from 'react'
import { LeftNav, AppBar} from 'material-ui'
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import { Router, Route, Navigation, Link, browserHistory } from 'react-router'
export default class Header extends Component {
getChildContext() {
return {muiTheme: getMuiTheme(baseTheme)};
}
_toggleNav(){
this.refs.leftNav.toggle();
}
render() {
const data = this.props.data
const nav_items = data.globals.nav_items
//Menu item links
const menu_items = nav_items.map(( nav_item ) => {
return (
<span key={ 'key-' + nav_item.value }>
<Link to={ '/' + nav_item.value }>{ nav_item.title }</Link>
</span>
)
})
return (
<div>
<LeftNav ref='leftNav'
docked={false}
menuItems={ menu_items } />
<AppBar title="App Bar Example" onLeftIconButtonTouchTap={this._toggleNav}
isInitiallyOpen={true} />
</div>
)
}
}
Header.childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
};
I am getting to obscure errors which explain me nothing about what am actually doing wrong:
VM15235:27 Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of `Header
and:
invariant.js:38 Uncaught (in promise) Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of `Header`.(…)
Is there a way of making this more explicit?
There was a change to material-ui that renamed LeftNav to Drawer! It seems like certain docs/examples haven't been updated. So if you use Drawer you will find documentation and it should work! Here is the commit/thread from the change away from LeftNav.
https://github.com/callemall/material-ui/issues/2697

Resources