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.