I'm writing an example app over here to get my head around React but one of my simpler components is throwing an error that I can't seem to track.
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. Check the render method of ContactListItem.
Here's the code for the ContactListItem component.
import React, { Component } from 'react';
import { ListGroupItem } from 'react-bootstrap';
import { Link } from 'react-router';
class ContactListItem extends Component {
render() {
const { contact } = this.props;
return (
<ListGroupItem>
<Link to={'/contact/${contact.id'}>
<h4>{contact.name}</h4>
</Link>
</ListGroupItem>
);
}
}
export default ContactListItem;
It's a pretty simple class. Nothing sticks out to me as problematic. For completion's sake, here's the parent component.
import React, { Component } from 'react';
import { ListGroup } from 'react-bootstrap';
import ContactActions from '../actions/ContactActions';
import ContactStore from '../stores/ContactStore';
import ContactListItem from './ContactListItem';
function getContactListItem(contact) {
return (
<ContactListItem key={contact.id} contact={contact} />
);
}
class ContactsComponent extends Component {
constructor() {
super();
this.state = {
contacts: []
}
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
ContactStore.addChangeListener(this.onChange);
}
componentDidMount() {
ContactActions.receiveContacts()
}
componentWillUnmount() {
ContactStore.removeChangeListener(this.onChange);
}
onChange() {
this.setState({
contacts: ContactStore.getContacts()
});
}
render() {
let contactListItems;
if ( this.state.contacts ) {
contactListItems = this.state.contacts.map(contact => getContactListItem(contact));
}
return (
<div>
<ListGroup>
{contactListItems}
</ListGroup>
</div>
);
}
}
export default ContactsComponent;
You get the error in ContactListItem#render() because Link is undefined. As of React Router v4, <Link /> is no longer a part of the react-router, but a part of the react-router-dom package. Changing this should fix your problem:
import { Link } from 'react-router-dom';
As per documentation,
use
import { Link } from 'react-router-dom';`
unlike react-router which used to work earlier.
But react-router-dom comes with:
HashRouter
BrowserRouter
MemoryRouter
Prompt
NavLink
StaticRouter, etc.
You might benefit from tree-shaking a lot, on using
import Link from 'react-router-dom/Link';
if all the above stuffs are not needed in your app.
Related
I am getting an error in my story, because my component is using <Link to="/" />. As far as I can understand the link will have no context in a story so I need to add a decorator, however most articles that describe this are not using the Component Story Format (CSF).
Any ideas how to do this globally or straight into the story?
Error: Invariant failed: You should not use <Link> outside a <Router>
This is my stories file.
// ./stories.tsx.tsx
import React from 'react'
import { LinkComponent } from '.'
import { StoryRouter } from 'storybook-react-router'
export default {
title: 'LinkComponent'
}
export const base = () => {
return <LinkComponent/>
}
This is my component:
// ./LinkComponent.tsx
import React from 'react'
import { Link } from 'react-router-dom'
const LinkComponent = () => {
return (
<section className="links">
<Link to="/">Home</Link>
</section>
)
}
I have tried addDecorator(StoryRouter()); globally in the storybook config as many people suggest, but it breaks the code.
// /.storybook/config.ts
import StoryRouter from 'storybook-react-router';
addDecorator(StoryRouter());
configure(require.context('../src', true, /stories\.tsx$/), module)
Total face-palm moment!!
There is no reason to add a decorator. You can simply import and wrap your component in <MemoryRouter/> to simulate the context of the link.
// ./stories.tsx
import React from 'react'
import { LinkComponent } from '.'
import { MemoryRouter } from 'react-router-dom'
export default {
title: 'LinkComponent'
}
export const base = () => {
return (
<MemoryRouter>
<LinkComponent/>
</MemoryRouter>
}
And... Bob's your uncle(I dont have an uncle called Bob!?!)!
I was trying to get the queries from my url pattern like localhost:3000/post?loc=100 by using useRouter() from "next/router" and fetching some data using that id from my server. It worked when I used it in a Stateless Functional Component.
But the page showing "Invalid hook call" then. I tried calling getInitalProps() of a Stateless Functional Component, but it didn't work there either and showed the same error.
Is there any rule to use this method?
I was developing a front-end using React Library and Next.js Framework.
constructor(props) {
this.state = {
loc: useRouter().query.loc,
loaded: false
};
}
Hooks can be used only inside functional components, not inside classes. I would recommend to use withRouter HOC as per next.js documentation:
use the useRouter hook, or withRouter for class components.
Or see From Classes to Hooks if you want to switch to hooks.
In general, it's possible to create a wrapper functional component to pass custom hooks into class components via props (but not useful in this case):
const MyClassWithRouter = (props) => {
const router = useRouter()
return <MyClass {...props} router={router} />
}
class MyClass...
constructor(props) {
this.state = {
loc: props.router.query.loc,
loaded: false
};
}
withRouter example
https://stackoverflow.com/a/57029032/895245 mentioned it, but a newbie like me needed a bit more details. A more detailed/direct description would be:
Function component:
import { useRouter } from "next/router";
export default function Post() {
const router = useRouter();
return (
<div>{ router.query.id }</div>
)
}
Class component equivalent:
import { withRouter } from 'next/router'
import React from "react";
export default withRouter(class extends React.Component {
render() {
return (
<div>{ this.props.router.query.id }</div>
)
}
})
I tested this out more concretely as follows. First I took vercel/next-learn-starter/basics-final/pages/posts/[id].js and I hacked it to use the router:
diff --git a/basics-final/pages/posts/[id].js b/basics-final/pages/posts/[id].js
index 28faaad..52954d3 100644
--- a/basics-final/pages/posts/[id].js
+++ b/basics-final/pages/posts/[id].js
## -4,13 +4,17 ## import Head from 'next/head'
import Date from '../../components/date'
import utilStyles from '../../styles/utils.module.css'
+import { useRouter } from "next/router"
+
export default function Post({ postData }) {
+ const router = useRouter();
return (
<Layout>
<Head>
<title>{postData.title}</title>
</Head>
<article>
+ <div>router.query.id = {router.query.id}</div>
<h1 className={utilStyles.headingXl}>{postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={postData.date} />
Then, I ran it as:
git clone https://github.com/vercel/next-learn-starter
cd next-learn-starter
git checkout 5c2f8513a3dac5ba5b6c7621d8ea0dda881235ea
cd next-learn-starter
npm install
npm run dev
Now when I visit: http://localhost:3000/posts/ssg-ssr I see:
router.query.id = ssg-ssr
Then I converted it to the class equivalent:
import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'
import Head from 'next/head'
import Date from '../../components/date'
import utilStyles from '../../styles/utils.module.css'
import { withRouter } from 'next/router'
import React from "react"
export default withRouter(class extends React.Component {
render() {
return (
<Layout>
<Head>
<title>{this.props.postData.title}</title>
</Head>
<article>
<div>router.query.id = {this.props.router.query.id}</div>
<h1 className={utilStyles.headingXl}>{this.props.postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={this.props.postData.date} />
</div>
<div dangerouslySetInnerHTML={{ __html: this.props.postData.contentHtml }} />
</article>
</Layout>
)
}
})
export async function getStaticPaths() {
const paths = getAllPostIds()
return {
paths,
fallback: false
}
}
export async function getStaticProps({ params }) {
const postData = await getPostData(params.id)
return {
props: {
postData
}
}
}
and everything seemed to be unchanged.
Tested on Next.js 10.2.2.
I'm setting up ContextApi for the first time in a production app, hoping to replace our current handling of our app configs with it. I've followed the official docs and consulted with similar issues other people are experiencing with the API, and gotten it to a point where I am able to correctly the config when I do Config.Consumer and a callback in render functions. However, I cannot get this.context to return anything other than an empty object.
Ideally, I would use this.context in lifecycle methods and to avoid callback hell, so help would be appreciated. I've double checked my React version and that I'm setting the contextType. Below is a representation of the code
config.js
import { createContext } from "react";
export default createContext();
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { Router, browserHistory } from "react-router";
import { syncHistoryWithStore } from "react-router-redux";
import Config from "../somePath/config";
// more imports
function init() {
const config = getConfig();
const routes = getRoutes(config);
const history = syncHistoryWithStore(browserHistory, appStore);
ReactDOM.render(
<Provider store={appStore}>
<Config.Provider value={config}>
<Router history={history} routes={routes} />
</Config.Provider>
</Provider>,
document.getElementById("app")
);
}
init();
someNestedComponent.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Config from "../somePath/config";
#connect(
state => ({
someState: state.someState,
})
)
class someNestedComponent extends Component {
componentDidMount() {
console.log(this.context);
}
render() {
return (...someJSX);
}
}
someNestedComponent.contextType = Config;
export default someNestedComponent;
Currently running on:
React 16.8.6 (hopi to see error messages about circuitous code but
didn't get any warnings)
React-DOM 16.7.0
React-Redux 6.0.1
The problem is that someNestedComponent doesn't refer to the class where this.context is used:
someNestedComponent.contextType = Config;
It refers to functional component that wraps original class because it was decorated with #connect decorator, it is syntactic sugar for:
const someNestedComponent = connect(...)(class someNestedComponent extends Component {
...
});
someNestedComponent.contextType = Config;
Instead, it should be:
#connect(...)
class someNestedComponent extends Component {
static contextType = Config;
componentDidMount() {
console.log(this.context);
}
...
}
There are no callback hell problems with context API; this is conveniently solved with same higher-order component pattern as used in React Redux and can also benefit from decorator syntax:
const withConfig = Comp => props => (
<Config.Consumer>{config => <Comp config={config} {...props} />}</Config.Consumer>
);
#connect(...)
#withConfig
class someNestedComponent extends Component {
componentDidMount() {
console.log(this.props.config);
}
...
}
You didn't use a consumer to get the values
ref: https://reactjs.org/docs/context.html#contextconsumer
I'm fairly advanced in iOS Swift language, but very new in react native framework or javascript language. I also have tried to find the right tutorial for stack navigator for hours but I can't find it. I'm currently using this tutorial to learn the basic of react native stack navigation, and would like to split the two screens into their own files. Basically, I want to mimic Swift with its AppDelegate.swift and View Controller's files. But it generates error like this:
Invariant Violation: Element type is invalid: expected a string (for
built-in components) or a class/function (for composite components)
but got: object. You likely forgot to export your component from the
file it's defined in, or you might have mixed up default and named
imports.
That was old error. The new error now:
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
This error is located at:
in HomeScreen (at renderApplication.js:33)
...
Here's my current code. This has been edited according to the solutions.
App.js:
import React from 'react';
import { Button, AppRegistry } from 'react-native';
import { StackNavigator } from 'react-navigation';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
export default HomeScreen;
const App = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
AppRegistry.registerComponent("TestProject", () => App);
HomeScreen.js:
import React from 'react';
import { Button } from 'react-native';
import { StackNavigator } from 'react-navigation';
export default class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigate('Profile', { name: 'Jane' })
}
/>
);
}
}
ProfileScreen.js:
import React from 'react';
import { Text } from 'react-native';
import { StackNavigator } from 'react-navigation';
export default class ProfileScreen extends React.Component {
static navigationOptions = {
title: 'Jane Profile',
};
render() {
const { navigate } = this.props.navigation;
return (
<Text>Welcome to Jane profile!</Text>
);
}
}
I would greatly appreciate any point out to any error and mistake in my code, as for now I'm completely oblivious to any problems in my code, as I'm learning this by myself. Please help. The first baby steps are always the hardest part every time I learn new language.
In this case, I'm trying to create a home screen with a button that leads to a second screen (profile) with a text in it. I'm also aware that I should be able to extract the name of the profile in the ProfileScreen which the HomeScreen sent, but I hardcoded it right now to make things simple for me to understand the solution.
Try this
import React from 'react';
import { StackNavigator } from 'react-navigation';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
const App = StackNavigator({
HomeScreen: { screen: HomeScreen },
ProfileScreen: { screen: ProfileScreen },
}, {
initialRouteName: 'HomeScreen',
headerMode: 'none'
});
export default () => <App />;
The name of the route should be same not mandatory but
recommended.
Export the navigation element.
I don't even have a AppRegistry script in my project but it is still working.
If any issue comment that down below! Hope this will fix the issue
Note : This will work in react-navigation version 1.5.11
For react-navigation v2 try this code
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
const App = createStackNavigator({
HomeScreen: { screen: HomeScreen },
ProfileScreen: { screen: ProfileScreen },
}, {
initialRouteName: 'HomeScreen',
headerMode: 'none'
});
export default () => <App />;
Make the navigation script file global to access its props!
In react-native we don't register every component using AppRegistry.registerComponent, rather we register the parent component and parent component render child component. The changes that you need is to register the App component and export the HomeScreen and ProfileScreen.
Sample
App.js
...
const App = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
AppRegistry.registerComponent('projectName', () => App)
HomeScreen.js
...
class HomeScreen extends React.Component {
...
}
export default HomeScreen;
ProfileScreen.js
...
class ProfileScreen extends React.Component {
...
}
export default ProfileScreen;
Hope this will help!
This is my class:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
class MyClass extends Component {
constructor(props) {
super(props);
}
get pageTitle() {
const { intl } = this.props;
return intl.formatMessage({id: 'messages_my_class_page_title'});
}
render() {
return (
<div>
Dummy content
</div>
);
}
}
MyClass.propTypes = {
intl: intlShape.isRequired
}
export default injectIntl(MyClass);
My page title appears when I put in a random text and don't use react-intl injectIntl function.
I have 'react-intl' working fine for all other cases. Even for static properties using 'hoist-non-react-statics' library.
I am stumbled upon this one.
Edit 1:
I can fix it using
<FormattedMessage id="messages_my_class_page_title"/>
But I want to know how to use injectIntl way.