Only in the context of a <Router> component error on import outside project root - react-router

I have two React projects.
Project "app" contains the main project.
Project "sb" contains a Storybook project.
The dependencies have been made the same, both projects are created with CRA.
app/
components/
SampleComp.tsx
sb/
components/
SampleComp.tsx
stories/
SampleComp.stories.tsx
Both SampleComp.tsx are exactly the same in code.
When I import SampleComp.tsx from the sb/components folder, it's working.
When I import SampleComp.tsx from the app/components folder, it's NOT working.
The latter fails with the following error in Storybook:
"useHref() may be used only in the context of a component."
In the stories, I wrap the component in the MemoryRouter.
SampleComp.tsx (both)
import React from "react";
import { Link } from "react-router-dom";
export function SampleComp() {
return (
<div>
Home page
<Link to="/test">Test</Link>
</div>
);
}
SampleComp.stories.tsx
import React from "react";
import { ComponentStory, ComponentMeta } from "#storybook/react";
// import { SampleComp } from "../../app/components/SampleComp"; // not working
import { SampleComp } from "../components/SampleComp"; // works!
import { MemoryRouter } from "react-router";
export default {
title: "SampleComp",
component: SampleComp,
parameters: {
layout: "fullscreen",
},
} as ComponentMeta<typeof SampleComp>;
const Template: ComponentStory<typeof SampleComp> = (args) => (
<MemoryRouter>
<SampleComp />
</MemoryRouter>
);
export const Default = Template.bind({});
Why doesn't it work the same?

Related

React-router-dom params undefined in nested route after using link

I need a switch component to have access to route params. The switch is rendered in one of the routes but its also rendered outside of it. Is there a way to get the same params in the component rendered outside of the route? Thanks for the help in advance!
It's usually a good pattern to not directly pass params through the route and keep those simple with the view component. You can use useContext, and then have each component(route) plug into that state using the useContext hook in the component.
for example...
app.js
import { useState } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes } from "./auth/routes.js";
import { GlobalContext } from './globals/GlobalContext.js';
const App = () => {
// variables
const [someState, setSomeState] = useState('hello world');
// render
return (
<div>
<GlobalContext.Provider value={{someState, setSomeState}}>
<Router children={Routes} basename={process.env.REACT_APP_PUBLIC_URL} />
</GlobalContext.Provider>
</div>
);
}
GlobalContext.js
import { createContext } from 'react';
export const GlobalContext = createContext("");
routes.js
import { Route, Switch } from "react-router-dom";
// views
import ViewOne from '../views/ViewOne.js';
import ViewTwo from '../views/ViewTwo.js';
// globals
import { frontendLinks } from '../globals/index.js';
export const Routes = (
<Switch>
<Route exact path={frontendLinks.viewOne} component={ViewOne}></Route>
<Route exact path={frontendLinks.viewTwo} component={ViewTwo}></Route>
</Switch>
);
now the views...
import { useContext } from 'react';
// globals
import { GlobalContext } from '../globals/GlobalContext.js';
const ViewOne = () => {
const { someState } = useContext(GlobalContext);
return (
<div>
<h1>{someState}<h1>
</div>
)
}
export default ViewOne;
and
import { useContext } from 'react';
// globals
import { GlobalContext } from '../globals/GlobalContext.js';
const ViewTwo = () => {
const { someState } = useContext(GlobalContext);
return (
<div>
<h1>{someState}<h1>
</div>
)
}
export default ViewTwo;
If you don't want to manage shared state in your app.js file, I suggest you check out this video for managing useContext state in different files > https://www.youtube.com/watch?v=52W__dKdNnU

Warning: [react-router] Location "/add" did not match any routes

