React-Admin v4: how to set basename with keyword after /#/ - react-router

I'm trying the simplest React Admin v4 setup to have a path structure such as /#/{project}/resource..., for instance /#/project_1/user/, /#/project_2/user/ etc...
(I want everything under /#/ so I can serve my app via static files).
Looking at this guide I have:
import * as React from "react";
import { Admin, Resource, CustomRoutes, ListGuesser } from "react-admin";
import { Route } from "react-router-dom";
import { BrowserRouter } from "react-router-dom";
let project = "project_1";
const Dashboard = () => {
return <>Dashboard</>;
};
const Test = () => {
return <>Test</>;
};
const App = () => {
let basename = "/#/" + project;
return (
<BrowserRouter>
<Admin dashboard={Dashboard} basename={basename}>
<Resource name="user" list={ListGuesser} />
<CustomRoutes>
<Route path="/settings" element={<Test />} />
</CustomRoutes>
</Admin>
</BrowserRouter>
);
};
export default App;
What's working properly
The menu link paths are as desired (eg /#/project_1/user/)
What's not working
No matter which menu link I click, it's the dashboard being displayed (even if I try the custom route at /#/project_1/settings/ or /#/settings)
What do I need to restore resources/custom routes while preserving the path structure with the leading /#/{project}/?
PS: I am running RA v4:
"dependencies": {
"#testing-library/jest-dom": "^5.14.1",
"#testing-library/react": "^12.0.0",
"#testing-library/user-event": "^13.2.1",
"prop-types": "^15.8.1",
"ra-data-json-server": "^3.19.11",
"react": "^18.0.0",
"react-admin": "4.0.0-rc.1",
"react-dom": "^18.0.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.0"
},

Related

Uncaught Error: useRoutes() may be used only in the context of a <Router> component [duplicate]

I have installed react-router-domV6-beta. By following the example from a website I am able to use the new option useRoutes I have setup page routes and returning them in the App.js file.
After saving I am getting the following error:
Error: useRoutes() may be used only in the context of a component.
I am wondering If I am missing something here? I have created the pages inside the src/pages folder.
My code:
import { BrowserRouter, Link, Outlet, useRoutes } from 'react-router-dom';
// Pages
import Home from './pages/Home';
import About from './pages/About';
import Services from './pages/Services';
import Gallery from './pages/Gallery';
import Prices from './pages/Prices';
import Contact from './pages/Contact';
const App = () => {
const routes = useRoutes([
{ path: '/', element: <Home /> },
{ path: 'o-nama', element: <About /> },
{ path: 'usluge', element: <Services /> },
{ path: 'galerija', element: <Gallery /> },
{ path: 'cjenovnik', element: <Prices /> },
{ path: 'kontakt', element: <Contact /> }
]);
return routes;
};
export default App;
You should have a <BrowserRouter> (or any of the provided routers) higher up in the tree. The reason for this is that the <BrowserRouter> provides a history context which is needed at the time the routes are created using useRoutes(). Note that higher up means that it can't be in the <App> itself, but at least in the component that renders it.
Here's what your entry point could look like:
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
I think the problem is that you still need to wrap routes (Routes / useRoutes) inside a Router element.
So an example would look something like this:
import React from "react";
import {
BrowserRouter as Router,
Routes,
Route,
useRoutes,
} from "react-router-dom";
const Component1 = () => {
return <h1>Component 1</h1>;
};
const Component2 = () => {
return <h1>Component 2</h1>;
};
const App = () => {
let routes = useRoutes([
{ path: "/", element: <Component1 /> },
{ path: "component2", element: <Component2 /> },
// ...
]);
return routes;
};
const AppWrapper = () => {
return (
<Router>
<App />
</Router>
);
};
export default AppWrapper;
Refactor according to your needs.
its means in Your index js Or App JS wrap with BrowserRouter like this
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter> // Like This here I am using
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root"),
);
Mention below code in index.js
import { BrowserRouter as Router } from "react-router-dom";
Just want to report on a similar issue -- as of writing (v6.2.1), it seems you actually encounter this error as well if you are importing from react-router instead of react-router-dom. A costly typo on my part.
I.e., make sure you are importing Routes and Route from react-router-dom and NOT react-router
// This is deceptively valid as the components exist, but is not the intended usage
import { Routes, Route } from 'react-router';
// This works and is the intended usage
import { Routes, Route } from 'react-router-dom';
Code: index.js
import {BrowserRouter as Router} from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById("root")
);
app.js
function App() {
return (
<>
<Routes>
<Route path ="/" element={<Main />} />
<Route path ="gigs" element={<Gigs />} />
</Routes>
</>
);
}
Try to add your routes in index.js not in App.js. Your App.js is called from index.js. In the index.js your external page is called like this
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Navbar />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
);
> Following codes works since react-router-dom syntax changed because of React 18.
import logo from './logo.svg';
import './App.css';
import Header from './components/Header';
import Login from './components/Login';
import {
BrowserRouter as Router,
Routes,
Route,
useRoutes,
} from 'react-router-dom';
import Signup from './components/Signup';
function AppRoutes() {
const routes = useRoutes(
[
{path:'/',element:<Login/>},
{path:'/signup',element:<Signup/>}
]
)
return routes;
}
function App(){
return (
<Router>
<Header/>
<AppRoutes />
</Router>
)
}
export default App;
Try
const Routes = () => useRoutes([])
and then wrap it like this in App.js
<BrowserRouter>
<Routes />
</BrowserRouter>
It worked for me
I got this error because I had two different versions of react-router-dom being bundled.
If you're using npm/yarn workspaces, check that there is only one installed version of react-router-dom in the top-level node_modules folder

React router only working with Link to, not with URL or on refresh

Not sure what is happening here. I have set up my routing and when I go to my first page localhost:8080/ the first route renders as expected. However if I enter into the url in localhost:8080/store the expected route fails and I receive a 404 cannot find (doesnt even fallback to my not found component).
However if I set up a Link to and click the link it will render my store route as expected.
Shouldn't /store render out my StorePicker component regardless if its entered into the URL or selected via a Link to element?
App.js
import React, { Component } from 'react';
import ReactDOM, { render } from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
//Components
import StorePicker from './components/StorePicker.js';
import Main from './components/Main';
import NotFound from './components/NotFound';
const Routes = () => {
return (
<Router>
<div>
<Link to="/store">Store</Link>
<Switch>
<Route path="/" exact component={StorePicker} />
<Route path="/store" component={Main} />
<Route component={NotFound} />
</Switch>
</div>
</Router>
)
}
render(<Routes />, document.querySelector('#container'));
Assuming you're using Webpack. If so, adding a few things to your webpack config should solve the issue. Specifically, output.publicPath = '/' and devServer.historyApiFallback = true.
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js',
publicPath: '/'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ]}
]
},
devServer: {
historyApiFallback: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
]
};

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"
]
}

React-Router 1.0 - 100% Client Side Routing - Page Refresh causes 404 error

I am creating a website for a client that will use strictly client side react-routing script.
Here is a sample of the router ....
import React from 'react';
import { Route } from 'react-router';
import { generateRoute } from '../utils/localized-routes';
export default (
<Route component={ require('../components/APP') }>
{ generateRoute({
paths: ['/', 'audience'],
component: require('../components/Audience')
}) }
{ generateRoute({
paths: ['speaker'],
component: require('../components/Speaker')
}) }
{ generateRoute({
paths: ['board'],
component: require('../components/Board')
}) },
{ generateRoute({
paths: ['questions'],
component: require('../components/parts/AskQuestion')
}) }
<Route path="*" component={ require('../pages/NotFound') } />
</Route>
);
With this being the code for generateRoute:
export function generateRoute({ paths, component }) {
return paths.map(function(path) {
const props = { key: path, path, component };
// Static `onEnter` is defined on
// component, we should pass it to route props
if (component.onEnter) props.onEnter = component.onEnter;
return <Route {...props} />;
});
}
Problem:
While I understand the Links will bypass server navigation and utilize transition to (client side), on page refresh, I get a "Page Cannot Be found".
If I manually put a hash tag before the browser's url input (myexample.com/#speaker), the page appears, but of course I cannot expect the user to do that.
If I must use hash tags to allow client side routing, where do I put them? I put them in the and/or the router, neither work.
Alternatively, can I achieve total client side routing w/o the ugly hash tags? If, so, how do I do it?
I'd much prefer a solution based on #3, but if all else fails I'll take a solution based on #2.
Any ideas?
Thanks in advance.
I could only find a solution using step #2 above and am stuck with hashtags.
import React from 'react';
import ReactDOM from 'react-dom';
import Router from 'react-router';
import createHistory from 'history/lib/createHashHistory'; <-- using this
// import createBrowserHistory from 'history/lib/createBrowserHistory';
const routerProps = {
routes: require('./router/routes'),
history: createHistory({ <--- added this to remove ugly querystring
queryKey: false
}),
createElement: (component, props) => {
return React.createElement(component, { ...props });
}
};
ReactDOM.render(
React.createElement(Router, { ...routerProps }),
document.getElementById('root')
);
Would still like to know how I can remove the hashtags completely with client-side routing.