How to use React Router with Preact - javascript

I'm using Preact as my View framework (can NOT use React due to the Facebook IP problems). I needed to use React Router for the location routing because it has more flexibility than the Preact Router that the same team built.
I managed to get React Router to accept Preact in place of React, however, I can't get it to match locations. I'm not sure if it's a compatibility problem, or a configuration problem. I've tried using just one pair of routes ( App and Account ) and it still doesn't work with that simplified setup.
Q: Does anyone see if I'm doing something wrong here?
The error I get is: Location "/account/12345/" did not match any routes
main.js
import { h, render } from 'preact';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import createStore from './createStore';
import createRoutes from './createRoutes';
process.env.DEBUG && console.log('Hello, developer!');
const history = browserHistory;
const store = createStore( history );
const routes = createRoutes( store );
render((
<Provider store={ store } key="redux-provider">
<Router history={ history } createElement={ h } routes={ routes } />
</Provider>
), document.body );
createRoutes.js
import { h } from 'preact';
import { IndexRoute, Route } from 'react-router';
// App Component
import App from './components/app';
// Sub Components
import Account from './components/account';
import Conversation from './components/conversation';
import Dashboard from './components/dashboard';
// Error Components
import BadAccount from './components/bad-account';
import NotFound from './components/not-found';
// Routes
export default ()=> (
<Route path="/" component={App}>
{/* Get URL parameter */}
<Route path="account/:accountID" component={Account}>
{/* Index Route */}
<IndexRoute component={Dashboard} />
{/* Sub Routes ( Alphabetical Please ) */}
<Route path="chat" component={Conversation} />
{/* Catch-All Route */}
<Route path="*" component={NotFound} />
</Route>
{/* Handle Invalid URIs */}
<Route path="*" component={BadAccount} />
</Route>
);
createStore.js
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import messages from './resources/messages/reducer';
import conversation from './resources/conversation/reducer';
import layout from './resources/layout/reducer';
import profile from './resources/profile/reducer';
import contract from './resources/contract/reducer';
/*const { devToolsExtension } = window;*/
export default history => {
// Sync dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware( history );
// Create single reducer from all modules
const rootReducer = combineReducers({
messages,
conversation,
layout,
profile,
contract
});
// List redux middleware to inject
const middleware = [
thunk,
reduxRouterMiddleware
];
// Compose the createStore function
const createComposedStore = compose(
applyMiddleware( ...middleware )/*, // Figure this out...
( process.env.DEBUG && devToolsExtension ) ? devToolsExtension() : f => f*/
)( createStore );
// Create the store
const store = createComposedStore( rootReducer );
// Hook up Redux Routing middleware
// reduxRouterMiddleware.listenForReplays(store);
// Return store
return store;
};

(OP already solved his issue, but this ranks high in Google and is not very helpful for newcomers, so I thought I'd provide some background info)
Preact and preact-compat
preact is a minimal version of React that weighs just 3Kb. It implements a subset of React's API, with some small differences here and there. It also comes with a helper library, preact-compat, which provides compatibility with React by filling in missing parts and patching up API differences.
React-Router
react-router is a router library designed to work with React. But you can make it work with Preact as well, using preact-compat.
Setting up preact-compat
npm i --save preact-compat
Make sure you set up aliases for react and react-dom in your webpack / browserify configuration, or write some code to set up these aliases manually.
example webpack config
{
// ...
resolve: {
alias: {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
}
// ...
}
Then you can use React Components as-is. They won't know they are being rendered by Preact i.s.o. React. Have a look at this preact-compat-example.
Issues with compatibility
Keep in mind that when you are using Preact Compat, you are taking a risk. Jason is a very smart guy, but his library is only a fraction of the size of the one provided by Facebook so there's bound to be some differences. Components that use lesser known features of React might not work correctly. If you encounter such issues, report them to the preact-compat issue tracker (with a minimal reproduction in the form of a GitHub repo) to help him improve it.
There have been a few of such issues in the past that prevented React-Router from working correctly with Preact, but they have been fixed since and you should now be able to use the two together nicely.
Fiddle of Preact + React-Router
Have a look at this JS Fiddle for a working example.

Updated answer is there is a preact-router package now: https://www.npmjs.com/package/preact-router
import Router from 'preact-router';
import { h } from 'preact';
const Main = () => (
<Router>
<Home path="/" />
<About path="/about" />
<Search path="/search/:query" />
</Router>
);
render(<Main />, document.body);

Found the issue, was a pair of problems with Preact's compatibility with React:
Contexts not handled correctly:
https://github.com/developit/preact/issues/156
props.children not handled correctly:
https://github.com/developit/preact-compat/issues/47#issuecomment-220128365

Here is extend solution for preact-router with hash support.
Works with reload and direct access.
https://www.webpackbin.com/bins/-KvgdLnM5ZoFXJ7d3hWi
import {Router, Link} from 'preact-router';
import {h, render} from 'preact';
import {createHashHistory} from 'history';
[cut]...[/cut]
const App = () => (
<div>
<Header />
<Router history={createHashHistory()}>
<Page1 default path="/" />
<Page2 path="/page2" />
</Router>
</div>
);
render(<App />, document.body);

Related

Error: <Route> elements are for router configuration only and should not be rendered in react-router v4

Tried to upgrade react-router from 2 to 4 and broke it and now cant render my app.
getting various errors (the most recent is: <Route> elements are for router configuration only and should not be rendered)
I have also had the error where my ./ route renders fine but every other route blows up when I refresh and says Cannot GET /randomRoute
I am creating a react app and my main index.js file (where I include ReactDOM.render) also includes the routes and looks like so:
import React from 'react';
import ReactDOM from 'react-dom';
import { Route } from 'react-router';
import { BrowserRouter as Router, Match, HashRouter } from 'react-router-dom'
import Header from './components/header';
import './index.scss';
class App extends React.Component {
render() {
return (
<Router history={HashRouter}>
<div>
<Route path={"/"} component={Header} />
</div>
</Router>
);
}
}
ReactDOM.render(<App />,
document.getElementById('content'));
why would I be getting that current error and can anyone give me a simple start to the basics I need to include just to get routing working? it worked in version 2 but I wanted to upgrade and now cant get it working again
The problem is that you are specifying history object as a Router type.
From the Documentation
A <Router> that uses the hash portion of the URL (i.e.
window.location.hash) to keep your UI in sync with the URL.
This is similar to what you would do when you specify history as
hashHistory in Router v2.
Also, history object has been seprated into a seprate package from v4 onwards.
You can either make use of BrowserRouter or HashRouter to render your Routes.
Change your Route Configuration to below if you want to use BrowserRouter which is <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.This is similar to what you would do when you specify history as browserHistory in Router v2.
Also you need to import Route from 'react-router-dom'.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Match, Route} from 'react-router-dom'
import Header from './components/header';
import './index.scss';
class App extends React.Component {
render() {
return (
<Router >
<div>
<Route path={"/"} component={Header} />
</div>
</Router>
);
}
}
Well, in react router v4 the API is different. You have to define it in your index.js file like this,
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<Switch>
<Route path="/path/one" component={ComponentOne} />
<Route path="/path/two" component={ComponentTwo} />
<Route path="/" component={IndexComponent} />
</Switch>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
Make sure the order is important here. Put the most generic one at last. Hope this helps. Happy coding !

Pagination issue using React Router v4.1

I'm migrating a site in ASP.NET MVC to REACT. And for pagination i have created a component in React.
Issue i'm facing is with Routing for the pagination URLs. React Router is not able to detect that the URL is different when i click on a pagination URL
Let me explain:
app.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux';
import allReducers from '../reducers/index';
import {Provider} from 'react-redux';
import ReduxPromiseMiddleware from 'redux-promise';
import { BrowserRouter, Route } from 'react-router-dom';
import Main from './main';
import Layout from './layout';
const app = document.getElementById('root');
const store = createStore(allReducers, applyMiddleware(ReduxPromiseMiddleware));
ReactDOM.render(<Provider store={store}>
<BrowserRouter>
<Layout>
<Main/>
</Layout>
</BrowserRouter>
</Provider>
,app);
Main component render:
render(){
return(
<main>
<Switch>
<Route exact path='/' component={HomePage}/>
<Route path='/posts' component={PostsRouter} />
<Route path='/studies' component={StudiesPage} />
</Switch>
</main>
);
}
PostsRouter component:
const PostsRouter = () => (
<Switch>
<Route exact path='/posts' component={PostsPage} />
<Route path='/posts/:page' component={PostsPage} />
</Switch>
);
For both /posts and /posts/2 i need the component to be PostsPage.
Lets say i'm at /home. Now i click a posts link and URL changes to /posts. Now if i click /posts/2 link, nothing happens. React Router doesn't detect that the URL is different.
And a weird thing i noted is that if i change the component:
<Route path='/posts/:page' component={PostsPage} />
to
<Route path='/posts/:page' component={StudiesPage} />
then React Router routes me to StudiesPage component if i click on /posts/2 link when i'm on /posts URL.
May be i'm missing something obvious. But i haven't been able to figure out a way after lots of attempts.
I suspect Sergey's comment was right, that's what my problem ended up being. I was fetching data within componentDidMount() but didn't realise that in order to actually update it with new data when the next page link was clicked, I needed to do the same thing inside componentWillReceiveProps(). You can see my full source here but the biggest key part was this:
componentWillReceiveProps(nextProps) {
this.setState({
loaded: false
});
this.fetchMediaItems(nextProps.match.params.page);
}
componentDidMount() {
this.fetchMediaItems(this.props.match.params.page);
}
componentWillReceiveProps() receives the new properties, including page number, when you click on the link to page 2, so you need to do whatever inside there to update with the new state.

Router history with react-router 4.0.0

In react-router 4.0.0 the history provisoning seems to have changed, with the following index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, hashHistory } from 'react-router';
import App from './components/App';
import './index.css';
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={App} />
</Router>, document.getElementById('root')
);
I get:
Warning: Failed prop type: The prop `history` is marked as required in `Router`, but its value is `undefined`.
and an error afterwards. I browsed the code but can't find any example or API how that has changed.
React Router v4 changed things little bit. They made separate top level router elements. Replace <Router history={hashHistory}> with <HashRouter> in your code.
Hope this will help full.
import {HashRouter,Route} from 'react-router-dom';
<HashRouter>
<Route path = "/getapp" component = {MainApp} />
</HashRouter>
hashHistory is no longer an exported object of react-router. If you want to use a hash history, you can just render a <HashRouter>.
import { HashRouter } from 'react-router-dom'
ReactDOM.render((
<HashRouter>
<App />
</HashRouter>
), holder)
This is a topic where RR documentation should add more clarity... To answer your question, you can either use a BrowserRouter, or you can use a Router and pass it a history instance.
Go with the first approach when all your route changes are through Link components.
However, when you need to change routes from your store methods, you would need to use the latter approach. You can write a module which creates and exports a history object and then use the same object in your Router component and in the store methods. It's important to use the same object otherwise the Router won't be able to correctly sync the URL changes with your store.
You may try the code below:
import { Router } from 'react-router'
import createBrowserHistory from 'history/createBrowserHistory'
const history = createBrowserHistory()
<Router history={history}>
<App/>
</Router>
Check the doc here.

React, Electron, And Router

I am currently to adapt a simple web app i made (using React and Redux) into a native desktop app.
I am using GitHub's Electron and Webpack to do this. Everything is fine if I use hashHistory from React-Router.. but I want to use browserHistory so my app will still look nice (URL-wise) when running as a webapp. If i do this though, I get the following error:
No route matches path ".../index.html"
Which makes sense to me. I am loading index.html as the main file for Electron:
mainWindow.loadURL('file://' + __dirname + '/index.html');
I'm just wondering if it is at all possible to use browserHistory with React-Router and Electron. If anyone knows it would be greatly appreciated!
Not exactly. But there is even better solution .
You should Separate from your react app the bootstrap file.
Bootstrap file which load your app and pass to it some additional params from outside.
In your situation you will create two bootstrap files, one for electron - with memoryHistory (I think it is better for electron) and second one for browsers with browser history.
Example of bootstrap file for electron index-electron.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { createMemoryHistory } from "react-router";
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
const history = createMemoryHistory("begin-path");
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
Example of bootstrap file for browsers index-browser.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { browserHistory } from 'react-router';
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
In my examples difference is small (only history) but you can make more changes. As you see I also provide additional begin params from outside (initialState, config);
And how your App should :
import React from 'react';
import { Router, Route } from 'react-router';
class App extends React.Component {
static propsTypes = {
history: React.PropTypes.object,
config: React.PropTypes.object,
initialState: React.PropTypes.object
};
render() {
return (
<Router history={this.props.history}>
<Route ...>
...
</Route>
</Router>
);
}
}
export default App;
Above code is only conception. It is from my project, where I removed obsolete things. Therefore without some modification it may not work
Now for electron you use index-electron.jsx, and for browsers index-browser.jsx. Most of your code is reusable cross both envs. And it is very flexible.

What's the right pattern to follow for an Altjs + ReactRouter project?

I started my first react app following this SurviveJS brilliant guide to develop a kanban app. This involves altjs as flux technology, really simple to use.
The author suggests to use an AltContainer to link store(s) to the component(s), an easy solution that will transform your App component (the root one) into something like this:
/* ... */
export default class App extends React.Component {
render = () => {
return (
<div>
<AltContainer
stores={[MyStoreOne]}
inject={{
myProps: () => MyStoreOne.getState().myProps
}}
>
<ComponentOne/>
</AltContainer>
<AltContainer
stores={[MyStoreTwo]}
inject={{
myProps: () => MyStoreTwo.getState().myProps
}}
>
<ComponentTwo/>
</AltContainer>
</div>
);
}
}
The question now is: when I use a react router what's the best way to let AltContainers work with their linked components, considering a nested routes approach?
/* ... */
import { Router, Route, browserHistory } from 'react-router';
import App from './components/App';
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/one/:paramOne" component={ComponentOne}/>
<Route path="/two/:paramTwo" component={ComponentTwo}/>
</Route>
</Router>
), document.getElementById('knots'));
Where I have to put my AltContainers, now that the components are called directly from the router (and not from the App)?
Moving the flux logic directly inside the subcomponents (so avoiding AltContainers) is antipattern?

Categories

Resources