简体   繁体   中英

React Router : Data fetched using axios doesn't save when navigating to new route

I am building a React app using react-router-dom and fetching data from my Django Rest Framework back-end and once the data has been fetched, and I navigate to another link, the data saved to the window and saved internally inside state is deleted. How do I persist this data in the client?

Here is the code:

Router

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import AuthHeader from './authheader';
import { BlogData, BlogDetail } from '../components/blog';



export default class AuthRouter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isLoggedIn: false,
            authtoken: '',
        }
    }

    handler = (token) => {
        this.setState({
            isLoggedIn: true,
            authtoken: token,
        })
    }
    /**
     * ! Render the Route props inside of the
     * ! BlogData component
     */

    render () {
        return (
            <Router>
                <div>
                    {/* <Header /> */}
                    {/* <Route path='/home' exact component={WrappedHorizontalLoginForm} /> */}
                    {/* <Route path='/about' exact component={AuthHeader} /> */}
                    <AuthHeader
                        action={this.handler}
                        token={this.state.isLoggedIn && this.state.authtoken}
                    />
                    <Route
                        path='/blog'
                        exact
                        render={(props) => <BlogData {...props}
                            token={this.state.isLoggedIn && this.state.authtoken}
                        />} />
                    <Route
                        path='/blog/:id'
                        exact
                        render={(props) => <BlogDetail {...props}
                            token={this.state.isLoggedIn && this.state.authtoken}
                        />} />
                </div>
            </Router>
        );
    }
}

The Authheader component passes props down eventually to LoginFormOrUserComponent here:

import React, { Component } from 'react';
import axios from 'axios';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { localhost, authtoken } from '../backendUrlConstants';
import Col from 'react-bootstrap/Col';

export default class LoginFormOrUserComponent extends Component {

    constructor(props) {

        super(props);

        this.state = {
            isLoggedIn: false,
            authtoken: null,
            username: '',
            password: '',
        }

        // if (this.props.token) {
        //     this.setState({ authtoken: this.props.token })
        //     this.setState({ isLoggedIn: true })
        // }
    }

    // componentDidMount (props) {
    //     if (this.props.token) {
    //         this.setState({ authtoken: this.props.token })
    //         this.setState({ isLoggedIn: true })
    //     }
    // }

    passAuthTokenToParentClass = () => {

        const { authtoken } = this.state;
        if (authtoken) {
            this.props.action(authtoken)
        }
    }

    handleUsernameChange = (e) => {
        this.setState({ username: e.target.value })
    }

    handlePasswordChange = (e) => {
        this.setState({ password: e.target.value })
    }

    handleSubmit = (e) => {
        console.log(e)
        const username = this.state.username;
        const password = this.state.password;
        axios.post(`${localhost}${authtoken}`, { username, password })
            .then(res => {
                this.setState({ authtoken: res.data.token })
                this.setState({ isLoggedIn: true })
                this.passAuthTokenToParentClass()
            })
        // TODO: Catch the error if the username and password
        // TODO: are incorrect.
    }

    renderLoginFormOrUserComponent = () => {
        if (this.state.isLoggedIn === false) {
            return <Form className="mr-auto" >
                <Form.Row>
                    <Col xs={4}>
                        <Form.Control placeholder="Username" onChange={this.handleUsernameChange} />
                    </Col>
                    <Col xs={4}>
                        <Form.Control placeholder="Password" onChange={this.handlePasswordChange} />
                    </Col>
                </Form.Row>
                <Button variant="dark" onClick={this.handleSubmit} >Submit</Button>
            </Form>
        }
        return <Button variant="dark" >Account</Button>
    }

    render () {

        return (
            this.renderLoginFormOrUserComponent()
        );
    }
}

And here are the BlogData and BlogDetail component:

import React, { Component } from 'react';
import axios from 'axios';
import { localhost } from '../backendUrlConstants';
import { Link } from 'react-router-dom';
import { Card } from 'react-bootstrap';


export class BlogData extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: [],
        }
    }

    getUrl () {
        return this.props.location.pathname
    }

    componentDidMount () {
        const uri = this.getUrl()
        axios.get(`${localhost}${uri}`)
            .then(res => {
                const data = res.data;
                this.setState({ data })
            })
    }

    render () {
        return (
            <ul>
                {this.state.data.map(
                    data => <li key={data.id} >
                        <Link to={`/blog/${data.id}`} >{data.url}</Link>
                    </li>
                )}
            </ul>
        )
    }
}

export class BlogDetail extends BlogData {

    constructor(props) {

        super(props)
        this.state = {
            blogObject: {}
        }
    }

    componentDidMount () {
        const uri = this.getUrl()
        axios.get(`${localhost}${uri}`)
            .then(res => {
                const data = res.data;
                this.setState({ blogObject: data })
            })
    }

    render () {

        const { blogObject } = this.state;
        return (
            <Card style={{ width: "18rem" }} >
                <Card.Img variant="top" src={blogObject.blog_picture_context} />
                <Card.Body>
                    <Card.Title>{blogObject.blog_name}</Card.Title>
                    <Card.Text>{blogObject.blog_post}</Card.Text>
                </Card.Body>
            </Card>
        )
    }
}

Also, for context here is the url of my localhost Django server:

export const localhost = 'http://127.0.0.1:8000';
export const login = '/api-auth/login/';
export const authtoken = '/authtoken/';

Once I get the data from my backend, I lose it once I navigate to another page. There is clearly something I am missing here.

Using hooks you can easily pass data when navigating to a new URL / link that normally would refresh your state. No need to use local storage or anything like that.

As an example, I have an admin dashboard that has nested routes for users where I want the url for the user to be the user id and I want to pass the user id to the new url and be able to use it within the new component.

My nested routes file looks like:

import React from 'react';
import { Index, SignIn, SignUp } from '../onepirate/';
import { Admin, Event, CheckoutPath, Profile, Tickets, CheckoutComplete } from '../screens/';
import AdminDashboard from '../components/admin/AdminDashboard';
import UserAccount from '../screens/admin/user-components/UserAccount'
import Users from '../screens/admin/Users';

const adminRoutes = {
    "/dashboard": () => <AdminDashboard />,
    "/events": () => <Event />,
    "/users": () => <Users />,
    "/users/:userId": ({ userId }) => <UserAccount userId={userId} />,
}

export default adminRoutes;

As you can see I am passing the user Id from the /users component and sending it through the route as a property. At the same time the user id will be dynamically laced in the URL.

Then you import your router to the component where the routes should be handled:

import { useRoutes, useRedirect, navigate } from 'hookrouter';
export default function Admin() {
    // this function allows your to redirect from one URL to another    
    useRedirect('/admin', '/admin/dashboard');

    return (
            <main className={classes.content}>
                <Container>
                    {router}
                </Container>
            </main>
        </div>
    );
}

Then as an example to navigate from my users component to users/:userID

import { useRoutes, useRedirect, navigate } from 'hookrouter';

const User = props => {
  const classes = useStyles();

  return (
    <Container>
      <Paper className={classes.root}>
        <TextField
          id="outlined-search"
          label="Search field"
          type="search"
          className={classes.textField}
          margin="normal"
          variant="outlined"
          onChange={props.search}
        />
        <Table className={classes.table}>
          <TableHead className={classes.tableHead}>
            <TableRow className={classes.tableHead}>
              <TableCell className={classes.tableHead} align="">Id</TableCell>
              <TableCell className={classes.tableHead} align="">Email</TableCell>
              <TableCell className={classes.tableHead} align="">First Name</TableCell>
              <TableCell className={classes.tableHead} align="">Last Name</TableCell>
              <TableCell className={classes.tableHead} align="">Info</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {props.data.map((user, index) => (
              <TableRow key={index}>
                <TableCell align="right">{user.id}</TableCell>
                <TableCell align="right">{user.email}</TableCell>
                <TableCell align="right">{user.firstname}</TableCell>
                <TableCell align="right">{user.lastname}</TableCell>

     // be sure to pass the user id in the navigate() function

                <TableCell><AccountBoxIcon onClick={() => (navigate("/admin/users/" + user.id))}></AccountBoxIcon></TableCell>
              </TableRow>
            ))}
          </TableBody>

        </Table>
      </Paper>
    </Container>
  )
}
export default User;

Then in UserAccount compontent was linked in our routes file and passed the user ID to, we can see the data passed by console logging the props in a useEffect (component will mount equivelent) hook.

// UserAccount Component:

useEffect(() => {
// userId we sent as prop through route
        console.log(props.userId);
// ... do cool things
}, []);

OK, so here is the code for persisting the data in local storage.

import React, { Component } from 'react';
import axios from 'axios';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { localhost, authtoken } from '../backendUrlConstants';
import Col from 'react-bootstrap/Col';

export default class LoginFormOrUserComponent extends Component {

    constructor(props) {

        super(props);

        this.state = {
            isLoggedIn: false,
            authtoken: null,
            username: '',
            password: '',
        }

        // if (this.props.token) {
        //     this.setState({ authtoken: this.props.token })
        //     this.setState({ isLoggedIn: true })
        // }
    }

    componentDidMount (props) {
        const storage = window.localStorage
        const status = storage.getItem('isLoggedIn')
        if (status) {
            this.setState({ isLoggedIn: true })
            this.setState({ authtoken: storage.getItem('token') })
        }
    }

    hanldeClick = (e) => {
        this.setState({ isLoggedIn: false })
        this.setState({ authtoken: null })
        const storage = window.localStorage
        storage.clear()
    }

    passAuthTokenToParentClass = () => {

        const { authtoken } = this.state;
        if (authtoken) {
            this.props.action(authtoken)
        }
    }

    handleUsernameChange = (e) => {
        this.setState({ username: e.target.value })
    }

    handlePasswordChange = (e) => {
        this.setState({ password: e.target.value })
    }

    handleSubmit = (e) => {
        const storage = window.localStorage
        const username = this.state.username;
        const password = this.state.password;
        axios.post(`${localhost}${authtoken}`, { username, password })
            .then(res => {
                // ! persist the data in localstorage =>
                storage.setItem('token', res.data.token)
                storage.setItem('isLoggedIn', true)
                this.setState({ isLoggedIn: true })
                this.setState({ authtoken: res.data.token })
                console.log(storage)
            })
        // TODO: Catch the error if the username and password
        // TODO: are incorrect.
    }

    renderLoginFormOrUserComponent = () => {
        if (this.state.isLoggedIn === false) {
            return <Form className="mr-auto" >
                <Form.Row>
                    <Col xs={4}>
                        <Form.Control placeholder="Username" onChange={this.handleUsernameChange} />
                    </Col>
                    <Col xs={4}>
                        <Form.Control placeholder="Password" onChange={this.handlePasswordChange} />
                    </Col>
                </Form.Row>
                <Button variant="dark" onClick={this.handleSubmit} >Submit</Button>
            </Form>
        }
        return <Button variant="dark" onClick={this.hanldeClick} >Account</Button>
    }

    render () {

        return (
            this.renderLoginFormOrUserComponent()
        );
    }
}
'''

I also added a logout button to clear the data kept in `localStorage` when the user clicks the Account button. 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM