【问题标题】:React router render causing unmount反应路由器渲染导致卸载
【发布时间】:2019-08-13 14:56:25
【问题描述】:

I'm trying to implement authenticated routes (as per React router docs) alongisde dynamic theming from my top level component (essentially the theme changes when a menu item is selected but no redirection occurs) - this means a small amount of state我的顶级组件需要管理。

function App() {
    const [ state, setState ] = React.useState({
        current_theme: themes['blue']
    });

    const [ logged_in, setLoggedIn ] = React.useState( !!Cookie.get('JWT') );

    function selectTheme( theme ) {
        setState({
            current_theme: themes[theme]
        });
    };

    return (
        <MuiThemeProvider theme={state.current_theme}>
            <>
                {
                    logged_in && <Header selectTheme={ selectTheme }/>
                }
                <AppContainer logged_in={ logged_in }>
                    <Switch>
                        <Route exact path='/' component={ Home }/>
                        <Route exact path='/service_users' component={ ServiceUsers } />
                    </Switch>
                </AppContainer>
            </>
        </MuiThemeProvider>
    );
}

export default App;

在添加经过身份验证的路由之前,一切正常。例如,用户将在“/service_users”处单击一个菜单项,该菜单项将导致顶级状态更改,并使用正确的颜色重新渲染组件,但子项的其他方面不会改变(因为没有改变影响他们)

这正是我想要的,但是在添加经过身份验证的路由时,使用了 Route 上的 render 属性。这里的问题是,当顶级组件中的状态发生变化时,子组件会完全卸载并重新安装。这会导致状态丢失,并且用户当前看到的所有内容都会丢失。

更新代码:

function PrivateRoute({ component: Component, ...rest }) {
    return (
        <Route
            { ...rest }
            render={ props => {
                return logged_in ? (
                    <Component { ...props } />
                ) : (
                    <Redirect
                        to={{
                            pathname: '/login',
                            state: { from: props.location }
                        }}
                    />
                )
            }}
        />
    );
}

function App() {
    const [ state, setState ] = React.useState({
        current_theme: themes['blue']
    });

    const [ logged_in, setLoggedIn ] = React.useState( !!Cookie.get('JWT') );

    function selectTheme( theme ) {
        setState({
            current_theme: themes[theme]
        });
    };

    function login() {
        setLoggedIn( true );
    }

    return (
        <MuiThemeProvider theme={state.current_theme}>
            <Router>
                {
                    logged_in && <Header drawer_open={ drawer_open } selectTheme={ selectTheme }/>
                }
                <AppContainer logged_in={ logged_in }>
                    <Switch>
                        <PrivateRoute exact path='/' component={ ServiceUsers }/>
                        <PrivateRoute exact path='/service_users' component={ ServiceUsers } />
                        <Route exact path='/login' render={ ( props ) => <Login { ...props } setLogin={ login.bind( this ) }/> } />
                    </Switch>
                </AppContainer>
            </Router>
        </MuiThemeProvider>
    );
}

export default App;

登录.js:

function Login( props ) {
    const classes = useStyles();
    const [ state, setState ] = useState({
        username: '',
        password: ''
    });
    const [ loading, setLoading ] = useState( false );
    const [ success, setSuccess ] = useState( !!Cookie.get('JWT') );

    console.log( 'success: ', success );

    function onInputChange( e ) {
        setState({ ...state, [e.target.id]: e.target.value });
    }

    async function loginRequest( e ) {
        e.preventDefault();

        const { username, password } = state;

        //TODO: validation of email/password
        if( username.length < 5 || password.length < 5 )
            return;

        setLoading( true );
        const res = await asyncAjax( 'POST', '/login', { username, password } );

        setLoading( false );

        if( res.status !== 200 )
            console.log( 'ERROR' ); //TODO: add error handling

        //Store JWT and systems
        Cookie.set( 'JWT', `Bearer ${ res.token }`, { path: '/', days: 30 } );

        //Use local storage for systems as likely to be much more data
        localStorage.setItem( 'SYSTEMS', JSON.stringify( res.login.systems ) );

        //Set login status and push user to referrer
        props.setLogin();

        props.history.push( from );
    }

    return (
        <Container component="main" maxWidth="xs">
            <CssBaseline />
            <div className={classes.paper}>
                <HeaderLogo/>
                <form className={classes.form} noValidate>
                    <TextField
                        variant="outlined"
                        margin="normal"
                        required
                        fullWidth
                        id="username"
                        label="Email Address"
                        name="username"
                        autoComplete="username"
                        autoFocus
                        onChange={ onInputChange }
                    />
                    <TextField
                        variant="outlined"
                        margin="normal"
                        required
                        fullWidth
                        name="password"
                        label="Password"
                        type="password"
                        id="password"
                        autoComplete="current-password"
                        onChange={ onInputChange }
                    />
                    <Button
                        type="submit"
                        fullWidth
                        variant="contained"
                        color="primary"
                        className={classes.submit}
                        onClick={ loginRequest }
                    >
                        {
                            loading ? (
                                <CircularProgress
                                    color='inherit'
                                    size={ 25 }
                                    style={{ color: '#FFF' }}
                                />
                            ) : (
                                'Login'
                            )
                        }
                    </Button>
                    <Grid container>
                        <Grid item xs>
                            <Link href="#" variant="body2">
                                Forgot password?
                            </Link>
                        </Grid>
                    </Grid>
                </form>
            </div>
        </Container>
    );
}

export default Login;

我的问题是如何解决这个问题?我相信问题正在发生,因为受保护的路由是从函数生成的,导致父级中的状态更改/重新渲染导致函数重新运行并返回新组件。那么我如何在函数之外渲染这些受保护的路由/没有重新渲染导致它们被卸载/重新安装?

编辑:现在可以了 - 谢谢。但是,现在登录成功时,用户不会从登录组件重定向到上一页(这部分至少在添加修复之前工作正常)。 props.history 存在并且没有错误,但 URL 不会更改,除非我将 forceRefresh 添加到我无法执行的路由器。

我已经编辑了更新的代码块并从 Login.js 添加了登录功能

【问题讨论】:

    标签: javascript reactjs react-router


    【解决方案1】:

    那么我如何将这些受保护的路由渲染到 功能/没有重新渲染导致它们被卸载/重新安装?

    我不确定这是您问题的根源,但您是否尝试过在 &lt;App /&gt; 组件之外构建一个 &lt;PrivateRoute /&gt; 组件?

    类似这样的:

    function App() {
        const [ state, setState ] = React.useState({
            current_theme: themes['blue']
        });
    
        const [ logged_in, setLoggedIn ] = React.useState( !!Cookie.get('JWT') );
    
        function selectTheme( theme ) {
            setState({
                current_theme: themes[theme]
            });
        };
    
        function login() {
            setLoggedIn( true );
        }
    
        return (
            <MuiThemeProvider theme={state.current_theme}>
                <>
                    {
                        logged_in && <Header selectTheme={ selectTheme }/>
                    }
                    <AppContainer logged_in={ logged_in }>
                        <Switch>
                            <PrivateRoute exact path='/' component={ ServiceUsers } logged_in={logged_in}, log_in={login}/>
                            <PrivateRoute exact path='/service_users' component={ ServiceUsers } logged_in={logged_in}, log_in={login} />
                            <Route exact path='/login' render={ ( props ) => <Login { ...props } setLogin={ login.bind( this ) }/> } />
                        </Switch>
                    </AppContainer>
                </>
            </MuiThemeProvider>
        );
    }
    
    function PrivateRoute({ component: Component, logged_in, log_in, ...rest }) {
            return (
                <Route
                    { ...rest }
                    render={ props => {
                        return props.logged_in ? (
                            <Component { ...props } />
                        ) : (
                            <Redirect
                                to={{
                                    pathname: '/login',
                                    setLogin: log_in.bind( this ),
                                    state: { from: props.location, test: 'test' }
                                }}
                            />
                        )
                    }
    
                    }
                />
            );
        }
    
    export default App;
    

    编辑:

    既然您已经提到了,我想解决您的路由重定向问题。我认为问题在于 history 对象无法独立访问该函数。我不确定为什么它以前可用,因为您没有共享更多代码和您分解的结构。

    无论如何,以下步骤应该允许您使用独立和导出的历史对象:

    • 安装历史包:npm i --save history

    &lt;App /&gt; 组件所在的文件中:

    • import createHistory from 'history/createBrowserHistory'
    • const history = createHistory();
    • 使用Router 代替BrowserRouterimport {Router} from 'react-router';
    • history 对象作为道具传递给Router&lt;Router history={history}

    所以所有这些都完成了,所以您可以拥有一个独立的历史对象,现在您需要将其导出。所以稍微修复一下: * export const history = createHistory();

    现在你已经导出了独立的历史对象,你可以在login.js 导入和使用:

    • import {history} from './app.jsx';当然是你自己的正确路径

    就是这样。现在您可以在 login.js 和该文件中的函数中使用它。

    【讨论】:

    • 是的,这将是问题的根源。在App 中定义PrivateRoute 将导致PrivateRoute 成为一个新的组件类型,每次重新渲染Appexample in the docs 在顶层定义 PrivateRoute,如本答案所示。
    • 现在工作正常,谢谢,但是我注意到,因为这样做,反应路由器不会在登录后将用户推回到以前的 URL。 props.history 存在,但不会发生重定向,除非我添加我不想这样做的 forceRefresh。有任何想法吗?用代码更新问题
    • @AdamRoberts 你是在使用 react Router 还是 react BrowserRouter?尝试更改为BrowserRouter 并进行测试。如果它不起作用,您还可以模拟 history 对象并通过道具将其传递给其他组件。
    • 我正在使用BrowserRouter ({ BrowserRouter as Router }) - 历史对象在那里,我可以在控制台记录props.history 时看到它(以及推送功能)但history.push 什么都不做
    • @AdamRoberts 不清楚您在哪里使用 login.js loginRequest()。它在&lt;Login /&gt; 组件中吗?如果可以,你能分享一下组件代码吗?
    猜你喜欢
    • 2019-04-25
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 2019-01-12
    相关资源
    最近更新 更多