faceyspacey / redux-first-router-link

<Link /> + <NavLink /> that mirror react-router's + a few additional props
MIT License
55 stars 33 forks source link

React-hot-loader #97

Closed GuillaumeCisco closed 6 years ago

GuillaumeCisco commented 6 years ago

Hey there, I'm experiencing a very very weird issue with the Link component and react-hot loader. I clearly don't know if this is an issue with redux-first-router-link or with react-hot-loader, but it looks like more with redux-first-router-link as I experience this issue ONLY with the Link component.

Issue : Everything before a <Link> declaration hot reloads correctly, everything after does not. Like if when meeting a <Link/> component, the hot reload stop refreshing my components, but it displays correctly in the console devtools part the components that should have been updated. Moreover, if I inspect my mapped file in the devtools, I can see correctly the file is updated, but nothing happen on the main window. It really really surprised me and I think I still don't know where the problem come from.

So as a solution,I wrote my own version of the Link component to debug it as I'm not fluent with flow.

So my version of Link looks like:

import React from 'react';
import {connect} from 'react-redux';

import handlePress from './handlePress';

const preventDefault = e => e && e.preventDefault && e.preventDefault();

const Link = ({
                  to,
                  href,
                  redirect,
                  replace,
                  tagName = 'a',
                  children,
                  onPress,
                  onClick,
                  down = false,
                  shouldDispatch = true,
                  target,
                  dispatch,
                  location,
                  ...props
              }) => {

    const newTo = href || to; // href is deprecated and will be removed in next major version

    const {routesMap} = location;
    const url = newTo;
    const handler = handlePress.bind(
        null,
        url,
        routesMap,
        onPress || onClick,
        shouldDispatch,
        target,
        dispatch,
        newTo,
        replace || redirect,
    );
    const Root = tagName;

    const localProps = {};

    if (tagName === 'a' && url) {
        localProps.href = url;
    }

    if (down && handler) {
        localProps.onMouseDown = handler;
        localProps.onTouchStart = handler;
    }

    if (target) {
        localProps.target = target;
    }

    return (
        <Root
            onClick={(!down && handler) || preventDefault}
            {...localProps}
            {...props}
        >
            {children}
        </Root>
    );
};

const mapStateToProps = ({location}, ownProps) => ({location, ...ownProps});

// $FlowIgnore
export default connect(mapStateToProps)(Link);

handlePress.js:

import { pathToAction, redirect, getOptions } from 'redux-first-router';

export default (
    url,
    routesMap,
    onClick,
    shouldDispatch,
    target,
    dispatch,
    to,
    dispatchRedirect,
    e
) => {
    let shouldGo = true;

    if (onClick) {
        shouldGo = onClick(e); // onClick can return false to prevent dispatch
        shouldGo = typeof shouldGo === 'undefined' ? true : shouldGo;
    }

    const prevented = e.defaultPrevented;

    if (!target && e && e.preventDefault && !isModified(e)) {
        e.preventDefault();
    }

    if (
        shouldGo &&
        shouldDispatch &&
        !target &&
        !prevented &&
        e.button === 0 &&
        !isModified(e)
    ) {
        const { querySerializer: serializer } = getOptions();
        let action = isAction(to) ? to : pathToAction(url, routesMap, serializer);
        action = dispatchRedirect ? redirect(action) : action;
        dispatch(action);
    }
}

const isAction = (to) => typeof to === 'object' && !Array.isArray(to);

const isModified = (e) => !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);

As you can see, I don't use the store and selectLocationState for getting the location, but I connect my new Link to redux for getting it. Surprisingly, react-hot-loader works very well with this version. Which suprises me even more, as I clearly do not understand why the flow version breaks... the flow.

Does someone ever experienced this issue? I'm using :

"redux": "4.0.0",
"redux-actions": "2.4.0",
"redux-first-router": "1.9.15",
"redux-first-router-link": "1.4.2",
"react-hot-loader": "4.3.3",

I test this issue with the following snippet:

import Link from 'redux-first-router-link';

// import Link from './Link';
//
const Component = () =>
    <h1>component</h1>;

const Routes = ({location}) => (
    <div>
        <Component/>
        <Component/>
        <Link to={{type: 'HOME'}}>test</Link>
        <Component/>
        <Component/>
    </div>);

When I change the text inside the h1, only the first two components are updated, not the two last. If I change the text in the Link component, it is not updated too.

Don't hesitate to ask for more information. Thanks

GuillaumeCisco commented 6 years ago

Ok just found something even more interesting, I forgot something in my precedent snippet. The correct one is:

import React from 'react';
import {connect} from 'react-redux';
import Link from 'redux-first-router-link';

// import Link from './Link';
//
const Component = () =>
    <h1>component</h1>;

const Routes = ({location}) => (
    <div>
        <Component/>
        <Component/>
        <Link to={{type: 'HOME'}}>test</Link>
        <Component/>
        <Component/>
    </div>);

const mapStateToProps = ({location}, ownProps) => ({location, ...ownProps});

export default connect(mapStateToProps)(Routes);

This does not work as explained just above. But If I simply replace:

export default connect(mapStateToProps)(Routes);

by

export default Routes;

It does work well. Looks like I have a conflict with the connect part of the redux-first-router-link, which use in its source code:

import { connect } from 'react-redux'
import type { Store } from 'redux'
import type { Connector } from 'react-redux'

...

const connector: Connector<OwnProps, Props> = connect()

// $FlowIgnore
export default connector(Link)

Any ideas on this issue?

GuillaumeCisco commented 6 years ago

Ok, after some tweaking in the redux-first-router-link code, I discovered that simply replacing in the dist/Link.js file:

var connector = (0, _reactRedux.connect)();

// $FlowIgnore
exports.default = connector(Link);

by

exports.default = Link

make the Link component works correctly with react-hot-loader.

Why do we need to connect to redux the Link component in the first place?

GuillaumeCisco commented 6 years ago

Ok I just found out the problem. I'm using the autodll-webpack-plugin and configured it like:

    entry: {
        reactVendors: [
            'react',
            'react-dom',
            'react-emotion',
            'emotion',
            'react-redux',
            'react-tap-event-plugin',
        ],
        reduxVendors: [
            'redux',
            'redux-actions',
            'redux-first-router',
            'redux-reducers-injector',
            'redux-saga',
            'redux-sagas-injector',
        ],
        commonVendors: [
            'fastclick',
            'history',
            'react-helmet',
            'recompose',
        ],
    },

removing the react-redux entry, implicitly from the dll generated js files makes it work. I still don't understand why, but there is clearly something weird with the way react-redux is generated and used.

If someone has an idea and undesrtand why.

PS: I found out the solution, when simply importing the generated redux-first-router-link in my project and see it working... Lost 2 days on this.

GuillaumeCisco commented 6 years ago

Hey there, after a lot of research, I finally know what was the real guilty plugin: new webpack.optimize.ModuleConcatenationPlugin() Simply removing this plugin from my configuration made everything works correctly, with react-redux or redux-first-router-link not loaded in the dll list.

Hope it will help a lot of fellows ;)