I am stuck with react-router routing. I am getting the error:
Warning: [react-router] Location "/add" did not match any routes`
// conf.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link} from "react-router-dom";
import add from './add';
import { createBrowserHistory } from 'history';
import confv1 from './confv1';
var button =React.createElement(Link, {
to: "/add"
}, React.createElement("button", {
type: "button"
}, "Add a project"));
export default class Root extends Component {
render() {
return (
React.createElement(Router, {
history: createBrowserHistory()
}, React.createElement(Route, {
path: "/conf",
component: confv1
}, React.createElement(Route, {
component: conf
}), React.createElement(Route, {
path: "/add",
component: add
})
)));
// );this is the conf page
and this is the add page when I refresh it I got the error "Warning: [react-router] Location "/add" did not match any routes`"
}
}
`
I resolved the error with a routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './App';
import conf from './conf';
import add from './add';
export default (
<Route path="/" component={App}>
<IndexRoute component={conf} />
<Route path="/conf" component={conf} >
<IndexRoute component={add} />
<Route path="/add" component={add} />
</Route>
</Route>
);
//index.js
import 'babel-polyfill';
import React from 'react';
import {render} from 'react-dom';
import {Router, BrowserRouter } from 'react-router';
import routes from './routes';
import {browserHistory} from 'react-router';
const history = require("history").createBrowserHistory();
render(
<Router history={browserHistory} routes={routes} />, document.getElementById('root')
)
but when I refresh the add page nothing shows in the contententer image description here
I noticed from your code snippet that you are using two different npm packages, and I am wondering if that is part of the issue.
under Config.js you called:
import { BrowserRouter as Router, Route, Link} from "react-router-dom";
Then under index.js you called:
import {Router, BrowserRouter } from 'react-router';
if that doesn't fix the issue could you create a codesandbox so I can take a look?
https://codesandbox.io/
Here is more information about the two npm packages you were trying to use:
https://www.npmjs.com/package/react-router
https://www.npmjs.com/package/react-router-dom
good luck!

Using Jest to test a Link from react-router v4

I'm using jest to test a component with a <Link> from react-router v4.
I get a warning that <Link /> requires the context from a react-router <Router /> component.
How can I mock or provide a router context in my test? (Basically how do I resolve this warning?)
Link.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import { Link } from 'react-router-dom';
test('Link matches snapshot', () => {
const component = renderer.create(
<Link to="#" />
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
The warning when the test is run:
Warning: Failed context type: The context `router` is marked
as required in `Link`, but its value is `undefined`.
You can wrap your component in the test with the StaticRouter to get the router context into your component:
import React from 'react';
import renderer from 'react-test-renderer';
import { Link } from 'react-router-dom';
import { StaticRouter } from 'react-router'
test('Link matches snapshot', () => {
const component = renderer.create(
<StaticRouter location="someLocation" context={context}>
<Link to="#" />
</StaticRouter>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Have a look at the react router docs about testing
I had the same issue and using StaticRouter would still require the context which needed more configuration to have it available in my test, so I ended up using the MemoryRouter which worked very well and without any issues.
import React from 'react';
import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
// SampleComponent imports Link internally
import SampleComponent from '../SampleComponent';
describe('SampleComponent', () => {
test('should render', () => {
const component = renderer
.create(
<MemoryRouter>
<SampleComponent />
</MemoryRouter>
)
.toJSON();
expect(component).toMatchSnapshot();
});
});
The answer of #Mahdi worked for me! In 2023 if you want to test a component that includes <Link> or <NavLink>, we just need to wrap it with the <MemoryRouter> in the test file:
// App.test.js
import { render, screen } from "#testing-library/react";
import MyComponent from "./components/MyComponent";
import { MemoryRouter } from "react-router-dom"; // <-- Import MemoryRouter
test("My test description", () => {
render(
<MemoryRouter> // <-- Wrap!
<MyComponent />
</MemoryRouter>
);
});
my test like this:
import * as React from 'react'
import DataBaseAccout from '../database-account/database-account.component'
import { mount } from 'enzyme'
import { expect } from 'chai'
import { createStore } from 'redux'
import reducers from '../../../reducer/reducer'
import { MemoryRouter } from 'react-router'
let store = createStore(reducers)
describe('mount database-account', () => {
let wrapper
beforeEach(() => {
wrapper = mount(
< MemoryRouter >
<DataBaseAccout store={store} />
</MemoryRouter >
)
})
afterEach(() => {
wrapper.unmount()
wrapper = null
})
})
but I don't konw why MemoryRouter can solve this。
Above solutions have a common default defact:
Can't access your component's instance! Because the MemoryRouter or StaticRouter component wrapped your component.
So the best to solve this problem is mock a router context, code as follows:
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
describe('YourComponent', () => {
test('test component with react router', () => {
// mock react-router context to avoid violation error
const context = {
childContextTypes: {
router: () => void 0,
},
context: {
router: {
history: createMemoryHistory(),
route: {
location: {
hash: '',
pathname: '',
search: '',
state: '',
},
match: { params: {}, isExact: false, path: '', url: '' },
}
}
}
};
// mount component with router context and get component's instance
const wrapper = mount(<YourComponent/>, context);
// access your component as you wish
console.log(wrapper.props(), wrapper.state())
});
beforeAll(() => {
configure({ adapter: new Adapter() });
});
});

react-router-redux: Module build failed: SyntaxError: Unexpected token ...reducers

//app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import createHistory from 'history/createBrowserHistory';
import { Route } from 'react-router';
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';
import App from './components/app.js';
import reducers from './reducers';
const history = createHistory();
const middleware = routerMiddleware(history);
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore({
...reducers,
router: routerReducer
},
applyMiddleware(middleware)
);
const About = () => {
return (<div>
Will this work?
</div>);
}
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Route exact path="/" component={App}/>
<Route path="/about" component={About}/>
</div>
</ConnectedRouter>
</Provider>
, document.querySelector('#app'));
Here is my reducers/index.js code.
//reducers/index.js
import { combineReducers } from 'redux';
import { mobileLinks } from './reducer_header';
import UserDetailsReducer from './reducer_user_details';
const rootReducer = combineReducers({
mobileLinks,
userDetails: UserDetailsReducer
});
export default rootReducer;
What do I do to fix this? I'm unable to find any examples for the new version of react-router-redux. I've tried moving the routerReducer to reducers/index.js but that didn't work either. Can someone please help?
Most likely you do not have Babel set to transpile the Object Spread Operator, you can read up on it here.
You can simply install the Babel "Object rest spread transform" preset like so:
npm install --save-dev babel-plugin-transform-object-rest-spread
And add it to your list of plugins:
{
"plugins": [
// Other plugins...
"transform-object-rest-spread"
]
}

RouteHandler: React.createElement: type should not be null, undefined, boolean, or number

The use of RouteHanlder gives two errors:
VM2805 bundle.js:9597Warning: 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).
Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
The structure of my application.
src
-- components
-- -- App.jsx
-- -- LengthModule.jsx
-- index.jsx
-- routes.js
My routes.js file
var React = require('react');
var Router = require('react-router');
var DefaultRoute = Router.DefaultRoute;
var Route = Router.Route;
var routes = (
<Route name="app" path="/" handler={require('./components/app.jsx')}>
<DefaultRoute handler={require('./components/LengthModule.jsx')} />
</Route>
)
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<div><App /></div>, document.getElementById('app'));
App.jsx
import React from 'react';
import { Router, RouteHandler } from 'react-router';
export class App extends React.Component {
render () {
return <div>
<RouteHandler />
</div>;
}
}
LengthModule.jsx
import React from 'react';
import Router from 'react-router';
export class LengthModule extends React.Component {
render () {
return <div>"Hello World"</div>;
}
}
Am I using RouteHandler correctly? What am I missing? Are there any alternatives?
App.jsx
import React from 'react';
import { Router, RouteHandler } from 'react-router';
export default class App extends React.Component {
render () {
return <div>
<RouteHandler />
</div>;
}
}
LengthModule.jsx
import React from 'react';
import Router from 'react-router';
export defaulf class LengthModule extends React.Component {
render () {
return <div>"Hello World"</div>;
}
}
Why es6 react component works only with "export default"?
Newer tutorials warn: Be Careful About Deprecated Syntax. This article specifically mentions "<RouteHandler /> is Deprecated."