I'm trying to implement a fairly standard blog app using Svelte, Svelte Routing and Firestore, but I think I'm misunderstanding a fundamental part of how props are passed between components.
I based my initial code on the excellent tutorial on Fireship.io - which worked as per the tutorial: https://fireship.io/lessons/svelte-v3-overview-firebase/
From there I've added Svelte Routing - https://github.com/EmilTholin/svelte-routing - and I'm attempting to add a view route.
Relevant part of App.svelte:
<Router url="{url}">
<Navbar user="{user}" />
<div>
<Route path="posts/:id" component="{Post}" />
<Route path="posts/add" component="{PostForm}" />
<Route path="posts" component="{Blog}" />
<Route path="/" component="{Home}" />
</div>
</Router>
In my Blog Component I use a component called PostTeaser, in which I pass a link to the post view page.
Blog Component:
<div class="posts">
{#each posts as post}
<PostTeaser post="{post}" />
{/each}
</div>
PostTeaser component:
<div class="post-teaser">
<h2 class="title is-3"><Link to="posts/{ post.id }" {post}>{ post.title }</Link></h2>
<div class="post-teaser-content">{ post.content }</div>
</div>
Here I get a warning in the browser:
<Link> was created with unknown prop 'post'
Although the teaser does appear on the screen with the correct information.
When I click through to the post, i.e. the Post Component, my data is undefined.
I am placing export let post; in the script tag of each of my components.
Should I be using a "store" for my data? At the moment I'm fetching my data in the BlogComponent and passing it down the line. It would seem that this is incorrect. Any help gratefully appreciated.
See here for fuller example: https://codesandbox.io/s/romantic-cannon-ri8lo
With svelte-routing, you don't pass props from the <Link> component, you pass them from the <Route> component implicitly. Where you have this...
<Route path="posts/:id" component="{Post}" />
...you're telling the router that if the URL matches the pattern /posts/:id, it should render the Post component, passing it an id prop that matches that part of the URL.
The Post component is responsible for fetching its data based on that. So in this case, you could move the posts array into a separate module, and change Post.svelte accordingly:
<script>
import posts from "./posts.js";
export let id;
$: post = posts.find(p => p.id == id);
</script>
<div class="post">
<h1>{ post.title }</h1>
<div class="post-content">{ post.content }</div>
</div>
(Note that props are stringified because they're derived from the href, so we need a == comparison rather than ===.)
Here's a working fork.
Simple example:
Parent Component
<script>
import PostTeaser from "./PostTeaser.svelte";
let post = {
first: 'First prop',
second: 'Second prop'
};
</script>
<PostTeaser {...post} />
Child Component
<script>
export let first;
export let second;
</script>
First prop: {first} <br>
Second prop: {second}
Code here: https://codesandbox.io/s/spread-props-using-svelte-y7xou
Related
I'm testing Svelte and one of the things I'm trying to do is create a blog. I'm keeping it simple, all I want is to click in a link an open an article.
I only have three files:
home.svelte
blog.svelte
article.svelte
Home.svelte
<script>
import { Router, Route, Link } from "svelte-navigator";
import Blog from "./Blog.svelte";
import Article from "./Article.svelte";
</script>
<main>
<Router primary={false}>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="blog">Blog</Link>
</li>
</ul>
<Route path="/">
<h1>Welcome to my beautiful website!</h1>
</Route>
<Route path="blog" component={Blog} />
<Route path="blog/article/*" component={Article}/>
</Router>
</main>
Blog.svelte
<script>
import { Router, Route, Link } from "svelte-navigator";
const articles =
[
{id: 1, title: 'Sample title', description: 'Sample description' },
{id: 2, title: 'Sample title 2x', description: 'Sample description 2x' },
];
</script>
<h1>List of articles</h1>
<ul>
{#each articles as article}
<li>
<Link to="article/{article.id}">{article.title}</Link>
</li>
{/each}
</ul>
Article.svelte
<script>
export let title;
export let description;
</script>
<h1>{title}</h1>
<p>{description}</p>
My problems:
When opening the URL blog/article/1 it shows undefined in both title and description
The point 1. makes sense, since I'm not passing anywhere values to the article route, how do I do it?
It doesn't make sense to me that in the home.svelte I have to declare the route of article, I think it should be declared in the blog.svelte, however, if I do that, the article route is not loaded once I try to navigate to it.
I have tried to pass data as argument but doesn't seem to work:
<Link to="article/{article.id}" title={article.title}>{article.title}</Link>
From svelte-navigator documentation, the right way is one of:
<Route path="blog/:id" component="{Article}" />
<Route path="blog/:id" let:params>
<Article id="{params.id}" />
</Route>
You will the receive a props named id in your Article component. You'll have to get the title & description out of this id (through a basic import or a http fetch or something else).
I don't know your whole project but I highly recomand you to have a look at svelte-kit (routing & server extension of svelte that can generate static site if you need) which handles routing natively. You can then use mdsvex or svelte-markdown as a way to display markdown as html:
the first one is better if your articles are file based
the second one is better if your article are stored in a database.
I have a problem with routing to a specific part of another functional component in React.
I have declared Route in Main component and Link around the place where I want to redirect from... it looks like this...
The main function gets:
<Switch>
<Route exact path='/news/:eyof' component={News} />
<Route exact path='/news/:svetsko' component={News} />
<Route exact path='/news' component={News} />
</Switch>
And I defined in other functional component:
<div className="card">
<Link to="/news/:svetsko">
<div className="card-header">
<h3>Artistic gymnastics world championship</h3>
</div>
</Link>
</div>
So by clicking on this link, I need to get redirected to News page class "svetsko" so I can read about that news... All news are in containers in same page....
Try something like this in your component:
let { scrollToSection } = useParams();
svetskoRef = React.createRef();
useParams() allows you to access :svetsko. When the component loads, you can use scrollToSection to navigate between different parts of the page. The scrollRef is used to access the DOM element you want to scroll to.
window.scrollTo(0,scrollRef.offsetTop)
The markup would look something like this:
<div className="card" ref="svetskoRef">
<Link to="/news/:svetsko">
<div className="card-header">
<h3>Artistic gymnastics world championship</h3>
</div>
</Link>
</div>
You need only one route like
<Switch>
<Route exact path='/news' component={News} />
</Switch>
you can give link like
<Link to={{
pathname: '/news',
state: { news : yourNewsName } // you can pass svetsko here
}}>
<div className="card-header">
<h3>Artistic gymnastics world championship</h3>
</div>
</Link>
You can access this state in your News Page like
<div>{ props.location.state !== undefined ? props.location.state.news : '' }</div>
After getting your news type like eyof :
Step 1 :
you can create on functional component which takes argument as your
data and return your new post jsx with your data.
Step2 :
So,when you get your eyof in your page then you are going to call
this function and pass your data related to your eyof and that function
return your new post to your page.
Ok, I found one really good library as a solution, it's called react-scroll and it has an option to wrap link you need in earlier defined scroll link, and to wrap component you want it to link as a specific part of the page with Element with id you gave to scroll link as a parameter... Try it if you need it somehow.
I have this react + redux app
I have 1 Movie component with some movie data displayed on it which are fetched from API.
So at the bottom, i decided to add a Similar movies section with a couple of components to navigate to the new movie pages.
<div className="similar_movieS_container">
{ this.props.thisMovieIdDataSIMILAR.slice(0,4).map((movie,index)=>{
return(
<Link to={"/movie/"+movie.id} key={index}>
<div className="similar_movie_container">
<div className="similar_movie_img_holder">
<img src={"http://image.tmdb.org/t/p/w185/"+movie.poster_path} className="similar_movie_img" alt=""/>
</div>
</div>
</Link>
)
})
}
</div>
Now when I'm at a root route for instance /toprated and i click on a <Link to={"/movie/"+movie.id} key={index}> i get navigated to the particular route (e.g. /movie/234525 ) and everything works fine , but if I'm in a /movies/{some move ID } route and i click on some <Link to={"/movie/"+movie.id} key={index}> The route in the URL bar gets updated, but the page stays still, meaning nothing changes if I reload the page with the new route the new movieID data is displayed...
So how can i make a navigation to a new /movie/{movieID} FOMR a /movie/{movieID} ?
It's because you are just within same route.
There are few ways to work around this. I bet you are rendering routes like this.
<Route path="/movie/:id" component={Movie} />
Try like this.
<Route path="/movie/:id" component={() => <Movie />} />
Another solution would be to use componentWillReceiveProps instead of componentWillMount.
This happens because the component of page movie was mounted, so the render method of this component will not be mounted again or updated, so you need to fire a code to change the state, and consequently re-rendering the page content, for this you can implement the componentDidUpdate method which is fired for every props modification.
https://reactjs.org/docs/react-component.html#componentdidupdate
Basically you implement the same code above inside
I have a component comment.js
export default function Comment (props) {
return (
<div className="comment-wrapper">
<img src={props.userImage} />
<p className="comment">{props.commentTitle}</p>
</div>
);
}
So I just simply want to have that component in the parent component as
<Comment userImage="IMAGE_LINK" commentTitle="BLAH BLAH" />
Again, I am using the Create-React-App build system from facebook. With that being said I know I can hard code an image using the following
<img src={require(`./images/MY-IMAGE.png`)} />
The code above works perfectly fine for the test image I am trying to load. However, when needed dynamically for the component the issue gets a bit more complex.
Now with the comment.js component above, I cannot do
<img src={require("./images" + {props.userImage})} />
I have taken a look at one thread on this site as well as reading this blog post on the issue and can still not come to a conclusion.
How can I handle image assets being passed as props to a component, in this case?
you can use import
// parent component
import MenuImage from '/img/menu.png'
<Comment image={MenuImage} commentTitle="Title"} />
then on Comment component
export default props => (
<img src={props.image} alt='' />
)
I have what looks like it will be a simple and common case of non-DRY code - I'm hoping there's a way to avoid this.
I have a page to render information about an object based on a route like this:
<Route path="project/:projectid" component={ProjectDetails}/>
So... the 'project id' comes in as a prop from React Router, and every stateless presentation component needs the resulting project object:
class ProjectDetails = ({project}) => {
render() {
return (
<div className="project-details-page container-fluid">
<HomeLeftBar/>
<div className="col-md-10">
<ProjectHeaderBar project={project}/>
</div>
<div className="project-centre-pane col-md-7">
<ProjectActions project={project}/>
<ProjectDescription project={project}/>
<ProjectVariations project={project}/>
</div>
<div className="project-right-bar col-md-3">
<ProjectContacts project={project}/>
<ProjectCosting project={project}/>
</div>
</div>
)
}
}
export default connect(
state => ({project: state.projects[state.router.params.projectid]})
)(ProjectDetails)
How should I clean this up, so I don't have to pass the project to every component?
I've noted a couple of similar questions this and this at least. But while their titles are similar, their details are not. They seem to ask more advanced questions about more specific situations.
For me, passing the data down from the top via props is the clearest way to get data into your leaf components, so I think what you've done here is fine.
The bit that looks not-clean is that you're passing the whole project object to every component but at a glance, it looks as though each of those components is responsible for rendering only a portion of that project object.
Passing only the branches of project that each component needs will make the relationship between the data and the view much clearer.
<ProjectContacts project={project.contracts}/>
<ProjectCosting project={project.costing}/>
and in turn I think this will make the whole component feel cleaner:
class ProjectDetails = ({project: { costings, description, variations, title, actions, contacts} }) => {
render() {
return (
<div className="project-details-page container-fluid">
<HomeLeftBar/>
<div className="col-md-10">
<ProjectHeaderBar title={title}/>
</div>
<div className="project-centre-pane col-md-7">
<ProjectActions actions={actions}/>
<ProjectDescription description={description}/>
<ProjectVariations variations={variations}/>
</div>
<div className="project-right-bar col-md-3">
<ProjectContacts contacts={contacts}/>
<ProjectCosting costings={costings}/>
</div>
</div>
)
}
}
I assume you are using react-router. You can do something like this:
<Router history={history}>
<Route path="project/:projectId">
<Route path="participants" component={Participants} />
<Route path="progress" component={Progress}/>
</Route>
</Router>
All children of <Route path="project/:projectId"> have this.props.params.projectId available. For example, url to the Participants component is "project/:projectId/participants", and therefore projectId is a param when rendering Participants.
Also, if the project details should be rendered the same way on all pages, you can add a component on the Route to "project/:projectId", which is responsible for doing rendering the project details. (That component would receive Participants or Progress as this.props.children, which you'd have to render in the new component.)