I have created an RSS feed consuming react component. Currently it only displays the title of the first news item. I want to be able to map all of the post titles into the render.
https://codesandbox.io/s/react-rss-feed-xtt86 - This is where my code is located. You will have to use firefox with CORS add on for the post items to come through.
this.state = { news: [{}, {}] }; <-- This gets populated with the news items
<div className="panel-list">{this.state.news[1].title}</div> <!-- This is how it is being rendered onto the page at the minute.
I am new to react so sorry for the nooby question! Can't wait to get better.
Thank you all so much! Going to be pro with React in no time (;
You can simply use map to render multiple components from an array:
this.state = { news: [{}, {}] };
...
const news = this.state.news.map((newsItem) =>
<div key={newsItem.id} className="panel-list">{newsItem.title}</div>
);
Note that I added a key attribute to each div. This is important to give the elements a stable identity as explained here. It needs to be an unique identifier for each news item ("id" is used here as an example).
To render the news variable, you just need to use curly braces. For example, to render it in a div:
<div>{news}</div>
You could also render the list directly, without creating a variable (which is a bit messy, though):
<div>
{this.state.news.map((newsItem) =>
<div key={newsItem.id} className="panel-list">{newsItem.title}</div>
)}
</div>
You can do something like the example below
var news = [{
title: 'first title',
date: 'first news date'
},
{
title: 'second title',
date: 'second news date'
},
{
title: 'third title',
date: 'third news date'
}
]
const panelList = document.getElementById('panel-list')
news.map(item => {
panelList.innerHTML += `<div class="panel"><h2 class="panel-title"> ${item.title}</h2><span>${item.date}</span></div>`
})
<div id="panel-list">
</div>
Except the fact that in React you use JSX and you can make that map dirrectly in the render function.
The above pure javascript code would translate into :
<div className="panel-list" >
{this.state.news.map(item => (
<div className="panel" key={item.title}>
<h2 className="panel-title">{item.title}</h2>
<span>{item.date}</span>
</div>
))}
</div>
Also, like it has been mentioned in another comment, don't forget to add key attribute to each element inside the map ( each panel ) so React can identify the items correctly.
Related
New to Vue and JS. I have a vue page myLists which takes an array of lists (containing media IDs for a title) which I used to make axios API calls and build a carousel (using vue3-carousel package) in the child with the return data sent as a prop. I'm currently dealing with a "Maximum recursive updates exceeded in v-for component " warning that I believe has to do with how I make my API calls. Here is the relevant code below:
Parent "myLists", has multiple lists (each list has movies) and fetches data from api using axios:
<template>
<div v-if="isReady">
<List v-for="list of theList" :key="list.id" :aList="list"></List>
</div>
</template>
export default {
setup() {
const theList = ref([]);
for (var myList of myLists) {
const aList = ref([]);
for (var ids of myList) {
var apiLink = partLink + ids;
axios.get(apiLink).then((response) => {
aList.value.push({
title: response.data.original_title || response.data.name,
overview: response.data.overview,
url: "https://image.tmdb.org/t/p/w500" + response.data.poster_path,
year: response.data.first_air_date || response.data.release_date,
type: ids[1],
id: response.data.id,
});
});
}
theList.value.push(aList.value);
}
return { theList };
},
computed: {
isReady() {
//make sure all lists are loaded from the api before rendering
return this.theList.length == myLists.length;
},
},
Child component "List" (not including script tag as I don't think it's too relevant) takes the fetched data as a prop and builds a carousel with it:
<template>
<Carousel :itemsToShow="4.5" :wrapAround="true" :breakpoints="breakpoints">
<Slide v-for="slide of aList" :key="slide">
<img
#click="showDetails"
class="carousel__item"
:src="slide.url"
alt="link not working"
:id="slide"
/>
</Slide>
<template #addons>
<Navigation />
<Pagination />
</template>
</Carousel>
</template>
Don't know what's causing the error exactly. I have a feeling it could be how I do all my API calls, or maybe it's something else obvious. Anyone have a clue?
The fix was actually absurdly simple, I was on the right track. The component was trying to render before my data was ready, so simply adding a
"v-if="list.length!==0"
directly on my Carousel component (within my List component), as opposed to the parent, fixed my issue.
I assumed that props are automatically ready when passed to a child if I used a v-if in my parent, turns out I was wrong, and that there's a delay.
Found the solution on the github issues page:
https://github.com/ismail9k/vue3-carousel/issues/154
I have a parent and child Vue components. The child component supplies the parent component with data required to render the page using a simple object that is emitted using emit.
Child component data:
const Steps [
{
sequence: 1,
name: "Personal",
description: `<p>Enter your name and phone number</p>`,
},
{
sequence: 2,
name: "OTP",
description: `<p>An OTP code has been sent to you. Resend code</p>`,
},
]
const SelectedStep = ref( 0 ); // Which step did you select?
const ActiveStep = ref( {} ); // What step is the form currently on?
SelectedStep.value += 1; // e.g. SelectedStep.value === 2
// Get the object in Steps array where the sequence === 2
ActiveStep.value = Steps.find(step => {
return step.sequence === SelectedStep.value
})
// Send this to the parent to render the description and title
emit('SelectedStep', ActiveStep.value);
Depending on which sequence is selected, the object within Steps matching that sequence value will get loaded into ActiveStep. This is then emitted/supplied to the parent component.
However if you look at the object with sequence: 2 above, within the description is a resend code text. I need that to be a link with a binding so that when it is clicked a function is run to resend the code. I imagined something like this:
{
sequence: 2,
name: "OTP",
description: `<p>An OTP code has been sent to you. <a v-on:click="resendOTP">Resend code</a></p>`,
},
When that is rendered on the page, the v-on:click is not being interpreted and is rendered as-is in the HTML.
The parent component is just a view that uses this component:
<header>
<h1>{{ActiveStep.title}}</h1>
<div v-html="`${ActiveStep.description}`">{{ActiveStep.description}}</div>
</header>
<div>
<div class="content">
<Component-Signup v-on:SelectedStep="updateActiveStep"/>
</div>
</div>
<script>
import ComponentSignup from "../../components/Signup.vue"
export default {
components: {
"Component-Signup": ComponentSignup
},
setup() {
const ActiveStep = ref({});
function updateActiveStep(SelectedStep) {
ActiveStep.value = SelectedStep // SelectedStep is the object emitted from child component
}
return {
updateActiveStep,
ActiveStep
}
}
}
</script>
How could this be achieved?
First, your description contains HTML, so interpolation ({{ }}) will not display it as you expect ...it will be displayed encoded
v-html directive can be used to render raw HTML
BUT v-html is useful ONLY for HTML. Any Vue related functionality (as v-on) will not work. Docs:
Note that you cannot use v-html to compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.
Your only option is to create separate component for each step, and use is to display the right component for the current step...
I am trying to render empty component when my sections are empty. This is the sample code of mine
<SectionList
sections={[
{title: 'sectionOne', data: this.props.Activities.ActivityTypeOne},
{title: 'sectionTwo', data: this.props.Activities.ActivityTypeTwo},
{title: 'sectionThree', data: this.props.Activities.ActivityTypeThree}
]}
keyExtractor={ (item, index) => index }
stickySectionHeadersEnabled={true}
extraData={this.state}
ListEmptyComponent={this.renderEmptyScreens}
/>
But when this 3 all arrays are empty, it does not trigger the ListEmptyComponent
Can anyone tell me what is wrong with this code, or if my logic is incorrect can anyone please explain me.
Bacically I need to render some view when all 3 arrays are empty.
After running into this problem myself, I discovered that the sections property has to be completely empty for the list to be considered empty (aka, sections itself should equal to []).
Solution:
Use a ternary operator to choose what to pass into sections based on your own criteria. If it's decided to be empty, pass in [], else pass in your sections object. As an example, your code would look something like this:
isSectionsEmpty(sectionsObject){
//logic for deciding if sections object is empty goes here
}
render(){
/* ...
Other render code
...
*/
const sections = [
{title: 'sectionOne', data: this.props.Activities.ActivityTypeOne},
{title: 'sectionTwo', data: this.props.Activities.ActivityTypeTwo},
{title: 'sectionThree', data: this.props.Activities.ActivityTypeThree}
]
return(
...
<SectionList
sections={this.isSectionsEmpty(sections) ? [] : sections}
keyExtractor={ (item, index) => index }
stickySectionHeadersEnabled={true}
extraData={this.state}
ListEmptyComponent={this.renderEmptyScreens}
/>
...
)
}
Note: You could also forego the ternary operator and directly return either your sections object or [] from isSectionsEmpty, but this method allows for you to check if sections is "empty" elsewhere in your code, if needed.
You could add a section footer component in which you will render your empty screen based on section.data.length
For a little example see this
I'm trying to render components depending on the state of an array in the parent (App.vue). I'm not sure at all that this is the correct approach for this use case (new to Vue and not experienced programmer) so I will gladly take advice if you think this is not the right way to think about this.
I'm trying to build a troubleshooter that consists of a bunch of questions. Each question is a component with data that look something like this:
data: function() {
return {
id: 2,
question: "Has it worked before?",
answer: undefined,
requires: [
{
id: 1,
answer: "Yes"
}
]
}
}
This question is suppose to be displayed if the answer to question 1 was yes.
My problem is I'm not sure on how to render my components conditionally. Current approach is to send an event from the component when it was answered, and to listen to that event in the parent. When the event triggers, the parent updates an array that holds the "state" of all answered questions. Now I need to check this array from each component to see if there are questions there that have been answered and if the right conditions are met, show the question.
My question is: How can I check for data in the parent and show/hide my component depending on it? And also - is this a good idea or should I do something different?
Here is some more code for reference:
App.vue
<template>
<div id="app">
<div class="c-troubleshooter">
<one #changeAnswer="updateActiveQuestions"/>
<two #changeAnswer="updateActiveQuestions"/>
</div>
</div>
</template>
<script>
import one from './components/one.vue'
import two from './components/two.vue'
export default {
name: 'app',
components: {
one,
two
},
data: function() {
return {
activeQuestions: []
}
},
methods: {
updateActiveQuestions(event) {
let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
if ( index === -1 ) {
this.activeQuestions.push(event);
} else {
this.activeQuestions[index] = event;
}
}
}
}
</script>
two.vue
<template>
<div v-if="show">
<h3>{{ question }}</h3>
<div class="c-troubleshooter__section">
<div class="c-troubleshooter__input">
<input type="radio" id="question-2-a" name="question-2" value="ja" v-model="answer">
<label for="question-2-a">Ja</label>
</div>
<div class="c-troubleshooter__input">
<input type="radio" id="question-2-b" name="question-2" value="nej" v-model="answer">
<label for="question-2-b">Nej</label>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
id: 2,
question: "Bla bla bla?",
answer: undefined,
requires: [
{
id: 1,
answer: "Ja"
}
]
}
},
computed: {
show: function() {
// Check in parent to see if requirements are there, if so return true
return true;
}
},
watch: {
answer: function() {
this.$emit('changeAnswer', {
id: this.id,
question: this.question,
answer: this.answer
})
}
}
}
</script>
Rendering questions conditionally
as #Roy J suggests in comments, questions data probably belongs to the parent. It is the parent who handles all the data and who decides which questions should be rendered. However, there are plenty of strategies for this:
Display questions conditionally with v-if or v-show directly in the parent template:
Maybe the logic to display some questions is not at all generic. It can depend upon more things, user settings... I don't know. If that's the case, just render the questions conditionally directly in the parent, so you don't need to access the whole questions data in any question. Code should be something like the following:
<template>
<div id="app">
<div class="c-troubleshooter">
<one #changeAnswer="updateActiveQuestions" v-if="displayQuestion(1)"/>
<two #changeAnswer="updateActiveQuestions" v-if="displayQuestion(2)"/>
</div>
</div>
</template>
<script>
import one from './components/one.vue'
import two from './components/two.vue'
export default {
name: 'app',
components: {
one,
two
},
data: function() {
return {
activeQuestions: [],
}
},
methods: {
updateActiveQuestions(event) {
let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
if ( index === -1 ) {
this.activeQuestions.push(event);
} else {
this.activeQuestions[index] = event;
}
},
displayQuestion(index){
// logic...
}
},
}
</script>
Pass a reference to the previous question to every question:
If any question should be visible only when the previous question has been answered or viewed or something like that, you can pass that as a prop to every question, so they know wether they must render or not:
<template>
<div id="app">
<div class="c-troubleshooter">
<one #changeAnswer="updateActiveQuestions"/>
<two #changeAnswer="updateActiveQuestions" prev="activeQuestions[0]"/>
</div>
</div>
</template>
And in two.vue:
props: ['prev'],
computed: {
show: function() {
return this.prev && this.prev.status === 'ANSWERED';
// or some logic related to this, idk
}
},
just pass the whole data to the children:
As you coded it, you can just pass the whole questions data as a prop to every question component, then use it in a computed property. This is not what I would do, but just works, and since objects are references this is not necessarily unperformant.
Using a generic component:
It seems weird to have a one.vue, two.vue for every question, and sure does not scale well.
I'm not really sure how modular I can do them since the template for each question can be a bit different. Some have images or custom elements in them for example, while others don't.
If template are really different from each question to another, this can get complicated. However, if, as I suspect, they share common HTML structure, with a defined header or a common 'ask' button at the bottom and stuff like that, then you should be able to address this using Vue slots.
Apart from template issues, I suppose that every question in your app can get an arbitrary number of 'sub-questions' (as two.vue having question-2-a and question-2-b). This will require a complex and flexible data structure for the questions data (which will get more complex when you start to add multiple choices, multiple possible answers etc. etc.). This can get very complex but you should probably work on this until you can use a single question.vue component, this will surely pay out.
tip: avoid watchers
You're using v-model to answer in the two.vue template, then using a watcher to track changes in the answer variable and emit the event. This is convoluted and difficult to read, you can use #input or #change events on the <input> element instead:
<input type="radio" id="question-2-a" name="question-2" value="ja" v-model="answer" #input="emitAnswer">
And then instead of the watcher, have a method:
emitAnswer() {
this.$emit('changeAnswer', {
id: this.id,
question: this.question,
answer: this.answer
})
This is a pretty broad question, but I'll try to give some useful guidance.
First data should be used for internal state. Very often, a component should use props for things you might think would be data it owns. That is the case here: the questions need to be coordinated by the parent, so the parent should own the data. That allows you to make a sensible function to control whether a question component displays.
Having the parent own the data also allows you to make one question component that configures itself according to its props. Or you might have a few different question component types (you can use :is to select the right one), but almost certainly some of them are reusable if you pass their question/answer/other info in.
To update answers, you will emit changes from the question components and let the parent actually change the value. I use a settable computed to allow the use of v-model in the component.
new Vue({
el: '#app',
data() {
return {
questions: [{
id: 1,
question: 'blah 1?',
answer: null
},
{
id: 2,
question: 'blah 2?',
answer: null,
// this is bound because data is a function
show: () => {
const q1 = this.questions.find((q) => q.id === 1);
return Boolean(q1.answer);
}
},
{
id: 3,
question: 'Shows anyway?',
answer: null
}
]
};
},
components: {
questionComponent: {
template: '#question-template',
props: ['props'],
computed: {
answerProxy: {
get() {
return this.answer;
},
set(newValue) {
this.$emit('change', newValue);
}
}
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div class="c-troubleshooter">
<question-component v-for="q in questions" v-if="!q.show || q.show()" :props="q" #change="(v) => q.answer = v" :key="q.id">
</question-component>
</div>
<h2>State</h2>
<div v-for="q in questions" :key="q.id">
{{q.question}} {{q.answer}}
</div>
</div>
<template id="question-template">
<div>
{{props.question}}
<div class="c-troubleshooter__input">
<input type="radio" :id="`question-${props.id}-a`" :name="`question-${props.id}`" value="ja" v-model="answerProxy">
<label :for="`question-${props.id}-a`">Ja</label>
</div>
<div class="c-troubleshooter__input">
<input type="radio" :id="`question-${props.id}-b`" :name="`question-${props.id}`" value="nej" v-model="answerProxy">
<label :for="`question-${props.id}-b`">Nej</label>
</div>
</div>
</template>
I've been struggling this for a couple of days, trying to figure out the "react" way to do it.
Basically, I have a tree, a list of lists (of lists ...) that can be arbitrarily nested, and I want a component that will display this and also enable rearrangement.
Here's my data:
var data = [{
id: 1
}, {
id: 2, children: [
{
id: 3, children: [{id: 6}]
}, {
id: 4
}, {
id: 5
}]
}]
My first pass was to just have a single "tree" component that builds the nested lists of DOM elements in its render function (look at the code here). That actually worked pretty well for small numbers of elements, but I want to be able to support hundreds of elements, and there was a very high re-render cost when an element was moved within the tree (~600ms when there were a few hundred elements).
So I think I'll have each "node" of the tree be it's own instance of this component. But here's my question (sorry for the long intro):
Should each node dynamically query for the list it's children's IDs from a central "database" and store that in state? Or should the top-most node load the whole tree and pass everything down through props?
I'm still trying to wrap my mind around how state & props should be handled & divvied up.
Thanks
I wanted to try out the tree structure with React and came up with a simple component that hides subtrees when you click on <h5>. Everything is a TreeNode. Is this similar to what you were thinking?
You can see it in action in this JSFiddle: http://jsfiddle.net/ssorallen/XX8mw/
TreeNode.jsx:
var TreeNode = React.createClass({
getInitialState: function() {
return {
visible: true
};
},
render: function() {
var childNodes;
if (this.props.node.childNodes != null) {
childNodes = this.props.node.childNodes.map(function(node, index) {
return <li key={index}><TreeNode node={node} /></li>
});
}
var style = {};
if (!this.state.visible) {
style.display = "none";
}
return (
<div>
<h5 onClick={this.toggle}>
{this.props.node.title}
</h5>
<ul style={style}>
{childNodes}
</ul>
</div>
);
},
toggle: function() {
this.setState({visible: !this.state.visible});
}
});
bootstrap.jsx:
var tree = {
title: "howdy",
childNodes: [
{title: "bobby"},
{title: "suzie", childNodes: [
{title: "puppy", childNodes: [
{title: "dog house"}
]},
{title: "cherry tree"}
]}
]
};
React.render(
<TreeNode node={tree} />,
document.getElementById("tree")
);
Seems like it'd be nicer to pass everything down as props, as this will prevent you from the trouble of managing individual insertion/deletion. Also, like the comments said, the key attributes prevents a huge chunk of unnecessary re-rendering.
You might want to check this link: http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html. It describes the kind of dilemma you're having and how to approach it.
(Coincidentally, I've made a react tree view a while ago: https://github.com/chenglou/react-treeview. Take whatever you want from it!)
Here is a quick example of how to create a treeview using React and Flux.
http://www.syntaxsuccess.com/viewarticle/5510d81be1ce52d00e93da55
The React component is recursive and state is managed using Flux.