Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log React Router <Link/> clicks as actions? #485

Closed
jzaefferer opened this issue Sep 23, 2016 · 14 comments
Closed

Log React Router <Link/> clicks as actions? #485

jzaefferer opened this issue Sep 23, 2016 · 14 comments

Comments

@jzaefferer
Copy link

I'm looking for a way to log clicks on <Link/> components (from react-router@2.x.x) 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.

@arunoda
Copy link
Member

arunoda commented Sep 25, 2016

@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.

@arunoda arunoda added the bug label Sep 25, 2016
@arunoda
Copy link
Member

arunoda commented Sep 25, 2016

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;
};

@arunoda arunoda added discussion and removed bug labels Sep 25, 2016
@arunoda arunoda changed the title Log <Link/> clicks as actions? Log React Router <Link/> clicks as actions? Sep 25, 2016
@jzaefferer
Copy link
Author

Thank you! I will give that a try.

@arunoda
Copy link
Member

arunoda commented Sep 30, 2016

@jzaefferer how was it?

@iansoper
Copy link

iansoper commented Oct 1, 2016

Can you use this method to use the linkTo() function for <Link /> components?

@jzaefferer
Copy link
Author

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 :/

@tmeasday
Copy link
Member

tmeasday commented Apr 6, 2017

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..

@nathancahill
Copy link

@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.

@nathancahill
Copy link

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>)

@jzaefferer
Copy link
Author

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 react-router@2.8.1.

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 ; it will be ignored" warning/error and I have no idea where it comes from.

@gvaldambrini
Copy link
Contributor

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.

@travi
Copy link

travi commented Apr 23, 2017

@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.

@gvaldambrini
Copy link
Contributor

@travi thanks. I'll create a storybook addon for that and I'll let you know when it will be ready & released (hopefully soon).

@gvaldambrini
Copy link
Contributor

@travi the package for the decorator is now available on npm. Of course any feedback is welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants