Multiple conditional rendering - reactjs

im doing a TODO list and in this tag P i would like render the 3 differents type of priority
what im doing wrong return always Low
<p className="text-todo italic">
Priority: {value.priority === 1 ? (
<span>
hight
</>
) : value.priority === 2 ? (
<span>
Medium
</span>
) : (
<span>
Low
</span>
)}

use switch case
const todoType = (priority) => {
switch (priority) {
case 1:
return (
<span>high</span>
);
case 2:
return (
<span>Medium</span>
);
default:
return <span>Low</span>;
}
}
in component
<p className="text-todo italic">
{todoType(value.priority)}
</p>

Assuming you want to render a list of elements in an Array of objects (in this case TODO items) in order of priority on a page, you would need to first order your list and then sort it.
Something like this:
const unorderedArray = [
{ priority: 2, todo: 'finish homework', state: 'active' },
{ priority: 1, todo: 'answer this SO question', state: 'completed' }
]
const orderedArray = unorderedArray.sort((a, b) => {
return a.priority - b.priority
})
console.log(orderedArray)
Variables a and b stand for items in the list, in this case item 1 and item 2. We are then asking to compare based on the property priority
You can read more on the sort function on w3schools site here.

Related

Adding Selected Option Prices for Checkboxes - Using Svelte

I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total?
<script>
let toylist = [];
let toynames = [
'Toy1 5.00',
'Toy2 5.00',
'Toy3 1.00',
];
function join(toylist) {
return toylist.join(', ');
}
</script>
{#each toynames as toy}
<label>
<input type=checkbox bind:group={toylist} value={toy}> {toy}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist.join(', ')} and your total is
</p>
{/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input.
In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values.
Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with
<script>
let toylist = [
{name: "Toy", value: 5, selected: false},
{name: "Elephant", value: 6, selected: false},
{name: "Choo-Choo", value: 1, selected: false}
]
$: total = {
names: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
</script>
{#each toylist as {name,value, selected}}
<label>
<input type=checkbox bind:checked={selected} bind:value={value}> {name}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values}
</p>
{/if}
EDIT:
Here is the total function with a more classic syntax:
$: total = {
names: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
And here without the ? operator:
<script>
function renderNames() {
if (total.names.length < 1) {
return "nothing";
} else {
return total.names;
}
}
</script>
<p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices.
I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want.
<script>
let toylist = [];
let toys = [
{id: 1, name: 'Toy 1', price: 5.00, selected: false},
{id: 2, name: 'Toy 2', price: 5.00, selected: false},
{id: 3, name: 'Toy 3', price: 1.00, selected: false}
];
let total = 0.00
function join(toy) {
if(toy.selected === false) {
toylist = [...toylist, toy.name]
total = total+toy.price
let i = toys.findIndex(t => t.id === toy.id)
toys[i].selected = true
toys = toys
} else {
total = total-toy.price
let i = toys.findIndex(t => t.id === toy.id)
let i2 = toylist.findIndex(t => t === toy.name)
toylist.splice(i2, 1)
toylist = toylist
toys[i].selected = false
toys = toys
}
}
</script>
{#each toys as toy}
<label>
{toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button>
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist} and your total is ${total}
</p>
{/if}

Inner Map function not working - ReactJS

I want to display 8 sports in a list and display it's status participated when user already participated. Display a button when user not participated.But i didn't succeed it with the inner map function
{this.state.sports.map(function(sport, index){
return(
<div>
<li key={index} style={{ listStyle:'none' }}>
<h4>{ sport.name }</h4>
</li>
{this.state.join_sports.map(function(sport1, index){
return (
<div>
{sport._id === sport1.s_id ?
<span>Participated</span> :
<button className="btn btn-info btn-xs">Participate</button>
}
</div>
)
}.bind(this))}
</div>
);
}.bind(this))}
I'm going to take a stab at this and simplify it with some ES6 syntax and some pseudo code markup
I think your map function looks like this:
mapSportsToUser() {
const sports = ['a', 'b', ... 'z']//pseudo data
const user = ['b', 'c'] //we should only show sport c and c
return (
<container> //not needed in 15.5+ I think
{ sports.map( (sport, sportIndex) => ( //this will generate one title for each sport
<title>{sport.name}</title>
//your second map iterates over the list...
//(probably not the easiest way to do this but it should work)
{ user.map( (sportUser, userIndex) => {
if (sportUser == sport) {
return <participate />
} else {
return null //don't render anything
}
} }
)) }
</container>
)
}
I think you're making this really complex by looping over the everything rather than building the data in a way that makes sense from the start.
You might consider something like this:
structureData(listOfSports, listUserHasParticipatedIn) {
let sportsLookup = {} //create a map
for(let i = 0; i < listOfSports.length; i++) {//or whatever iterator you like
const sport = listOfSports[i]
sportsLookup[sport.id] = {
sport: sport,
participation: false,
}
}
for(let i = 0; i < listUserHasParticipatedIn.length; i++) {
const user_sport = listUserHasParticipatedIn[i]
//you might want to do a null check here
sportsLookup[user_sport.id].participation = true;
}
return sportsLookup
}
Then you have something easier to map which looks like:
[{ sport: 'a', participation: false}, { sport: 'b', participation: true} ...]
....
sportsLookup.map( lookup => { //much simpler
return <container> {lookup.sport.name} {lookup.participation ? 'Yes' : 'No'}</container>
})
I got succeeded in the inner map looping technique. Just take a variable make it false initially and make it true when the condition is true. That's all. Thank you.
{ this.state.sports.map((sport,index)=>{
let is_participate = false;
this.state.join_sports.map((j_spt,index)=>{
if(sport._id === j_spt.s_id[0]){
is_participate = true;
}
});
return(
<li>{is_participate? <span>{sport.name}-- participated</span>:<span>{sport.name}-- participate </span>}</li>
)
})
}

How can I concatenate strings for a React key?

I am creating a list of list and want to put a unique key for each element. When I use the React Dev Tool, the new key is "2016-10,-,football".
Why does it have commas in it?
What is the correct way to specify a key when I want "2016-10-football"?
React Dev Tool Console
import React from 'react'
import ReactDOM from 'react-dom'
const dates = ['2016-10', '2016-11', '2016-12'];
const sports = ['football', 'baseball', 'basketball'];
const Dates = ( { dates, sports } ) => {
return (
<ul>
{ dates.map( date => {
return (
<div key={date.toString()} >
<li>{date}</li>
<Sports sports={sports} date={date}/>
</div>
)
})
}
</ul>
)
}
const Sports = ( { date, sports } ) => {
return(
<ul>
{ sports.map( sport => {
// Results in: key="2016-10,-,football"
// Expected: key="2016-10-football"
return (<li key={[date, '-', sport]} >{sport}</li>)
})}
</ul>
)
}
ReactDOM.render(<Dates dates={dates} sports={sports}/>, document.getElementById('main'))
key expects a string so when you pass an array you are calling the Array's .toString() function. You will see the same result if you do console.log([date, '-', sport].toString())
Replace [date, '-', sport] with date + '-' + sport to fix it.
It's showing with commas because toString will use commas to join the array.
This is what you have:
arr = ['2016-10', '-', 'football']
console.log(arr.toString); // "2016-10,-,football"
This is what you want:
arr = ['2016-10', '-', 'football']
console.log(arr.join()); // "2016-10-football"
So consider replacing the li to (notice the .join()):
return (<li key={[date, '-', sport].join()} >{sport}</li>)
edit: use join("") for expected result, you should pass a separator (in this case an empty string) to arguments of the method. For example, ['2016-10', '-', 'football'].join('~separator~') would return "2016-10~separator~-~separator~football"
Added some examples for better understanding
key={'company_'+index} // key={date +'-'+sport}
<TableCell key={'company_'+index} align="right">
{row.company?.name}
</TableCell>
return(
<ul>
{ sports.map( sport => {
// Results in: key="2016-10,-,football"
// Expected: key="2016-10-football"
return (<li key={date +'-'+sport} >{sport}</li>)
})}
</ul>
)
I had no problem using a plus sign to concatenate two fields to make a unique key:
{rows.map((Group) => (
<li key={Group.user_id + Group.lgroup_id}>
-- Display the parts of the Group object here. --
</li>
))}

React multiple input (array of inputs) not working

So I'm attempting to render multiple input fields with React.
Everything looks fine until I remove an item. Always the last item is being "removed". If you want to try my code, write "A" in input field 1, "B" in 2, "C" in 3 and remove "B". You'll notice that you have removed "C" instead.
I have tried both value and defaultValue for input to no avail. I have also tried giving a name to the input. I think I am missing a key point here.
Any recommendations?
var MultiInput = React.createClass({
getInitialState: function() {
value = this.props.value
// force at least one element
if (!value || value == '') {
value = [ null ]
}
return {
value: value
}
},
getDefaultProps: function() {
return {
}
},
add_more: function() {
new_val = this.state.value.concat([])
new_val.push(null)
this.setState({ value: new_val })
},
remove_item: function(e, i) {
new_state = this.state.value.concat([])
new_state.splice(i,1)
this.setState({ value: new_state })
},
render: function() {
me = this
// console.log(this.state.value)
lines = this.state.value.map( function(e, i) {
return (
<div key={i}>
<input value={e} />
<button onClick={me.remove_item} >X</button>
</div>
)
})
return (
<div>
{lines}
<button onClick={this.add_more}>Add More</button>
</div>
)
}
})
There are a few things going on here.
To start, you shouldn't use the array index as the key when rendering in an array:
lines = this.state.value.map( function(e, i) {
return (
<div key={i}>
<input value={e} />
<button onClick={me.remove_item} >X</button>
</div>
)
})
The first time through, ["A", "B", "C"] renders:
<div key={0}>
...
</div>
<div key={1}>
...
</div>
<div key={2}>
...
</div>
Then, the second time, once you've removed "B" and left ["A", "C"], it renders the following:
<div key={0}>
...
</div>
<div key={1}>
...
</div>
So, when you removed item at index 1, the item previous at index 2 moves to index 1. You'll want to use some unique value that doesn't change when the position in the array changes.
Second, you should use the empty string instead of null for initialization, and then you'll see that you can't type anything in your inputs. That's because value ensures that an input's value is always whatever you pass it; you'd have to attach an onChange handler to allow the value to be edited.
Changing to defaultValue allows you to type in the box, but when you type, the string in this.state.value doesn't get updated--you'd still need an onChange handler.
Finally, your button has an onClick of this.remove_item, but your remove_item method seems to take the event and index as parameters. However, React will not pass the current index to remove_item; you would need to create a new function that passes the correct params:
onClick={me.remove_item.bind(null, i)}
That said, you really shouldn't call Function#bind inside render as you'll create new functions every time it runs.
Working Code
#BinaryMuse clearly explains why my code above doesn't work: by removing an item from the array and render is called again, the items change position and apparently React's algorithm picks the "wrong changes" because the key we're providing has changed.
I think the simplest way around this is to not remove the item from the array but rather replace it with undefined. The array would keep growing with this solution but I don't think the number of actions would slow this down too much, especially that generating a unique id that doesn't change might involve storing this ID as well.
Here's the working code: (If you wish to optimize it, please check #BinaryMuse's suggestions in the accepted answer. My MultInput uses a custom Input component that is too large to paste here =) )
var MultiInput = React.createClass({
getInitialState: function() {
value = this.props.value
if (!value || value == '') {
value = [ '' ]
}
return {
value: value
}
},
getDefaultProps: function() {
return {
}
},
add_more: function() {
new_val = this.state.value.concat([])
new_val.push('')
this.setState({ value: new_val })
},
remove_item: function(i,e) {
new_state = this.state.value.concat([])
new_state[i] = undefined
this.setState({ value: new_state })
},
render: function() {
me = this
lines = this.state.value.map( function(e, i) {
if (e == undefined) {
return null
}
return (
<div key={i}>
<input defaultValue={e} />
<button onClick={me.remove_item.bind(null, i)} >X</button>
</div>
)
}).filter( function(e) {
return e != undefined
})
return (
<div>
{lines}
<button onClick={this.add_more}>Add More</button>
</div>
)
}
})

AngularJS cant get category "All" while filtering

I started to play with angular and I am trying to write a simple app that consists of categories containing items. ( I am trying to implement a tutorial for my needs )
Now I am trying to add a filter to select items by categories. I can filter them unless I choose All categories. I cant get all the categories.
I have edges service :
angular.module('swFrontApp')
.controller('EdgesController', function ($scope, edges,categories) {
$scope.edges = edges.query();
$scope.categories = categories.query();
$scope.filterBy = {
search: '',
category: $scope.categories[0]
};
var selectedEdge = null;
$scope.selectEdge = function(edge) {
selectedEdge = (selectedEdge === edge) ? null : edge;
};
$scope.isSelected = function(edge) {
return edge === selectedEdge;
};
$scope.displayRequirements = function(reqs) {
var result = '';
for ( var i = 0; i < reqs.length; i ++) {
if (result !== '' ) { result += ', '}
if (reqs[i].name) {
result += reqs[i].name+ ' ';
}
result += reqs[i].value;
}
return result;
};
});
and I try to filter them using :
angular.module('swFrontApp').filter('edges', function() {
return function(edges, filterBy) {
return edges.filter( function( element, index, array ) {
return element.category.name === filterBy.category.name;
});
};
} );
Here is my html to get edges with categories filter
<select
name="category"
ng-model="filterBy.category"
ng-options="c.name for c in categories"
class="form-control"></select>
<ul>
<li ng-repeat-start="edge in edges | filter:{name: filterBy.search}| edges: filterBy " ng-click="selectEdge(edge)">
<span class="label label-default">{{ edge.category.name }}</span>
{{edge.name}}
<span class="text-muted">({{ displayRequirements(edge.requirements) }})</span>
</li>
<li ng-repeat-end ng-show="isSelected(edge)">
{{edge.description}}
</li>
</ul>
I formed My Plunker link is here.
Thanks
It doesn't work because of the category.name attribute. In your categoriesService.js you return collection where name equals to All. But if you look into EdgesService file, you'll see that there is no such option as 'All'. So this comparison in script.js file (in your filter)
return element.category.name === filterBy.category.name;
will always return false when filterby.category.name equals to 'All'.
The way to fix it is to change it to something like this:
return element.category.name === filterBy.category.name || filterBy.category.name === 'All';
This way it will always return true if 'All' category is selected.
Also later in the course rank option will be introduced as well. You can browse the code for that project here: https://github.com/Remchi/sw-front
Hope that helps. :)

Resources