React-router-dom redirect issue - javascript

i'm working on this project that's an implementation of youtube, let's say i search for 'Sia' for example at '/' i get the result back with videos,channels,playlists and when i click on the channel item i route to '/channel' with the channel component now the problem is , when i search for something while at /channel i'm supposed to redirect back to '/' and get the search results with the submitted search term. but i have no idea what's going wrong or if it's a good idea wheather to make the Header component a direct child of the BrowserRouter or render it in each route component along with it's props (which what i went for anyway)
here's the channel component and routing
class ChannelDisplay extends React.Component {
onFormSubmit = (term) => {
this.props.fetchList(term);
this.props.defaultVideo(term);
}
renderHeader() {
const {channel} = this.props
if(!channel.snippet) return <Search/>
if(channel) {
const subNum = `${Number(channel.statistics.subscriberCount).toLocaleString()}`
return (
<div className="channel">
<Header onFormSubmit={this.onFormSubmit}/>
<div className="container">
<img className="img-fluid" src={channel.brandingSettings.image.bannerImageUrl} alt={channel.snippet.title} />
<div className="d-flex flex-nowrap">
<img className="img-thumbnail img-fluid channel-img mx-2 my-2" src={channel.snippet.thumbnails.default.url} alt={channel.snippet.title} />
<div className="media-content">
<p>{channel.snippet.title}</p>
<span><i className="fab fa-youtube mr-2"></i> Subscribe {subNum}</span>
</div>
</div>
</div>
</div>
)
}
}
render() {
return this.renderHeader()
}
}
const mapStateToProps = state => {
return {channel:state.channel}
}
export default connect(mapStateToProps,{fetchList,defaultVideo})
(ChannelDisplay)
.
render() {
return (
<div>
<BrowserRouter>
<div>
<Route path="" exact component={Search} />
<Route path="/channel" exact component={ChannelDisplay} />
</div>
</BrowserRouter>
</div>
)
}
entire code https://github.com/IslamGamal88/minitube

Maybe you should add history.push or history.replace into your submit function in Search.js file, but I think the push is a much better option because you will be able to go back with back button to your channel or video or something.
onFormSubmit = (term) => {
this.props.fetchList(term);
this.props.defaultVideo(term);
this.props.history.push('/');
};

Related

Optimal way to set state of another component from

there's a few posts explaining how to update state from another component but I'm still unable to parse the solution for my particular problem. I want the submit button in my SideBar.js component to trigger nextProblem() in my tableA.js component when it is clicked. This will cause the state of tableA.js to change as problemNum goes from 1 to 2, generating a different form. I can't import SideBar.js component into my tableA.js due to my design layout though! SideBar.js also contains images. Any ideas?
I'm using a CSS grid for the layout of my page:
grid-template-areas:
"navbar navbar navbar navbar"
"sidebar tableA ... ..."
"sidebar tableA ... ..."
"sidebar tableA ... ..."
I'm using App.js to route to the pages:
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path={"/"} exact element={<MainPage/>}/>
<Route path={"/gamePage"} exact element={<GamePage/>}/>
</Routes>
</div>
</Router>
);
}
export default App;
I created a SideBar.js component that contains a logo at the top and a submit button at the bottom:
class SideBar extends Component {
componentDidMount() {
}
render() {
return (
<div className={"sidebar"}>
<div className={"logo_group"}>
<img className={"some_logo"} src={logo}/>
</div>
<div className={"nav_group"}>
<button className={"home"}/>
<button className={"about"}/>
</div>
<img className={"an_image"} src={someImage}/>
<div className={"button_group"}>
<button className={"submit_button"} onClick={() => nextProblem()}>Submit</button>
</div>
</div>
)
}
}
export default SideBar
and a tableA.js component with a function nextProblem() that changes the state of the component:
class tableA extends Component {
state = {
problemNum: 1,
problemStatus: 'unsolved',
userAnswer: []
};
nextProblem = () => {
this.setState(state => ({
problemNum: state.problemNum + 1,
}))
}
componentDidMount() {}
render() {
return (
<div>
<form>
...depends on the state of the problem number
</form>
</div>
)
}
export default tableA
I then consolidate everything in GamePage.js:
const GamePage= () => {
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar></SideBar>
</div>
<div className={"tableA"}>
<TableA></TableA>
</div>
...
</div>
)
}
export default GamePage
i'd propably go the approach of letting the gamepage handle the state and such and send down the problem and the update function down the components as props. Depending on how big this thing will grow you could also use a centralized store.
const GamePage= () => {
const [problem, setPropblem] = useState(0)
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar
onSetNext={() => setProblem(problem + 1)}
></SideBar>
</div>
<div className={"tableA"}>
<TableA
problem={problem}
></TableA>
</div>
...
</div>
)
}
export default GamePage
and in your sidebar the button becomes
<button className={"submit_button"} onClick={() => this.props.onSetNext()}>Submit</button>
similarly you access the props.propblem instead of state in your table.

useLocation not rending props - Gatsby routing

I'm trying to render a blog as a card then open it up as a page , but its proving to be difficult using Gatsby. I did the same thing fine with react using React router and useLocation but it doesn't seem to be working with Gatsby.
I switched to reach router as suggested in another post but that doesnt work. Im looking for another method now that perhaps does not need to use useLocation.
I kept getting this error when I used react-router-dom:
Invariant failed: You should not use <Link> outside a <Router>
function Blog() {
const [blogs, setBlogs] = useState([])
const [image, setImage] = useState()
const [selectedBlog, setSelectedBlog] = useState(blogs)
useEffect(() => {
fetch("http://cdn.contentful.com...")
.then(response => response.json())
.then(data =>
setBlogs(data.items)
)
}, [])
console.log(blogs)
return (
<>
<div className="card-flex" >
{selectedBlog !== null ? blogs.map((blog =>
<Card title={blog.fields.title} date={blog.fields.date} introduction={blog.fields.introduction} mainBody1={blog.fields.mainBody1} mainBody2={blog.fields.mainBody2} setSelectedBlog={selectedBlog}
/>
)):
<Article title={blogs.find(d => d.fields.title === selectedBlog)} />
}
</div>
</>
)
}
export default Blog
Blog Card
function Card(props) {
console.log(props)
return (
<div class="container">
<div class="card">
<div class="card-header">
<img style={{backgroundImage: "url('https://i.pinimg.com/564x/7f/bb/97/7fbb9793b574c32f5d28cae0ea5c557f.jpg')"}}/>
</div>
<div class="card-body">
<span class="tag tag-teal">{props.tags}</span>
<h4>{props.title}</h4>
<p style={{fontSize:"17px", paddingTop:"10px"}} >{props.introduction}</p>
<div class="card-user">
<Link
to={{
pathname: '/article',
state: {
title: props.title,
introduction: props.introduction
}
}}
>
<button>read more</button>
</Link>
<div class="user-info">
<h5 >{ props.date}</h5>
</div>
</div>
</div>
</div>
</div>
)
}
export default Card
Article Page
import React from 'react'
import './Article.css'
import { useLocation } from "#reach/router"
function Article(props) {
// useLocation to access the route state from Blog.js
const { state = {} } = useLocation();
console.log(state)
return (
<div className="main">
<h1 className="title">{state.title}</h1>
<p className="intro">{state.introduction}</p>
<p className="main1">{state.mainBody1}</p>
<p className="main2">{state.mainBody2}</p>
</div>
)
}
export default Article
I believe you're not supposed to use react-router on a Gatsby project: https://www.gatsbyjs.com/docs/reference/routing/creating-routes/
For a normal project you could do:
Go to your top-most element and wrap it with a Router. https://reactrouter.com/web/api/BrowserRouter
You basically have to search for ReactDom.render(<YourApp />) and do ReactDom.render(<Router><YourApp /></Router>)

Forwarding props from parent to child component

I have 2 components list of posts and when clicking on link on post card i'm entering into post.
I can't access props.postDetails in child component. When I console log the props, I have {history: {…}, location: {…}, match: {…}, staticContext: undefined} only this without props.postDetails.
Can somebody help?
Code for parent component is:
mport {useState, useEffect} from 'react';
import {BrowserRouter as Router, Switch, Route, Link, withRouter} from "react-router-dom";
import logo from "./assets/images/logo.jpg";
import Post from './Post';
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
getResults();
},[]);
const getResults =() => {
fetch("https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json")
.then(response => response.json())
.then(data => {setPosts(data)});
}
const postsArr = [];
Object.values(posts).forEach((post, key) => {
postsArr.push(post);
});
return(
<div>
<div className="container-fluid">
<div className="row">
<div className="posts-container col-md-12">
<div className="row">
{
postsArr.map((post, key) => (
<div className="col-md-4">
<Link to={`/post/${key}`} >
<div className="pic-wrapper">
<img className="img-fluid" src={post.pic} alt={post.title}/>
</div>
<h4>{post.title}</h4>
<Post postDetails={post}/>
</Link>
</div>
))
}
</div>
</div>
</div>
</div>
</div>
)
}
Code for child component:
import {withRouter} from "react-router-dom";
const Post = (props) => {
const {pic, title, author, description} = props.postDetails;
return(
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title}/>
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
)
}
export default withRouter(Post);
Issue
Ok, it's as I started to suspect. You are rendering a Post component in more than 1 place.
The issue here is that in Home.js you are passing a postDetails prop, (<Post postDetails={post.pic} />), but in app.js you are only passing the route props from Route, (<Route path="/post/:postId" exact strict component={Post} />). This Post component is the one triggering the error.
Solution
An easy solution is to simply pass the post data along with the route transition.
<Link
to={{
pathname: `/post/${key}`,
state: {
post
}
}}
>
...
<Post postDetails={post.pic} />
</Link>
And access the route state on the receiving end in Post. Try to read the post details from props first, and if they is falsey (null or undefined) assume it was passed in route state and access it there.
const Post = (props) => {
const { state } = props.location;
const { pic, title, author, description } = props.postDetails ?? state.post;
return (
<div className="container">
<div className="pic-wrapper">
<img className="img-fluid" src={pic} alt={title} />
</div>
<h4>{title}</h4>
<p>{author}</p>
</div>
);
};
Of course there is room to make this a bit more robust but this is a good start.
Additional Suggestion
Instead of saving post state that isn't formed correctly for what/how you want to render it, you can transform the response data before saving it into state. This save the unnecessary step of transforming it every time the component rerenders.
const getResults = () => {
setLoading(true);
fetch(
"https://blog-d8b04-default-rtdb.europe-west1.firebasedatabase.app/posts.json"
)
.then((response) => response.json())
.then((data) => {
setPosts(Object.values(data));
setLoading(false);
});
};
Then map as per usual. Make sure to place the React key on the outer-most mapped element, the div in your case.
{posts.map((post, key) => (
<div className="col-md-4" key={key}>
...
</div>
))}
Demo
That is indeed an expected behaviour, because you are actually mapping what appears to be an empty array - see postArr; on your first render it will result as an empty array and since that's not a state, it will never re render your child component with the appropriate props.
I don't really see why you fetch the data, set them to your posts useState and then copy them over to a normal variable; Instead, remove your postArr and on the map replace it with your posts directly.
Since that's a state, react will listen to changes and rerender accordingly, fixing your problem

React router and props

Need help passing props from different components
my routing structure is as follows
app.js
<BrowserRouter>
<div className='App'>
<Switch>
<Route path='/' exact component={Home} />
<Route exact path='/details/:type/:id' component={ItemDetails} />
</Switch>
</div>
</BrowserRouter>
on my Home component, have a bunch of API call's all structured like this
getUpcomingMovies = () => {
axios.get('https://api.themoviedb.org/3/movie/upcoming?api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US&page=1')
.then((res) => {
console.log(res.data)
this.setState({ upcomingMovies: res.data.results })
})
}
functional component gets rendered like so
const UpcomingMovies = (props) => {
const upcomingMovieResults = props.upcomingMovies.map(r => (
<Link key={r.id} to={`/details/${r.id}`}>
<div
key={r.id} >
<img src={`https://image.tmdb.org/t/p/w185/${r.poster_path}`} alt={r.title} className='top-movie-results-poster' />
</div>
</Link>
))
return <div className='top-movie-results'>
<h2 className='top-rated-header'>Upcoming Movies</h2>
<div>
<Carousel infinite
slidesPerPage={8}
slidesPerScroll={3}
arrows
animationSpeed={1500}
stopAutoPlayOnHover
offset={50}
itemWidth={225}
clickToChange
centered>{upcomingMovieResults}</Carousel></div>
</div>
}
ItemDetails.js
fetchItemDetails = (type = this.props.match.params.type) => {
if (type === 'movie'){
const itemId = this.props.match.params.id;
const ROOT_URL = 'https://api.themoviedb.org/3/movie';
const API_KEY = 'api_key=40d60badd3d50dea05d2a0e053cc96c3&language=en-US';
axios.get(`${ROOT_URL}/${itemId}?${API_KEY}`).then(res => {
console.log(res.data);
console.log(this.props.match.params.type)
this.setState({ itemDetails: res.data })
});
}
};
functional component (child for itemDetails)
const MovieDetails = (props) => {
return <div className='item-details'>
<div>
<a href='#t' className='item-name'>{props.itemDetails.title}</a>
<a href='#t' className='item-name'>{props.itemDetails.name}</a>
</div>
</div>
}
I know this is a a lot of code, but I wanted to give you guys the full spectrum.
But basically the issue I'm having is when I do
<Link key={r.id} to={/details/${props.itemDetails.type}/${r.id}}>
into my functional component, I get 'TypeError: Cannot read property 'type' of undefined', which is on the localhost:3000/ aka home route, but when I manually navigate to localhost:3000/movie/12312 it works fine
so it seems like the issue is that my home route 'localhost:3000/' is not aware of {this.props.type} from itemDetails.. Any ideas?
In functional components, there is no this or the owner having props. Use props directly.
<Link key={r.id} to={/details/${props.itemDetails.type}/${r.id}}>
try this:
<Link key={r.id} to={/details/${props.itemDetails && props.itemDetails.type}/${r.id}}>
if this didn't work, you may wanna provide a sandbox demo for this.

Render react component onClick

I'm super new to react but excited about its potential. Still getting to grips with the fundamentals of it all so any explanation would be greatly appreciated.
I'm looking to render an 'About' component as the user clicks a button in the 'Nav' component (with the aim to toggle this later down the line).
I've attempted to do it in the simplest way I can think of, but this is obviously very wrong:
class Nav extends React.Component {
renderAbout() {
return (
<About />
);
}
render() {
return (
<div className="Nav">
<div className="Button-Container">
<div className="Nav-Text About-Button">
<h2 onClick={() => this.renderAbout()}>About</h2>
</div>
</div>
</div>
);
}
}
Would this have something to do with updating the 'state' of the About component?
Thanks in advance.
You can use state to define if imported component About has to be rendered or not.
class Nav extends React.Component {
state = {
isAboutVisible: false,
}
render() {
return (
<div className="Nav">
<div className="Button-Container">
<div className="Nav-Text About-Button">
<h2 onClick={() => this.setState({ isAboutVisible: true }) }>About</h2>
</div>
</div>
{ this.state.isAboutVisible ? <About /> : null }
</div>
);
}
}
You currently do not have "About" component in actual view, you just render it somewhere out there, in the void!
To properly render a component you have to specify its place in JSX expression. Also, as one of the easiest solutions) you probably want to toggle it. So that translates to something like this:
constructor(props){
super(props)
this.state={toggle:false}
}
renderAbout(toggle) {
if(toggle)
return <About />
else return null;
}
render() {
return (
<div className="Nav">
<div className="Button-Container">
<div className="Nav-Text About-Button">
<h2 onClick={() => this.setState({toggle: !toggle})}>About</h2>
</div>
</div>
{this.renderAbout(this.state.toggle)}
</div>
);
}
}
Yes, you have to change state of the component. Changing the state will automatically rerender your component. In your example it should be something like:
class Nav extends React.Component {
state = {
showAbout: false; // initial state
}
renderAbout = () => {
if (!this.state.showAbout) return '';
return (
<About />
);
}
// ES6 priavte method syntax
handleButtonClick = () => {
this.setState({showAbout: true});
}
render() {
return (
<div className="Nav">
<div className="Button-Container">
<div className="Nav-Text About-Button">
<h2 onClick={this.handleBtnClick}>About</h2>
{this.renderAbout()}
</div>
</div>
</div>
);
}
}
You could also consider using for example this package: https://www.npmjs.com/package/react-conditions
Also, remember that there is a rule that each method which listen for an event should start with the "handle" word. Like in may example.

Categories

Resources