How to transfer state through Link in react-dom 5.0? - reactjs

I develop react-app for the first time in my career.
So, I wander about a lot of things...haaa
I try to pass state between componet through Link element
But I did typing another variable syntax.
But I always see state: null on the console.
And There are many error message 'can't read state'
<Link to={{pathname:"/movie-detail", state:{year:year, title:title, summary:summary, poster:poster, genres:genres}}}>~~~</Link>
<Link to={"/movie-detail"} state={{year:year, title:title, summary:summary, poster:poster, genres:genres}}>~~</Link>
But I can't move state recipient component.
Please teach me that what is wrong with my code...
Hading over code.
import React from 'react'
import PropTypes from 'prop-types';
import './Movie.css';
import { Link } from 'react-router-dom';
function Movie({year, title, summary, poster, genres}){
return (
<div className="movie">
<Link to={"/movie-detail"} state={{year:year, title:title, summary:summary, poster:poster, genres:genres}}>
<img src={poster} alt={title} title={title}/>
<div className="movie__data">
<h3 className="movie__title" style={{backgroundColor:'red'}}>{title}</h3>
<h5 className="movie__year">{year}</h5>
<ul className="movie__genres">
{genres.map((genres, index) => {
return (
<li key={index} className="movie__genre">{genres}</li>
);
})}
</ul>
<p className="moive__summary">{summary.slice(0,180)}...</p>
</div>
</Link>
</div>
);
}
Movie.propTypes = {
id:PropTypes.number.isRequired,
year:PropTypes.number.isRequired,
title:PropTypes.string.isRequired,
summary:PropTypes.string.isRequired,
poster:PropTypes.string.isRequired,
genres:PropTypes.arrayOf(PropTypes.string).isRequired,
}
export default Movie;
Recipient Code
import React from 'react';
class Detail extends React.Component{
componentDidMount(){
const{location, history} = this.props;
//console.log(this.props);
if(location.state === undefined){
history.push('/');
}
}
render() {
const{location} = this.props;
if(location.state){
return(
<span>{location.state.title}</span>
);
}else{
return null;
};
}
}
export default Detail;

Link component has a to prop which can be string, object and function. So the correct code is:
<Link to={{ pathname: '/movie-detail', state: { year: year, title: title, summary: summary, poster: poster, genres: genres } }}>~~~</Link>
Try using withRouter HOC wrap your Detail component so that you can get the location and history objects from the props.

I would not recommend using your router to pass data.
Ideally you'll want to fetch data from within your details page based off of the given route
(e.g. /movie/star-wars fetches data for post "star-wars" from your server). By separating your router and component logic you maintain your application's modularity.

Related

React Link doesn't refresh page automatically

I am currently experiencing an issue similar to React Link doesn't refresh the page, however, the answer doesn't work well for my case.
See, I am currently using react-router to have a path called 'study/:id'. This :id variable will just be printed on the page
Here is the code for my BrowserRouter (App.js)
import React from 'react';
import './App.css';
import HomePage from './HomePage/HomePage';
import Study from './StudyPage/Study';
import {BrowserRouter as Router, Route, Switch } from 'react-router-dom';
function App() {
return (
<Router>
<Switch>
<Route path="/" exact={true} component={HomePage}/>
<Route path="/Study/:id" exact={true} component={Study} />
</Switch>
</Router>
);
}
export default App;
Inside the Study component itself, it basically just has a menubar and an indicator on which courseId are we in:
import React from 'react';
import './Study.css';
import Menubar from '../Menubar';
import Sidebar from './Sidebar';
import Chapter from './Chapter';
class Study extends React.Component{
constructor(props){
super(props);
this.state = {
courseId: this.props.match.params.id,
};
}
render(){
return(
<div id="studyWrapper">
<Menubar />
<h1>We are on course: {this.state.courseId}</h1>
</div>
)
}
}
export default Study;
In order for the user to navigate through the study pages, I use a menubar component like this (Menubar.js)
import React from 'react';
import './Menubar.css';
import { Nav } from 'reactstrap';
import { Dropdown, DropdownButton } from 'react-bootstrap';
import { Link } from 'react-router-dom';
class Menubar extends React.Component{
constructor(props){
super();
this.state = {
courses: [],
reload: false
}
}
async componentDidMount(){
const response = await fetch("/v1/courses/")
const body = await response.json();
this.setState({
courses: body
});
}
render(){
const {courses} = this.state
return (
<Nav className="navbar navbar-expand-md navbar-light menubarStyle fixed-top">
<div className="container-fluid">
<a className="navbar-brand logo" href="/">LOGO</a>
<div className="navbar-collapse">
<div className="navbar-nav">
<div className="dropdown nav-item">
<DropdownButton variant='Secondary' id="dropdown-basic-button" title="Browse Courses">
<Dropdown.Item as={Link} to={`/study/001`} >001</Dropdown.Item>
<Dropdown.Item as={Link} to={`/study/002`} >002</Dropdown.Item>
<Dropdown.Item as={Link} to={`/study/003`} >003</Dropdown.Item>
</DropdownButton>
</div>
</div>
</div>
</div>
</Nav>
)
}
}
export default Menubar
IRL, the study page basically looks like this
The problem
The problem that I am having is that, once I am in '/study/001' page already (just like the picture above). If I try to click on DropdownItem 002 from the menuBar, the URL will change to 'study/002', but the page won't change. It will not refresh.
The solution from React Link doesn't refresh the page basically says to use windows.location.reload() but that doesn't work in my case, if we do that, when I click on dropdownItem 002, the URL will change to 'study/002' for a moment, but then 'study/001' will refresh thus making the page back to 001
My question is, is there a way for us to refresh the page whenever the url is changed by link ?
Or if not, are there any other methods that I can use for this design? Maybe using links is not the right way in the first place?
Pardon the long post, I try to make it as clear as possible.
Thank you !
Inside your Study component you could use componentDidUpdate and compare the current props with the prevProps to check if the url has changed and then change the state, which should cause your component to update. More or less you would have this code:
componentDidUpdate(prevProps) {
if( this.props.match.params.id !== prevProps.match.params.id ){
this.setState({ courseId: this.props.match.params.id })
};
}

how to redirect to a different component when clicking on an external url in Reactjs?

I have to create a kind of news web app. I'm using NewsAPI.org to make API calls.I have a list of news channels in one page and clicking on any one channel should render specific content from that news channel on another page. I have some information on using react-router but I guess that is not used while dealing with the external domain urls. I'm not able to figure out how to implement this. I have used axios here.
import React, { Component } from 'react';
import {connect} from 'react-redux';
import { fetchSourceList } from '../actions';
class SourceList extends Component{
componentDidMount(){
this.props.fetchSourceList();
}
renderList(){
let {lists} = this.props;
let returnHTML = [];
for(let key in lists){
lists[key].map((item)=>{
returnHTML.push(
<li className="list-group-item" key={item.id}>
<a href={item.url}>{ item.name }</a>
</li>
);
});
}
return returnHTML;
}
render(){
return (
<div>
<ul className = "list-group">
<li className = "list-group-item text-light bg-dark"><h3>Sources</h3></li>
{this.renderList()}
</ul>
</div>
);
}
}
function mapStateToProps(state){
return { lists: state.sourceList };
}
export default connect(mapStateToProps, { fetchSourceList })(SourceList);
You have to use a router as follow:
/news/:type
Where news is a route and type is a param.
In news route you'll render always the same component to render the API response, while you'll use type params to switch channel and feed the service you use for fetching API's. This will allow also a user to copy/paste a channel url, or bookmark it.

Initialize script in componentDidMount – runs every route change

I am working on a navbar for my react app (using gatsbyjs to be precise). In the navbar I have a marquee that I initialize in the navbar component in componentDidMount.
It works as intended, but upon every route change componentDidMount will run again which results in the marquee speeding up for every page change, making it go faster and faster.
Is this expected behaviour? And if so, how do I make sure that the script is only run once?
navbar.js
import React from 'react';
import { Link } from 'gatsby';
import styles from '../styles/navbar.module.css';
import NewsMarquee from './newsMarquee';
import Marquee3k from 'marquee3000';
const topLevelNav = [
{
href: '/news',
label: <NewsMarquee/>,
extraClass: styles.navLinkNews,
mediaQueryClass: styles.navLinkHiddenSmall,
},
];
export default class Navbar extends React.Component {
componentDidMount() {
Marquee3k.init();
}
render() {
return (
<div>
<header className={styles.navbar} role="banner">
<nav className={styles.nav}>
{topLevelNav.map(({ href, label, extraClass = '', mediaQueryClass = '' }) => (
<Link
key={label}
to={href}
className={`${styles.navLink} ${extraClass} ${mediaQueryClass} ${menuItemsHidden}`}
activeClassName={styles.navLinkActive}
>
{label}
</Link>
))}
</nav>
</header>
</div>
)
}
}
newsMarquee.js
import React from 'react';
import { StaticQuery, graphql } from "gatsby";
import styles from '../styles/newsMarquee.module.css';
export default () => (
<StaticQuery
query={graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC } limit: 10) {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "YYYY.MM.DD")
}
fields {
slug
}
}
}
}
}
`}
render={data => (
<div className={`marquee3k ${styles.marquee}`}>
<div>
{data.allMarkdownRemark.edges.map(({ node }) => (
<span className={styles.marqueeItem} key={node.id}>
{node.frontmatter.date} {node.frontmatter.title}
</span>
))}
</div>
</div>
)}
/>
)
Since I'm using GatsbyJS I went with this plugin from V1, which makes my layout component persist across pages.
gatsby-plugin-layout
This plugin enables adding components which live above the page components and persist across page changes.
This can be helpful for:
Persisting layout between page changes for e.g. animating navigation
Storing state when navigating pages
Custom error handling using componentDidCatch
Inject additional data into pages using React Context.
This plugin reimplements the behavior of layout components in gatsby#1, which was removed in version 2.

Reactjs: Axios.post call returns array object from database - need to map to other component

I've been struggling with this for a couple days, and any help would be appreciated.
In this component, I have tried to do an HTTP call to my server and database. After parsing the response, using JSON.parse, I am getting back a correctly formed JSON object. I then want to map through that object and for each return a new component (called HistoryItem).
The code below attempts to do this by placing the object into the component state, but it is causing an infinite refresh loop. Previously I had tried a functional component.
The original iteration of this component did work. But it pulled a static JSON object from my client side files. Therefore, I am confident code works without the http call.
It seems to me I am doing something wrong with the async, which is disallowing the JSON object received asynchronously from being rendered.
Below is the main component. Note the component imports the username from redux. This feeds the HTTP call, so that it retrieves only records associated with the logged in user. Again, everything looks fine on the server/database end...
import React, {Component} from 'react';
import style from './history.css';
import HistoryItem from './HistoryItem/historyItem';
import data from '../../config/fakermyhistory.json';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import axios from 'axios';
class History extends Component {
constructor(props) {
super(props);
this.state = {
compiledList:[]
}
}
getData(){
this.state.compiledList.map((call, i) => {
const shaded = (call.rated) ? 'lightgrey' : 'white';
console.log("shaded", shaded);
return(
<Link to={`/reviewpage/${call._id}`} key={call._id}
style={{ textDecoration: 'none', color:'lightgrey'}}>
<div style={{backgroundColor:shaded}}>
<hr/>
<HistoryItem call={call}/>
</div>
</Link>
)
})
}
render(){
axios.post('/api/history', {username: this.props.username})
.then((res) => {
const array = JSON.parse(res.request.response);
this.setState({compiledList: array})
console.log("res", array);}
).catch((err) => console.log("err", err));
return (
<div className={style.container}>
<div className={style.historyHeader}>
<div className={style.historyHeaderText}>
Your Call History
</div>
</div>
<div className={style.historyList}>
{this.getData()};
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
username:state.auth.username
};
}
export default connect(mapStateToProps, null)(History);
Thanks in advance if you can help.
Here is another version using it as a functional component. Also doesn't render (although no errors on this one)
import React, {Component} from 'react';
import style from './history.css';
import HistoryItem from './HistoryItem/historyItem';
import data from '../../config/fakermyhistory.json';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import axios from 'axios';
const History =(props)=> {
const getData=(props)=>{
console.log("props", props);
axios.post('/api/history', {username: props.username})
.then((res) => {
const array = JSON.parse(res.request.response);
console.log("array", array);
array.map((call, i) => {
const shaded = (call.rated) ? 'lightgrey' : 'white';
console.log("shaded", shaded);
return(
<Link to={`/reviewpage/${call._id}`} key={call._id}
style={{ textDecoration: 'none', color:'lightgrey'}}>
<div style={{backgroundColor:shaded}}>
<hr/>
<HistoryItem call={call}/>
</div>
</Link>
)
})
}
).catch((err) => console.log("err", err));
}
return (
<div className={style.container}>
<div className={style.historyHeader}>
<div className={style.historyHeaderText}>
Your Call History
</div>
</div>
<div className={style.historyList}>
{getData(props)};
</div>
</div>
)
}
const mapStateToProps = state => {
return {
username:state.auth.username
};
}
export default connect(mapStateToProps, null)(History);
Instead of calling axios in render function, try to invoke it from componentDidMount.
This will help you prevent the infinite loop.
To return the components rendered within the map function, it was necessary to add a "return" command before the map function was called:
return array.map((call, i) => {...

React - Route with same path, but different parameters change data on update

I am creating a page where you can see peoples profiles and all their items, which has pathname="/users/{their id}" and a menu where it can take you to your profile. But my problem is that when you go to a persons profile page, and then to another one, the pathname changes but the data does not get rendered it only changes the pathname and the data remains the same. In order to render the data, you would have to refresh the page and then it shows the new users data. How would I get it so you wouldn't have to refresh the page, so like they click on the user they want to go to, the pathname changes and renders the new data without the page refresh? Also, something happens when you refresh the page when on a user profile, it is supposed to return the users' email address, which it does when you first visit the page, but when you refresh the page it returns an error saying it can't find the email.
Here is the code for the menu part (link to my profile):
import { Meteor } from "meteor/meteor"
import React from "react";
import { withRouter, Link } from "react-router-dom";
import { SubjectRoutes } from "./subjectRoutes/subjectRoutes";
import AddNote from "./AddNote";
class Menu extends React.Component{
render(){
return(
<div>
<div className="scroll"></div>
<div className="menu">
<h1>Menu</h1>
<p><Link to="/">Home</Link></p>
<Link to="/searchNotes">Notes</Link>
<p><Link to="/addNote">Add a Note</Link></p>
<p><Link to={`/users/${Meteor.userId()}`} >My Profile</Link></p>
</div>
</div>
)
}
}
export default withRouter(Menu)
userProfile.js:
import { Meteor } from "meteor/meteor";
import React from "react";
import { withRouter } from "react-router-dom";
import { Tracker } from "meteor/tracker";
import Menu from "./Menu";
import RenderNotesByUserId from "./renderNotesByUserId"
class userProfile extends React.Component{
constructor(props){
super(props);
this.state = {
email: ""
};
}
logoutUser(e){
e.preventDefault()
Accounts.logout(() => {
this.props.history.push("/login");
});
}
componentWillMount() {
Meteor.subscribe('user');
Meteor.subscribe('users');
this.tracker = Tracker.autorun(() => {
const user = Meteor.users.findOne(this.props.match.params.userId)
this.setState({email: user.emails[0].address})
});
}
render(){
return(
<div>
<Menu />
<button onClick={this.logoutUser.bind(this)}>Logout</button>
<h1>{this.state.email}</h1>
<RenderNotesByUserId filter={this.props.match.params.userId}/>
</div>
)
}
}
export default withRouter(userProfile);
Sorry to make this question so long it's just a really weird problem that I am having which I can't seem to find any answers to online.
ComponentWillMount() only runs one time, before your component is rendered. You need to also use ComponentWillReceiveProps() in order to update your state when your props change.
Check out React Component Lifecycle
you can use useLocation in this situation.
let location = useLocation();
useEffect(() => {
dispatch(fetchDetail(id))
dispatch(fetchSuggestions(category))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location]);

Resources