Реагировать на соединение маршрутизатора, не вызывая обновление компонента во вложенных маршрутах

Это сводит меня с ума. Когда я пытаюсь использовать ссылку React Router в рамках вложенного маршрута, ссылка обновляется в браузере, но представление не меняется. Тем не менее, если я обновлю страницу по ссылке, это так. Каким-то образом компонент не обновляется, когда должен (или, по крайней мере, это цель).

Вот как выглядят мои ссылки (prev / next-item действительно vars):

<Link to={'/portfolio/previous-item'}>
    <button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
    <button className="button button-xs">Next</button>
</Link>

Хакерское решение - вручную вызвать forceUpate (), например:

<Link onClick={this.forceUpdate} to={'/portfolio/next-item'}>
    <button className="button button-xs">Next</button>
</Link>

Это работает, но вызывает полное обновление страницы, что я не хочу, и ошибка:

ReactComponent.js:85 Uncaught TypeError: Cannot read property 'enqueueForceUpdate' of undefined

Я искал ответ сверху и снизу, и самое близкое, что я мог найти, это:https://github.com/reactjs/react-router/issues/880, Но он старый, и я не использую чистый рендер миксин.

Вот мои соответствующие маршруты:

<Route component={App}>
    <Route path='/' component={Home}>
        <Route path="/index:hashRoute" component={Home} />
    </Route>
    <Route path="/portfolio" component={PortfolioDetail} >
        <Route path="/portfolio/:slug" component={PortfolioItemDetail} />
    </Route>
    <Route path="*" component={NoMatch} />
</Route>

По какой-либо причине вызов Link не приводит к перемонтированию компонента, что должно произойти для получения контента для нового представления. Он вызывает componentDidUpdate, и я уверен, что смогу проверить изменение URL-адреса и затем запустить там свое обновление вызова / просмотра ajax, но, похоже, в этом нет необходимости.

РЕДАКТИРОВАТЬ (более соответствующий код):

PortfolioDetail.js

import React, {Component} from 'react';
import { browserHistory } from 'react-router'
import {connect} from 'react-redux';
import Loader from '../components/common/loader';
import PortfolioItemDetail from '../components/portfolio-detail/portfolioItemDetail';
import * as portfolioActions  from '../actions/portfolio';

export default class PortfolioDetail extends Component {

    static readyOnActions(dispatch, params) {
        // this action fires when rendering on the server then again with each componentDidMount. 
        // but not firing with Link...
        return Promise.all([
            dispatch(portfolioActions.fetchPortfolioDetailIfNeeded(params.slug))
        ]);
    }

    componentDidMount() {
        // react-router Link is not causing this event to fire
        const {dispatch, params} = this.props;
        PortfolioDetail.readyOnActions(dispatch, params);
    }

    componentWillUnmount() {
        // react-router Link is not causing this event to fire
        this.props.dispatch(portfolioActions.resetPortfolioDetail());
    }

    renderPortfolioItemDetail(browserHistory) {
        const {DetailReadyState, item} = this.props.portfolio;
        if (DetailReadyState === 'WORK_DETAIL_FETCHING') {
            return <Loader />;
        } else if (DetailReadyState === 'WORK_DETAIL_FETCHED') {
            return <PortfolioItemDetail />; // used to have this as this.props.children when the route was nested
        } else if (DetailReadyState === 'WORK_DETAIL_FETCH_FAILED') {
            browserHistory.push('/not-found');
        }
    }

    render() {
        return (
            <div id="interior-page">
                {this.renderPortfolioItemDetail(browserHistory)}
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        portfolio: state.portfolio
    };
}
function mapDispatchToProps(dispatch) {
    return {
        dispatch: dispatch
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(PortfolioDetail);

PortfolioItemDetail.js

import React, {Component} from 'react';
import {connect} from 'react-redux';
import Gallery from './gallery';

export default class PortfolioItemDetail extends React.Component {

    makeGallery(gallery) {
        if (gallery) {
            return gallery
                .split('|')
                .map((image, i) => {
                    return <li key={i}><img src={'/images/portfolio/' + image} alt="" /></li>
            })
        }
    }

    render() {
        const { item } = this.props.portfolio;

        return (
            <div className="portfolio-detail container-fluid">
                <Gallery
                    makeGallery={this.makeGallery.bind(this)}
                    item={item}
                />
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        portfolio: state.portfolio
    };
}

export default connect(mapStateToProps)(PortfolioItemDetail);

gallery.js

import React, { Component } from 'react';
import { Link } from 'react-router';

const Gallery = (props) => {

    const {gallery, prev, next} = props.item;
    const prevButton = prev ? <Link to={'/portfolio/' + prev}><button className="button button-xs">Previous</button></Link> : '';
    const nextButton = next ? <Link to={'/portfolio/' + next}><button className="button button-xs">Next</button></Link> : '';

    return (
        <div>
            <ul className="gallery">
                {props.makeGallery(gallery)}
            </ul>
            <div className="next-prev-btns">
                {prevButton}
                {nextButton}
            </div>
        </div>
    );
};

export default Gallery;

Новые маршруты, основанные на предложении Anoop:

<Route component={App}>
    <Route path='/' component={Home}>
        <Route path="/index:hashRoute" component={Home} />
    </Route>
    <Route path="/portfolio/:slug" component={PortfolioDetail} />
    <Route path="*" component={NoMatch} />
</Route>

Ответы на вопрос(4)

но я смог достичь своих целей с помощью ComponentWillRecieveProps:

componentWillReceiveProps(nextProps){
    if (nextProps.params.slug !== this.props.params.slug) {
        const {dispatch, params} = nextProps;
        PortfolioDetail.readyOnActions(dispatch, params, true);
    }
}

Другими словами, по какой-либо причине, когда я использую React Router Link для ссылки на страницу с ОДНЫМ КОМПОНЕНТОМ РОДИТЕЛЯ, он не запускает componentWillUnMount / componentWillMount. Поэтому мне приходится вручную запускать свои действия. Это работает, как я ожидаю, когда я связываюсь с Маршрутами с другим родительским компонентом.

Может быть, это так, как задумано, но это не кажется правильным и не интуитивно понятно. Я заметил, что в Stackoverflow есть много похожих вопросов о том, что Link изменяет URL-адрес, но не обновляет страницу, поэтому я не единственный. Если у кого-то есть понимание этого, я все еще хотел бы услышать это!

Мое решение было следующим:

componentWillMount() {
    const { id } = this.props.match.params;
    this.props.fetchCategory(id); // Fetch data and set state
}

componentWillReceiveProps(nextProps) {
    const { id } = nextProps.match.params;
    const { category } = nextProps;

    if(!category) {
        this.props.fetchCategory(id); // Fetch data and set state
    }
}

Я использую избыточность для управления состоянием, но концепция та же самая, я думаю.

Установите состояние как обычно для метода WillMount, и когда вызывается WillReceiveProps, вы можете проверить, было ли состояние обновлено, если нет, вы можете вызвать метод, который устанавливает ваше состояние, это должно привести к повторной визуализации вашего компонента.

но он немного раздражает. Я написал «концепцию» BaseController, которая устанавливает действие состояния при изменении маршрута ДАЖЕ, хотя компонент маршрута одинаков. Итак, представьте, что ваши маршруты выглядят так:

<Route path="test" name="test" component={TestController} />
<Route path="test/edit(/:id)" name="test" component={TestController} />
<Route path="test/anything" name="test" component={TestController} />

Тогда BaseController проверит обновление маршрута:

import React from "react";

/**
 * conceptual experiment
 * to adapt a controller/action sort of approach
 */
export default class BaseController extends React.Component {


    /**
     * setState function as a call back to be set from
     * every inheriting instance
     *
     * @param setStateCallback
     */
    init(setStateCallback) {
        this.setStateCall = setStateCallback
        this.setStateCall({action: this.getActionFromPath(this.props.location.pathname)})
    }

    componentWillReceiveProps(nextProps) {

        if (nextProps.location.pathname != this.props.location.pathname) {
            this.setStateCall({action: this.getActionFromPath(nextProps.location.pathname)})
        }
    }

    getActionFromPath(path) {

        let split = path.split('/')
        if(split.length == 3 && split[2].length > 0) {
            return split[2]
        } else {
            return 'index'
        }

    }

    render() {
        return null
    }

}

Затем вы можете наследовать от этого:

импорт React из "реакции"; импортировать BaseController из './BaseController'

export default class TestController extends BaseController {


    componentWillMount() {
        /**
         * convention is to call init to
         * pass the setState function
         */
        this.init(this.setState)
    }

    componentDidUpdate(){
        /**
         * state change due to route change
         */
        console.log(this.state)
    }


    getContent(){

        switch(this.state.action) {

            case 'index':
                return <span> Index action </span>
            case 'anything':
                return <span>Anything action route</span>
            case 'edit':
                return <span>Edit action route</span>
            default:
                return <span>404 I guess</span>

        }

    }

    render() {

        return (<div>
                    <h1>Test page</h1>
                    <p>
                        {this.getContent()}
                    </p>
            </div>)
        }

}

я попытался воссоздать то же самое локально и работает нормально для меня. Ниже приведен пример кода,

import { Route, Link } from 'react-router';
import React from 'react';
import App from '../components/App';

const Home = ({ children }) => (
  <div>
    Hello There Team!!!
    {children}
  </div>
);

const PortfolioDetail = () => (
  <div>
    <Link to={'/portfolio/previous-item'}>
      <button className="button button-xs">Previous</button>
    </Link>
    <Link to={'/portfolio/next-item'}>
      <button className="button button-xs">Next</button>
    </Link>
  </div>
);

const PortfolioItemDetail = () => (
  <div>PortfolioItemDetail</div>
);

const NoMatch = () => (
  <div>404</div>
);

module.exports = (
  <Route path="/" component={Home}>
    <Route path='/' component={Home}>
        <Route path="/index:hashRoute" component={Home} />
    </Route>
    <Route path="/portfolio" component={PortfolioDetail} />
    <Route path="/portfolio/:slug" component={PortfolioItemDetail} />
    <Route path="*" component={NoMatch} />
  </Route>
);
 dcsan18 авг. 2016 г., 07:51
ты понял это? Я попал в ту же проблему. Это потому, что компоненты имеют свой собственный «маршрут»? что может вызвать их запуск и обновление?
 unleash.it18 авг. 2016 г., 10:04
Не совсем, за исключением обходного пути ниже. Любое изменение состояния должно вызывать обновление, но по какой-то причине Link не всегда делает это. В какой-то момент я могу переключиться на React Router Redux и посмотреть, исправит ли это, так как я использую Redux.
 unleash.it08 авг. 2016 г., 00:06
Полностью согласен, поэтому я просто добавил соответствующие части кода компонента. Спасибо за рабочий пример, но я не уверен, что он соответствует моим потребностям, потому что я не хотел бы пересматривать верхний / нижний колонтитул при переходе к следующим / предыдущим элементам портфолио. Тем не менее, в духе того, чтобы заставить его работать, я попробовал это без вложений, аналогично тому, что вы делали (но не / маршрутный портфель и нацеливание на родительский компонент вместо детализации элемента). Это все еще работает как прежде, но мне все еще не повезло получить Ссылку, чтобы перемонтировать компонент.
 unleash.it08 авг. 2016 г., 00:11
Также стоит упомянуть, что это универсальное приложение. Это нормально работает, когда страницы связаны с не-портфельным маршрутом (например, Home), но я не могу связать страницы портфолио без обновления. Живой пример здесь:jasongallagher.org

Ваш ответ на вопрос