I'm looking for a way to log clicks on <Link/>
components (from [email protected]) as actions. Currently a click throws this error: "Uncaught Invariant Violation: s rendered outside of a router context cannot navigate."
Is there a way to do this already? I couldn't find anything
The links addon is interesting, but a completely unrelated usecase.
@jzaefferer First of all, link addon is a totally different one.
This is because, we are using the RR outside of the it's router context.
We need to find a proper way to deal with the <Link/>
. One way is to put a custom webpack alias and set a mock to <Link>
.
Or you can new Link component wrapping the original with react-stubber.
I tried this, here's how to do this:
First create a file called rr.js
inside your Storybook config directory (.storybook
) with the following content:
import React from 'react';
import { action } from '@kadira/storybook';
// Export the original react-router
module.exports = require('react-router-original');
// Set the custom link component.
module.exports.Link = class Link extends React.Component {
handleClick(e) {
e.preventDefault();
const { to } = this.props;
action('Link')(to);
}
render() {
const { children, style } = this.props;
return (
<a
style={style}
href='#'
onClick={(e) => this.handleClick(e)}
>
{children}
</a>
);
}
};
Then create another file called webpack.config.js
and add following content:
// load the default config generator.
var genDefaultConfig = require('@kadira/storybook/dist/server/config/defaults/webpack.config.js');
module.exports = function(config, env) {
// You can use your own config here as well, instead our default config.
var config = genDefaultConfig(config, env);
// this is used by our custome `rr.js` module
config.resolve.alias['react-router-original'] = require.resolve('react-router');
// this `rr.js` will replace the Link with a our own mock component.
config.resolve.alias['react-router'] = require.resolve('./rr.js');
return config;
};
Thank you! I will give that a try.
@jzaefferer how was it?
Can you use this method to use the linkTo()
function for <Link />
components?
I tried this, but couldn't get it working. Apparently the aliased rr.js
is never loaded, so the custom Link
implementation isn't loaded either. I couldn't figure out how to debug the resolve.alias besides outputting the resolved path for rr.js
(which was correct).
Any ideas what I might be missing? I'm hardly an expert on webpack :/
Would it not be better to use a custom React Router "provider" and wrap all stories in it. Apologies for shooting from the hip, but if we had a way to add a decorator for all stories (do we?), and plugins were able to add them, then we would make a plugin to do the above..
@tmeasday That strikes me as a much better idea, I'm going to play around with it and see what I can come up with.
Here's the "correct" way to replace React-Router's history functions with actions. This is in my config.js
:
import { addDecorator, action } from '@kadira/storybook'
import { Router } from 'react-router'
import createMemoryHistory from 'history/createMemoryHistory'
const history = createMemoryHistory()
history.push = action('history.push')
history.replace = action('history.replace')
history.go = action('history.go')
history.goBack = action('history.goBack')
history.goForward = action('history.goForward')
addDecorator(story => <Router history={history}>{story()}</Router>)
Since I don't have history
as a dependency, I imported createMemoryHistory
from react-router
:
import { Router, createMemoryHistory } from 'react-router'
Otherwise I tried it as you suggested. I get these "warnings" (really errors):
Warning: [react-router] Location "/" did not match any routes
Warning: [react-router] You cannot change <Router routes>; it will be ignored
Using [email protected].
Trying to resolve that, I came up with this:
addDecorator(story => <Router history={history}>
<Route path='/' component={story} />
</Router>)
That actually works, logging the actions, but I still get the "You cannot change
Starting from this discussion, I've created a decorator that logs react-router v4 links and replace them with a linkTo if configured.
The decorator is actually an HOC that accepts an object (containing the links to replace) and wraps the actual decorator to create a component that performs the real replacement. The decorator code can be found here, and it is used this way:
.addDecorator(StoryRouter({'/about': linkTo('App', 'about')}))
Paths non matching the ones defined as argument of the StoryRouter will use the action addon as in the @nathancahill solution.
@gvaldambrini that seems like a great solution. Any plans to publish it as a package to npm? It seems like one that many of us would find useful. I certainly would.
@travi thanks. I'll create a storybook addon for that and I'll let you know when it will be ready & released (hopefully soon).
@travi the package for the decorator is now available on npm. Of course any feedback is welcome.
Most helpful comment
Here's the "correct" way to replace React-Router's history functions with actions. This is in my
config.js
: