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.
Related
I'm not sure why I can't use my pass on useState from Parent component <AppMain /> to the child component <GroupPage /> and <CreateGroupForm />
What I'm trying to do:
I'm working on an update functionality, where on clicking the edit button in <GroupPage />, I want the content of GroupPage to fill on the form fields of <CreateGroupForm />. So for that, I have created states currentId and setCurrentId in <AppMain /> since it's the parent component of both, and I can pass on these to its child components assuming they both share the states.
const AppMain = () => {
const [ currentId, setCurrentId ] = useState(null)
return (
<div>
<Switch>
<Route path="/groupMain" exact> <GroupMain /> </Route>
<Route path="/groupMain/:id" exact> <GroupPage setCurrentId={setCurrentId} /> </Route>
<Route path="/createGroup" exact> <CreateGroupForm currentId={currentId} setCurrentId={setCurrentId} /> </Route>
</Switch>
</div>
)
}
export default AppMain
const GroupPage = ({setCurrentId}) => {
const { group } = useSelector((state) => state.groups)
// the reason for this condition is to prevent rendering something before data is actually fetched
if(!group) return null
return (
<div>
<EditButton onClick= {() => {
setCurrentId(group._id)
history.push(`/createGroup/`)
}} />
<h1>{group.groupName}</h1>
</div>
)
}
export default GroupPage
Now when clicking on the edit button of <GroupPage /> I'm setting the current group Id in setCurrentId and directing it to the <CreateGroupForm />. In <CreateGroupForm /> I'm checking if currentId matches the one with the already existed group. And by useEffect I'm populating those values in form fields.
const CreateGroupForm = ({currentId, setCurrentId}) => {
const [groupData, setGroupData] = useState({
groupName: ''
})
const group = useSelector((state) => currentId ? state.groups.groups.find((grp) => grp._id === currentId) : null)
console.log(group) // null
console.log(currentId) // undefined
useEffect(() => {
if(group) setGroupData(group)
}, [group])
return (
<div>
<MainForm>
<form autoComplete="off" onSubmit={handleSubmit}>
<h1>{ currentId ? 'Editing' : 'Creating'} a Group:</h1>
<label htmlFor="Group Name">Your Group Namee: </label>
<input type="text" value={groupData.groupName} onChange={(e) => setGroupData({ ...groupData, groupName: e.target.value })} />
<button type="submit">Submit</button>
</form>
</MainForm>
</div>
)
}
export default CreateGroupForm
What is happening:
On clicking the Edit button, the form fields are not populating with the group content.
Please any help would be appreciated.
It's not a good practice to pass setStates between its children. I recommend you to create callback functions in your appMain, and pass that functions to your GroupPage and CreateGroupForm components. When you call those functions inside the components your functions will change your currentIdState in the appMain. Changing the state of currentId in your appMain the components will recive the new state of currentId
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
I am facing an issue while passing the state to the child component, so basically I am getting customer info from child1(Home) and saving in the parent state(App) and it works fine.
And then I am passing the updated state(basketItems) to child2(Basket). But when I click on the Basket button the basket page doesn't show any info in console.log(basketItems) inside the basket page and the chrome browser(console) looks refreshed too.
Any suggestion why it is happening and how can I optimize to pass the data to child2(basket) from main (APP).
update:2
i have tired to simulated the code issue in sand box with the link below, really appreciate for any advise about my code in codesandbox (to make it better) as this is the first time i have used it
codesandbox
Update:1
i have made a small clip on youtube just to understand the issue i am facing
basketItems goes back to initial state
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
Snippet from Parent main(App) component
function App() {
const [basketItems, setBasketItems] = useState([]);
const addBasketitems = (product, quantity) => {
setBasketItems(prevItems => [...prevItems, { ...product, quantity }])
}
console.log(basketItems) // here i can see the updated basketItems having customer data as expected [{...}]
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userData} userstatus={siginalready} />
</header>
<Switch>
<Route path="/" exact render={(props) => (
<Home {...props} userData={userData} userstatus={siginalready}
addBasketitems={addBasketitems}
/>
)}
/>
<Route path="/basket" exact render={(props) =>
(<Basket {...props} basketItems={basketItems} />
)}
/>
</Switch>
</div>
</Router>
Snippet from the child(basket)
function Basket({basketItems}) {
console.log(basketItems) // here i only get the [] and not the cusotmer data from parent component
return (
<div>
{`${basketItems}`} // here output is blank
</div>
);
}
export default Basket;
Snippet from the child(Home)
... here once the button is pressed it will pass the userselected details to parent
....
<Button name={producNumber} value={quantities[productName]} variant="primary"
onClick={() => {
addBasketitems(eachproduct, quantities[productName])
}}>
Add to Basket
</Button >
Your function works fine, the reason your output in addbasketItem does not change is the when using setState it takes some time to apply the changes and if you use code below you can see the result.
useEffect(()=>{
console.log('basket:',basketItems)
},[basketItems])
Your Basket component only renders once so replace it with this code and see if it works:
function Basket({ basketItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(basketItems);
}, [basketItems]);
return <div>{`${items}`}</div>;
}
but for passing data between several components, I strongly suggest that you use provided it is much better.
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('/');
};
I have this App.jsx that has the routing and I have a component NextPage.jsx that is a simple Link that should point to the next page. My issue is:how can I tell to the link in NextPage component to point to the next page? so if I am in Homepage the link should let me go to Portfolio, if on Portfolio I should be able to go to Skills and so on.
This is my App.jsx
const App = () => (
<div className="main-container">
<Menu/>
<NextPage/>
<AnimatedSwitch
atEnter={{ offset: 100}}
atLeave={{ offset: -100}}
atActive={{offset: 0}}
mapStyles={(style) => ({
transform: `translateX(${style.offset}%)`,
})}
>
<Route exact path="/" component={Homepage} />
<Route path="/portfolio" component={Portfolio} />
<Route path="/skills" component={Skills} />
<Route path="/contacts" component={Contacts} />
<Route path='*' component={NotFound} />
</AnimatedSwitch>
</div>
);
export default App;
This is my NextPage.jsx
const NextPage = () => (
<div className="next-arrow">
<Link to='#HEREGOESTHENEXTPAGELINK#'><i className="fa fa-angle-right" aria-hidden="true"></i></Link>
</div>
);
export default NextPage;
Your approach to implementing this seems a bit odd in my opinion, but I digress.
The first thing you'd need to do is to somehow store all the links as well as their order of appearance. The biggest question I'd say is where you're going to store this; you could store it in a database, create a global variable, store it in a stage-management library (if you use one), etc etc.
It is unclear which of these you use and which you'd prefer, so I'll leave that to you and just present the concept below.
In your root React Component define an array of all the links. You could do this in the constructor:
class MyApp extends React.Component {
constructor() {
super();
window.links = ["/", "/portfolio", "/skills", "/contacts"];
}
render() ...
}
This will make window.links accessible in all components. This means you compare the active url, look it up in the array, and make the link direct you to the next one.
const NextPage = (props) => {
let idx = window.links.indexOf(props.route.path) + 1;
let link = (idx == window.links.length) ? window.links[0] : window.links[idx];
return (
<div className="next-arrow">
<Link to={link}><i className="fa fa-angle-right" aria-hidden="true"></i></Link>
</div>
);
}
export default NextPage;
Note that if the current link is not one of the ones defined in window.links, the link will take you to the first one.