Let's asume that I have three components:
<ArticleFinder />
<ArticleViewer />
<RecentArticleList />
ArticleFinder is a component that fetch article list from server, and shows it. If user click a list item, will show <ArticleViewer />.
ArticleViewer is a component that fetch single article from server and display it.
RecentArticleList is a component that fetch recent 10 articles from server, and shows it. Like ArticleFinder, on user click a list item will show <ArticleViewer />, and if <ArticleViewer /> already mounted, <ArticleViewer /> just reload the article.
ArticleFinder and RecentArticleList components are receiving brief article data like this:
[{ _id: 1, title: 'Helloworld', date: '...' }, { _id: 2, title: 'Farewell!', date: '...' }]
And ArticleViewer will receive more specific data like this:
{ _id: 1, title: 'Helloworld', date: '...', content: '<p>Helloworld!</p>', files: [ ... ], tags: [ ... ] }
This is just for reduce the size of transmission, server will response as minimum data as possible. So for display content and files and others from ArticleViewer, this component must have called somekind of getData method.
Note that those 3 components can exists same time, they have their own range on screen. They can be overwrapped but still they are in the same time, same screen.
So when ArticleViewer mounted, it calls own getData method and display the results, but the problem is that if ArticleViewer is already mounted, click a list from RecentArticleList or ArticleFinder will not trigger getData of ArticleViewer because component didn't unmounted and mounted.
I'm using React Router 4.x with this, so I can redirect the url, but still there is no way to forcely invoke ArticleViewer.getData in ArticleFinder and RecentArticleList.
Someway, I did it with some kind of trick. I just checked the props that receives from React Router(this.props.params) has changed, and then invoke the getData method like this:
componentWillReceiveProps(nextProps, nextState) {
if(nextProps.location.pathname !== prevPath) {
prevPath = nextProps.location.pathname;
this.getArticles(category, search);
}
}
It's working actually, even if refresh the page, but I don't like it, it's not intuitive, not elegant, at least to me.
Is there a way to solve this problem more elegantly? Especially, somekind of React like(if it exists)?
Code is quite a mess and adding more functionalities makes more ugly and I want to fix this. I know there are no answers in programming but there can be exists readable solution, as I think.
Any advice will very appreciate it. Thanks!
These components seem to share state and functionality between them. You could move this state to the component containing these three components and just pass the article to show into your <ArticleViewer>.
You can use key prop to create a completely new instance of component and will be exactly like you are using the component for the first time.
<ArticleViewer {...yuorProps} key={id_of_article}/>
make sure the id is unique for each article.
Remember that you won't be using the existing component, you end up creating new component.
The React Router v4 way would be to use a custom render function for the Article View route -
<Match pattern="/:articleid"
render={({params}) => <ArticleViewer article={params.articleid} />}/>
This will render a new ArticleViewer component on route change.
Related
Will conditional rendering out of an object code split and lazy load as expected? Here's a short example of what I'm talking about.
const Component1 = lazy(() => import('some path'));
const Component2 = lazy(() => import('some path'));
const Component3 = lazy(() => import('some path'));
render () {
const { selectionIndex } = this.state;
<Suspense fallback={<div>Loading...</div>}>
{{
one: <Component1 />,
two: <Component2 />,
three: <Component3 />,
}[selectionIndex]}
</Suspense>
}
I want to know whether all three components will load on render, or just the one selected by selectionIndex. I'm trying to use this to conditionally select something to display based on a menu set by state, but I don't want to load everything at once.
They will not get rendered all at once. You can experiment by yourself, put console.log inside components is an easy way to find out.
React for web consists of two libs, "react" and "react-dom". "react" is in charge of encapsulating your logic intention into declarative data structures, while "react-dom" consumes these data structures and handles the actual "rendering" part of job.
The JSX element creation syntax <Component {…props} /> translates to plain JS as an API call to React.createElement(Component, props). The return value of this API call is actually just a plain object of certain shape that roughly looks like:
{
type: Component,
props: props
}
This is the aforementioned "declarative data structure". You can inspect it in console.
As you can see, calling React.createElement just return such data structure, it will not directly call the .render() method or functional component’s function body. The data structure is submitted to "react-dom" lib to be eventually "rendered".
So your example code just create those data structures, but the related component will not be rendered.
seems like its conditionally loaded based on selectionIndex. all the other components are not loaded at once.
P.S.: if you ever feel like which will get load first, just put a console log in that component and debug easily
conditionally load demo link - if you open this, the components are being loaded initially based on selectionIndex value being "one".
I'm not going to go into too much technical detail, because I feel like #hackape already provided you with a great answer as to why, point of my answer is just to explain how (to check it)
In general, I'd recommend you to download download the React Developer Tools
chrome link
firefox link
and then you can check which components are being rendered if you open the components tab inside your developer console. Here's a sandboxed example, best way to find out is to test it yourself afterall :-)
As you can see in the developer tools (bottom right), only the currently set element is being rendered
started with VueJs for the first time
yesterday and now I'm stuck..
I have a parent component who has child items that also has a child inside them (I call them grandchildren). I want to fetch data from all the grandchildren when i click a button in the parent but i can't figure out how.
In my mind a want to call an event from parent to to all the grandchildrens that they should store their data to vuex store. Is this possible somehow or is there another way to do this?
// Data
blocks = [
{
id: 1,
type: 'HeadingBlock',
title: 'Hello',
color: 'blue'
},
{
id: 2,
type: 'ImageBlock',
image_id: 2
}
];
// App.js
<ContentBlocks :blocks="blocks" / >
// ContentBlock.vue
<ContentBlockItem v-for="(block, index) in blocks" :component="block.type" ... />
// ContentBlockItem.vue
<component :is="component" :block="block" /> // Grandchild
// component aka the grandchild (eg. HeadingBlock.vue)
data() {
return {
title: 'Hello - I want save the changed data for this heading',
color: 'blue'
}
}
So, the only call to action happens in the parent by a "save"-button. And i want as little logic in grandchildren as possible (to make it easy to create new ones, like a "ParagraphBlock").
Thx in advance
It is possible to emit() a global Event that all your components subscribe to - however it seems rather impractical. (e.g. this.$root.emit('saveData') in parent; in all children to listen to it: this.$root.on('saveData'))
A more practical approach is to store all your component data in the store in the first place. And have each component retrieve its state from the store. (e.g. in a computed property: title() { return this.$store.myComponent.title }.
The trick here is obviously to set all your store-data correctly (e.g. with componentIDs to match it correctly). To do this you need to be aware, that vuex does not support maps or sets. Also, you have to set each property/element individually - you cannot set nested structures in one go bu thave to do it recursively. Hereby Arrays have to be filled with native array methods (push(), splice()) and Object properties have to be set with Vue.set(object, key, value).
For accessing data between parent and child component you can use one of the best features of vue is vuex store. It's really helpful when you want to pass data to child component and update that data in child and again pass back to parent without the use of props and event emit.
Here is a link you can follow for your web application
https://medium.com/dailyjs/mastering-vuex-zero-to-hero-e0ca1f421d45
https://medium.com/vue-mastery/vuex-explained-visually-f17c8c76d6c4
I hope this will help you.
I'd like to know if it's possible to avoid a sibling component rerendering when it's data hasn't changed.
I guess it's probably because of the way i have it structure so let me clarify what i have today.
I have a Smart component which has access to a state similar to this:
showcaseState = {
products: {
1: {
name: "Ball"
},
2: {
name: "Puzzle"
}
},
cart: [2]
}
And this Smart component renders two Dumb Components. The first one (ProductsList component) receives the list of products, and the second one (Cart component) gets a list that only contains the products that match the index(es) inside the cart property.
Whenever i click one product inside the ProductsList component, it fires an action that adds that product index to the cart, so the Cart component gets rerendered.
The thing is, the ProductsList is also getting rerendered, even though it's data didn't change at all. My guess is that it's happening because the Main Component rerenders. How do i avoid this? Should i make the ProductList component Smart, give it access to the products state and remove products from the Main component?
I'm a little bit lost regarding which is the best way to achieve this.
EDIT
The rendering of the Smart components is something like this
let { products, cart } = this.props.app
let cartProds = cart.map(prodId => { return products[prodId] })
<div>
<ProductsList prods={products} />
</div>
<div>
<Cart prods={cartProds} />
</div>
For anyone interested in this, I ended up using redux to manage the app state, and separating the things each component needed.
I know that it's not a default behaviour / feature of react-router to help us reload easily the current component but I really need this in my application.
My application deals with products. I have a product list that I can load, and when I click on an item, it displays the concerned product details.
On that page, I have related product links that load the same component, but with another product details, located at
<Route path="/products/:id/details" component={ProductDetail} />
I m fetching data in my componentWillMount, and it seems that if I only change the URL, a new component is NOT mounted, and so, I m always having my old data displayed, without fetching anything.
As a beginner using React, I'm looking for some help, or some tricks to reload the component concerned by that page. I mean being able to reload the ProductDetail with the good product.
I tried to look around with componentWillUpdate (a method in which I can see that the router URI changes :D) but I can't setState inside of it to make my component reload (it doesn't seem to be a good practice at all)
Any idea how can I make this work ?
EDIT : According to the first answer, I have to use onEnter. I m now stuck with the way of passing state/props to the concerned component :
const onEnterMethod = () => {
return fetch(URL)
.then(res => res.json())
.then(cmp => {
if (cmp.length === 1) {
// How to pass state / props to the next component ?
}
});
};
The way to handle it depends if you are using flux, redux or however you want to manage your actions. On top of it I would try to make use of onChange property of Route component (check React router docs):
<Route path="/products/:id/details" component={ProductDetail} onChange={someMethod} />
And then in the someMethod create the action if you are using redux or however is done in flux.
The redux would be:
<Route path="/products/:id/details" component={ProductDetail} onEnter={onEnterHandler(store)} />
And the onEnterHandler with the redux store:
function onEnterHandler(store) {
return (nextState, replace) => {
store.dispatch({
type: "CHANGEPRODUCT",
payload: nextState.params.id
})
};
}
And then in your ProductDetail component you would print the new information (It would require a bit more of learning in redux and redux-sagas libraries to complete that part).
Keep in mind that React is just the view part, trying to solve all those problems using only react is not only not recommended but also would mess up your code.
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0