Filter functionality in a react component - javascript

I am building an apartment renting app in React, with very limited Javascript and programmatic knowledge (using this app to learn while i go). I've stumbled upon a big roadblock trying to build the filter functionality.
Here is the code for the FilterContainer.js container component
const FilterContainer = React.createClass({
render () {
return (
<section id="filterContainer" className="container">
<Filter
name={'Kvart'}
filterList={['Šubićevac', 'Meterize', 'Baldekin', 'Vidici', 'Rokići', 'Grad']} />
<Filter
name={'Cijena'}
filterList={['< 600kn', '600kn - 700kn', '700kn - 800kn', '800kn - 900kn', '> 900kn']} />
<Filter
name={'Broj osoba'}
filterList={['1', '2', '3', '4', '5', '6']} />
<Filter
name={'Kvadratura'}
filterList={['< 40m', '40m - 50m', '50m - 60m', '60m - 70m', '70m - 80m', '> 80m']} />
</section>
)
}});
This is the Filter.js component
var Filter = React.createClass({
getDefaultProps() {
return {
filterList: [],
name: ''
};
},
render() {
var handleClick = function(i, props) {
console.log('You clicked: ' + props.filterList[i].listValue[i]);
}
return (
<div className="filterCloud quarter-section">
<h3>{this.props.name}</h3>
<ul>
{this.props.filterList.map(function(listValue, i, props) {
return <li onClick={handleClick.bind(this, i, props)} key={i}>{listValue}</li>;
}, this)} {/* this at the end to fix the scope issue from global to local */}
</ul>
</div>
)
}});
This is how the Filter looks on the website atm
The error I am getting is "Filter.js:13 Uncaught TypeError: Cannot read property '0' of undefined" whenever i click on one of the filter items, I want to be able to save the name of the filter item as data which I will use to filter the apartments.
So the simple issue is the undefined error. The more complex issue i have is the fact that I have no idea how to build the filter functionality in my app, so if any of you guys could point me in the right direction it would help me alot.

The third parameter to the map function in javascript is the full array that is being mapped over.
In this case, it is the filterList, not this.props.
So in you click handler, you don't need to traverse props:
var handleClick = function(i, props) {
console.log('You clicked: ' + props[i].listValue[i]);
}
It would be easier to see if you did some renaming:
render() {
var handleClick = function(i, filterList) {
console.log('You clicked: ' + filterList[i].listValue[i]);
}
return (
<div className="filterCloud quarter-section">
<h3>{this.props.name}</h3>
<ul>
{this.props.filterList.map(function(listValue, i, filterList) {
return <li onClick={handleClick.bind(this, i, filterList)} key={i}>{listValue}</li>;
}, this)} {/* this at the end to fix the scope issue from global to local */}
</ul>
</div>
)
}});

Related

How can I grab the key of a list item generated from a map function?

So I am learning React, and I've tried searching for solutions to my problem both on stackoverflow and on React's own documentation, but I am still stumped.
Essentially, I have a list of 10 subreddits that is being mapped to list items in the form of the subredditsArray variable.
I render the results, and try to pass the selected item when I click that list item to my getSubredditInfo function. However, this doesn't work - event.target.key is undefined. (To clarify, I am looking to grab the key of the single list element that I have clicked).
When I try to just get event.target, I get the actual htmlElement (ex: <li>Dota2</li>), where as I want to get the key, or at least this value into a string somehow without the tags. I also tried putting my onClick method in the list tag of the map function, but that did not work.
Here is the relevant code:
//this is where I get my data
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(event){
//console.log(event.target.key); <-- DOESNT WORK
}
render() {
var subredditsArray = this.state.subreddits.map(function(subreddit){
return (<li key={subreddit.toString()}>{subreddit}</li>);
});
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul onClick={this.getSubredditInfo}>{subredditsArray}</ul>
</div>
);
}
My questions essentially boil down to:
How do I grab the key value from my list object?
Additionally, is there a better way to generate the list than I currently am?
Thank you in advance.
EDIT: Added my componentDidMount function in hopes it clarifies things a bit more.
try the following code:
class App extends React.Component {
constructor(props){
super(props);
this.state = {subreddits:[]};
}
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(subreddit){
console.log(subreddit);
}
render() {
return <div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>
{
this.state.subreddits.map((subreddit)=>{
return (<li key={subreddit.toString()} onClick={()=>this.getSubredditInfo(subreddit)}>{subreddit}</li>);
})
}
</ul>
</div>;
}
}
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
please check the onClick event handler now. its an arrow function and its calling the getSubredditInfo function with your subreddit now. so you will get it there.
so its basically different way of calling the handler to pass data to the handler.
it works as you expect it to.
You can use lamda function or make component for item list which have own value for getSubredditInfo function
getSubredditInfo(value) {}
render() {
var subredditsArray = this.state
.subreddits.map((subreddit, i) =>
(<li key={i}
onClick={() => this.getSubredditInfo(subreddit)}>{subreddit}</li>));
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
1) Key should be grabbed either by the id in your object in array. Or you can combine the 2 properties to create a unique key for react to handle re-renders in a better way.
If you have a string array, you may use a combination of string value + index to create a unique value, although using index is not encouraged.
Given a quick example for both below.
2) A better way could be to move your map function into another function and call that function in render function, which will return the required JSX. It will clean your render function.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
subredditsObjArray: [
{ id: 1, value: 'A'},
{ id: 2, value: 'B'},
{ id: 3, value: 'C'},
{ id: 4, value: 'D'}
],
subredditsArray: ['A', 'B', 'C', 'D'],
selectedValue: ''
};
}
getSubredditInfo = (subreddit) => {
console.log(subreddit)
this.setState({
selectedValue: ((subreddit && subreddit.id) ? subreddit.value : subreddit),
});
}
render() {
return (
<div className="redditResults">
<p>Selected Value: {this.state.selectedValue}</p>
<h1>Top {this.state.subredditsArray.length || '0'} subreddits for that topic</h1>
<p>With Objects Array</p>
<ul>
{
this.state.subredditsObjArray
&& this.state.subredditsObjArray.map(redditObj => {
return (<li key={redditObj.id}><button onClick={() => this.getSubredditInfo(redditObj)}>{redditObj.value || 'Not Found'}</button></li>);
})
}
</ul>
<br />
<p>With Strings Array</p>
<ul>
{
this.state.subredditsArray
&& this.state.subredditsArray.map((reddit, index) => {
return (<li key={reddit + '-' + index}><button onClick={() => this.getSubredditInfo(reddit)}>{reddit || 'Not Found'}</button></li>);
})
}
</ul>
</div>
);
}
}
ReactDOM.render(
<App etext="Edit" stext="Save" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Are you trying to do this? I'm not sure what you want to do.
getSubredditInfo(e, subreddit) {
console.log(subreddit)
}
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
this.getSubredditInfo(e, subreddit)
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
The key purpose is to pass your subreddit to the onClick function so you will receive the value while you click the item.
If you still get error try this and tell me what's happened.
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
console.log(subreddit.toString())
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}

How to use the condition map function in ReactJS?

I am trying to display bootstrap carousel via ajax call using react js. Ajax receives json data consisting image name, content title and some meta information of per slide what I want to inject in DOM. So, I use the map function to generate all slides. My problem is, for the first slide I want to add a class active. But I do not know how to use condition in map().
In React, I have written: (in SliderWidget class, I have written a comment actually where I should use active class conditionally)
var HomeCarousel = React.createClass({
getInitialState: function() {
return {
data: []
}
},
componentDidMount: function() {
$.get("/api/slider", function(result) {
this.setState({
data: result
});
}.bind(this));
},
render: function() {
return (
<div id={"myCarousel"} className={"carousel slide"} data-ride="carousel">
{this.state.data.map((slider, i) => <SliderWidget key = {i} data = {slider} />)}
</div>
);
}
});
class SliderWidget extends React.Component {
render() {
return (
<div className={"item active"}> // here I want to use active class for the first slide
<img className={"first-slide"} src="images/pexels.jpeg" alt="First slide" />
<div className={"container"}>
<div className={"carousel-caption"}>
<h3>Research Indicates Breakfast is the Most Important Meal</h3>
<p><a className={"btn btn-primary"} href="#" role="button">Find Out More</a></p>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<HomeCarousel />, document.getElementById('react-home-carousel')
);
The i in the map callback is the loop index, so pass a property accordingly:
this.state.data.map( (slider, i) =>
<SliderWidget key={i} data={slider} active={i===0} />
)
Then in SliderWidget:
render() {
return (
<div className={"item" + this.props.active ? ' active' : ''}>...
)
}
Using classnames will make your life even easier.

"Cannot read property 'props' of undefined" React Issue

I'm building an app in React based on this tutorial.
Instead of using the updated es2016, I'm using an older way, so I'm having some trouble with the challenges that come. I got this error in the browser: "TypeError: Cannot read property 'props' of undefined". I assume it's pointing to the {this.props.onDelete} part. Here's a snippet of my code for the Notes.jsx component:
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
);
}
)}
</ul>
);
}
});
And here's a snippet from App.jsx, it's parent:
var App = React.createClass({
getInitialState: function () {
return {
notes: [
{
id: uuid.v4(),
task: 'Learn React'
},
{
id: uuid.v4(),
task: 'Do laundry'
}
]
}
},
newNote: function () {
this.setState({
notes: this.state.notes.concat([{
id: uuid.v4(),
task: 'New task'
}])
});
},
deleteNote: function() {
return 'hi';
},
render: function () {
var {notes} = this.state;
return (
<div>
<button onClick={this.newNote}>+</button>
<Notes notes={notes} onDelete={this.deleteNote}/>
</div>
);
}
});
I deleted the actually useful parts from deleteNote to make sure no issues were there. I'm having a difficult time wrapping my head around using "this" and what the binding is doing in the tutorial I mentioned.
this inside the map function isn't the same as this outside of it because of how JS works.
You can save off this.props.onDelete and use it w/o the props reference:
render: function () {
var onDelete = this.props.onDelete;
return (
<ul>
{this.props.notes.map(
function(note) {
return (
<li key={note.id}>
<Note
onTheDelete={onDelete}
task={note.task}
/>
</li>
);
}
)}
</ul>
);
}
Unrelated, but I'd move that map function into its own function and avoid the deep nesting.
Dave Newton's answer is entirely correct, but I just wanted to add that if you use ES6 arrow functions then you can avoid having to keep an additional reference to this, as well as removing the return statement and taking advantage of the implicit return syntax.
var Notes = React.createClass({
render: function () {
return (
<ul>
{this.props.notes.map(
note => {(
<li key={note.id}>
<Note
onTheDelete={this.props.onDelete}
task={note.task} />
</li>
)}
)}
</ul>
);
}
});

React - Array Splice Trouble

Learning React. Attempting to make my own mini-app based very closely on what's done here: https://www.youtube.com/watch?v=-AbaV3nrw6E.
I'm having a problem with the deletion of comments in my app. I've looked in several other places for people having similar errors, but it seems the problem is within my own code (and yet I can find no errors). I've scoured the Babel file over and over, but to no avail.
Here are the specifics:
When you create a new comment, you have two options in the form of buttons: Save and Delete. After exactly one comment is written and you press "Save," the delete function works just fine. However, if there are three comments total (for example) and you click "delete" on the first one, the next comment (the second, in this case) is deleted.
Hopefully that makes some amount of sense.
Can you find my error? The math/logic behind the delete function is located on line 71 under the name "deleteComment."
Full Pen here.
var CommentSection = React.createClass({
getInitialState: function() {
return {editing: true}
},
edit: function() {
this.setState({editing: true});
},
save: function() {
this.props.updateCommentText(this.refs.newText.value, this.props.index);
this.setState({editing: false});
},
delete: function() {
this.props.deleteFromCard(this.props.index);
},
renderNormal: function() {
return (
<div className="comment-section">
<div className="comment-content">{this.props.children}</div>
<a className="-edit" onClick={this.edit}>Edit</a>
</div>
);
},
renderEdit: function() {
return (
<div className="comment-section">
<textarea ref="newText" defaultValue={this.props.children}></textarea>
<button className="-save" onClick={this.save}>Save</button>
<button className="-delete" onClick={this.delete}>Delete</button>
</div>
);
},
render: function() {
if(this.state.editing) {
return this.renderEdit();
} else {
return this.renderNormal();
}
}
});
var PhotoSection = React.createClass({
render: function() {
return <div className="photo-section"></div>;
}
});
var Desk = React.createClass({
getInitialState: function() {
return {
comments: []
}
},
addComment: function(text) {
var arr = this.state.comments;
arr.push(text);
this.setState({comments: arr})
},
deleteComment: function(i) {
console.log(i);
var arr = this.state.comments;
arr.splice(i, 1);
this.setState({comments: arr})
},
updateComment: function(newText, i) {
var arr = this.state.comments;
arr[i] = newText;
this.setState({comments: arr})
},
commentFormat: function(text, i) {
return (
<CommentSection key={i} index={i} updateCommentText={this.updateComment} deleteFromCard={this.deleteComment}>
{text}
</CommentSection>
);
},
render: function() {
return (
<div className="desk">
<div className="card">
<PhotoSection />
<div className="comment-section-backing">
<button className="-comment" onClick={this.addComment.bind(null, "")}>Leave a Comment</button>
{this.state.comments.map(this.commentFormat)}
</div>
</div>
</div>
);
}
});
ReactDOM.render(<Desk />, document.getElementById('app'));
Your problem stems from using the index as keys:
https://facebook.github.io/react/docs/lists-and-keys.html#keys
When you delete an item from your array, the array in the state is correctly updated. However, when the array is rendered, they keys will all be the same regardless of which element you deleted except there will be one less.
At this point the reconciliation happens and your components are rerendered. However you have an (uncontrolled) textarea in each component that holds its own internal state. The uncontrolled textarea component does get it's default (initial) value from the children prop but is otherwise unaffected by changes to that value. Therefore the re-rendering of the components with new values for text do not change the values in those textarea instances.
If the keys for the components in the mapped components were not linked to the index, the correct component would be removed.
edit: The code in the pen has changed slightly where there are two different render branches (editing, normal). Since the normal rendering doesn't use the uncontrolled textarea inputs the pen no longer exhibits the aberrant behavior.
There was an issue with using this.props.children when rendering the CommentSection component
Changing the code to use a prop:
return (
<div className="comment-section">
<div className="comment-content">{this.props.commentText}</div>
<a className="-edit" onClick={this.edit}>Edit</a>
<button className="-delete" onClick={this.delete}>Delete</button>
</div>
);
and setting this in the commentFormat functiion in the container:
commentFormat: function(text, i) {
return (
<CommentSection
key={i}
index={i}
updateCommentText={this.updateComment}
deleteFromCard={this.deleteComment}
commentText={text}>
</CommentSection>
);
}
appears to work.
CodePen
Try with Array.filter.
deleteComment: function(i) {
var arr = this.state.comments.filter(function(comment) {
return comment.index !== i;
});
this.setState({comments: arr});
},

Rendering an array.map() in React

I am having a problem where I am trying to use array of data to render a <ul> element. In the code below the console logs are working fine, but the list items aren't appearing.
var Main = React.createClass({
getInitialState: function(){
return {
data: dataRecent
}
},
render: function(){
return (
<div>
<ul>
{
this.state.data.map(function(item, i){
console.log('test');
<li>Test</li>
})
}
</ul>
</div>
)
}
});
ReactDOM.render(<Main />, document.getElementById('app'));
What am I doing wrong? Please feel free to point out anything that isn't best practice.
Gosha Arinich is right, you should return your <li> element.
But, nevertheless, you should get nasty red warning in the browser console in this case
Each child in an array or iterator should have a unique "key" prop.
so, you need to add "key" to your list:
this.state.data.map(function(item, i){
console.log('test');
return <li key={i}>Test</li>
})
or drop the console.log() and do a beautiful oneliner, using es6 arrow functions:
this.state.data.map((item,i) => <li key={i}>Test</li>)
IMPORTANT UPDATE:
The answer above is solving the current problem, but as Sergey mentioned in the comments: using the key depending on the map index is BAD if you want to do some filtering and sorting. In that case use the item.id if id already there, or just generate unique ids for it.
You are not returning. Change to
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>;
})
let durationBody = duration.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
});
Using Stateless Functional Component We will not be using this.state. Like this
{data1.map((item,key)=>
{ return
<tr key={key}>
<td>{item.heading}</td>
<td>{item.date}</td>
<td>{item.status}</td>
</tr>
})}
You are implicitly returning undefined. You need to return the element.
this.state.data.map(function(item, i){
console.log('test');
return <li>Test</li>
})
Best Answer:
import React, { useState } from 'react';
import './App.css';
function App() {
// Array of objects containing our fruit data
let fruits = [
{ label: "Apple", value: "🍎" },
{ label: "Banana", value: "🍌" },
{ label: "Orange", value: "🍊" }
]
// Using state to keep track of what the selected fruit is
let [fruit, setFruit] = useState("⬇️ Select a fruit ⬇️")
// Using this function to update the state of fruit
// whenever a new option is selected from the dropdown
let handleFruitChange = (e) => {
setFruit(e.target.value)
}
return (
<div className="App">
{/* Displaying the value of fruit */}
{fruit}
<br />
<select onChange={handleFruitChange}>
{
fruits.map((fruit) => <option value={fruit.value}>{fruit.label}</option>)
}
</select>
</div>
);
}
export default App;
Add up to Dmitry's answer, if you don't want to handle unique key IDs manually, you can use React.Children.toArray as proposed in the React documentation
React.Children.toArray
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Note:
React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.
<div>
<ul>
{
React.Children.toArray(
this.state.data.map((item, i) => <li>Test</li>)
)
}
</ul>
</div>
I've come cross an issue with the implementation of this solution.
If you have a custom component you want to iterate through and you want to share the state it will not be available as the .map() scope does not recognize the general state() scope.
I've come to this solution:
`
class RootComponent extends Component() {
constructor(props) {
....
this.customFunction.bind(this);
this.state = {thisWorks: false}
this.that = this;
}
componentDidMount() {
....
}
render() {
let array = this.thatstate.map(() => {
<CustomComponent that={this.that} customFunction={this.customFunction}/>
});
}
customFunction() {
this.that.setState({thisWorks: true})
}
}
class CustomComponent extend Component {
render() {
return <Button onClick={() => {this.props.customFunction()}}
}
}
In constructor bind without this.that
Every use of any function/method inside the root component should be used with this.that
Dmitry Brin's answer worked for me, with one caveat. In my case, I needed to run a function between the list tags, which requires nested JSX braces. Example JSX below, which worked for me:
{this.props.data().map(function (object, i) { return <li>{JSON.stringify(object)}</li> })}
If you don't use nested JSX braces, for example:
{this.props.data().map(function (object, i) { return <li>JSON.stringify(object)</li>})}
then you get a list of "JSON.stringify(object)" in your HTML output, which probably isn't what you want.
import React, { Component } from 'react';
class Result extends Component {
render() {
if(this.props.resultsfood.status=='found'){
var foodlist = this.props.resultsfood.items.map(name=>{
return (
<div className="row" key={name.id} >
<div className="list-group">
<a href="#" className="list-group-item list-group-item-action disabled">
<span className="badge badge-info"><h6> {name.item}</h6></span>
<span className="badge badge-danger"><h6> Rs.{name.price}/=</h6></span>
</a>
<a href="#" className="list-group-item list-group-item-action disabled">
<div className="alert alert-dismissible alert-secondary">
<strong>{name.description}</strong>
</div>
</a>
<div className="form-group">
<label className="col-form-label col-form-label-sm" htmlFor="inputSmall">Quantitiy</label>
<input className="form-control form-control-sm" placeholder="unit/kg" type="text" ref="qty"/>
<div> <button type="button" className="btn btn-success"
onClick={()=>{this.props.savelist(name.item,name.price);
this.props.pricelist(name.price);
this.props.quntylist(this.refs.qty.value);
}
}>ADD Cart</button>
</div>
<br/>
</div>
</div>
</div>
)
})
}
return (
<ul>
{foodlist}
</ul>
)
}
}
export default Result;

Categories

Resources