How to perform multiple filtering? - reactjs

I am working on Filters which are based on categories. For the single category it's working, but how can I implement it for multiple category selections?
Example: If the user clicks on 'clothing' and 'sport', he should be able to see the list of both categories.
Redux state:
categories
>0 :{id:999 , name:'All', slug:'all'}
>1 :{id:2 , name:'clothing', slug:'clothing'}
>2 :{id:1 , name:'sport', slug:'sport'}
class ListFilter extends React.Component {
changeFilter = (category) => {
this.props.changeFilter(category, this.props.text);
gaEvent("Home - ListFilter", category, this.props.text);
};
clearFilters = () => {
this.props.changeFilter('all', '');
gaEvent("Home - ListFilter", "Reset");
};
render() {
return (
<>
<div className={classNames({
"search_list__filters": true,
"search_list--show": this.props.search
})}>
{this.props.categories.map((category, index) => {
return (
<Form.Group key={index} className="search_filters" >
<Form.Check onClick={(event)=>(event.target.checked!==true)?this.clearFilters():this.changeFilter(category.slug)} custom inline label={category.name} className='search_list__btn' type='checkbox' id={category.name} />
</Form.Group>
)
})}
<Row className="search_list_btn search_list__clear ">
<Col className="clear_wrapper">
{this.props.filters &&
<button className="clear_btn" onClick={this.clearFilters} >
Clear all filters
</button>
}
</Col>
</Row>
</div>
</>
);
}
}
const mapStateToProps = state => {
return state.Store
}
;
const mapDispatchToProps = dispatch => ({
changeFilter: (category, text) => dispatch(changeFilter(category, text))
});
export default connect(mapStateToProps, mapDispatchToProps)(ListFilter);

Currently you are dispatching the changeFilter event with single category. You can store the Filters in State and dispatch the event with array of Categories. Refer the CodeSandbox for working with multiple categories filters.
class ListFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: []
};
}
changeFilter = category => {
const { filters } = this.state;
const updatedFilter = [...filters, category];
this.setState({
filters: updatedFilter
});
this.props.changeFilter(updatedFilter, "testText");
};
render() {
console.log(this.state.filters);
return (
<div className="App">
{categories.map((category, index) => {
return (
<Form.Group key={index} className="search_filters">
<Form.Check
onClick={event =>
event.target.checked !== true
? this.clearFilters()
: this.changeFilter(category.slug)
}
custom
inline
label={category.name}
className="search_list__btn"
type="checkbox"
id={category.name}
/>
</Form.Group>
);
})}
</div>
);
}
}

Related

How to get value of inputs onChange when they are inside map function

I would like to get the values of some input on change when they are inside a map loop function. This is my component:
import React, { Component } from "react";
class ArrayComponent extends Component {
constructor(props) {
super(props);
this.state = { objects: [] };
}
handleChange(index, id, e) {
// what to put here ?
// want to have a state like :
// [{index: e.target.value, key: id}, {index: e.target.value, key: id}, ...]
}
render() {
return (
<Form onSubmit={this.onSubmit}>
{items.map((item, index) => (
<div>
{item.name}
<input
key={index}
value={this.state.objects[index]}
onChange={this.handleChange.bind(this, index, item.id)}
/>
</div>
))}
<Button>
Update
</Button>
</Form>
);
}
}
export default ArrayComponent;
I want to have a state like :
[{index: e.target.value, id: id}, {index: e.target.value, id: id}, ...] It means that If they are four inputs I want to have a state like above for four inputs.
Are you looking for something like this:
constructor(props) {
super(props);
this.state = { objects: {} };
}
handleChange(event, index, id) {
this.setState((state) => {
const newObject = {...state.objects};
newObject[`${index}`] = {value: event.target.value, key: id}
return {objects: newObject }
});
}
render() {
return (
<Form onSubmit={this.onSubmit}>
{items.map((item, index) => (
<div>
{item.name}
<input
key={item.id}
value={this.state.objects[`${index}`]?.value || ''}
onChange={(event) => this.handleChange(event, index, item.id)}
/>
</div>
))}
<Button>
Update
</Button>
</Form>
);
}
}
You should avoid setting map index values as component keys. So I removed index and just used the item id as the key prop.
Edit - Removed index
You can remove index all together:
handleChange(event, id) {
this.setState((state) => {
const newObject = {...state.objects};
newObject[`${id}`] = {value: event.target.value, key: id}
return {objects: newObject }
});
}
//........
value={this.state.objects[`${item.id}`]?.value || ''}
onChange={(event) => this.handleChange(event, item.id)}

Comment reply system with React Quill and Firebase Firestore

I'm making a comment system with React Quill as my editor and Firebase Firestore. Each comment post gets stored in firestore. Each stored comment has a reply button, and when clicked, the editor should be populated with the comment content I want to reply to. Basically I need to populate my editor with the content stored in firestore database. Here's a screenshot as to watch I want to achieve:
Comment reply
Here's some code from the comment editor component
class NewComment extends Component {
constructor(props) {
super(props);
this.state = {
comment: {
commentID: "",
content: "",
createDate: new Date(),
featureImage: "",
isPublish: "True",
createUserID: "",
},
};
}
...
onChangeCommentContent = (value) => {
this.setState({
comment: {
...this.state.comment,
content: value,
},
});
};
...
render() {
return (
<Container>
<Row>
<Col xl={9} lg={8} md={8} sn={12}>
<h2 className={classes.SectionTitle}>Comment</h2>
<FormGroup>
<ReactQuill
ref={(el) => (this.quill = el)}
value={this.state.comment.content}
onChange={(e) => this.onChangeCommentContent(e)}
theme="snow"
modules={this.modules}
formats={this.formats}
placeholder={"Enter your comment"}
/>
</FormGroup>
</Col>...
The reply button is in a different component where I render the stored comments. Tell me if you need the full code from the components.
Here is a simple example on how to pass on information between two components via the parent component using function components:
// Index.js
const MyComponent = () => {
const [replyValue, setReplyValue] = useState("");
const onClick = (value) => {
setReplyValue(value);
};
return (
<>
<Comment value="This is a reply" onClick={onClick} />
<Comment value="This is another reply" onClick={onClick} />
<CreateReply quoteValue={replyValue} />
</>
);
};
// Comment.js
export const Comment = ({ value, onClick }) => {
return (
<div className="comment" onClick={() => onClick(value)}>
{value}
</div>
);
};
// CreateReply.js
export const CreateReply = ({ quoteValue = "" }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(quoteValue);
}, [quoteValue]);
const onValueUpdated = (newValue) => {
if (newValue !== value) {
setValue(newValue);
}
};
return (
<>
<ReactQuill value={value} onChange={onValueUpdated} />
</>
);
};
Here is the same example using class components:
// Index.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
replyValue: ""
};
}
onClick = (value) => {
this.setState({
replyValue: value
});
};
render() {
return (
<>
<Comment value="This is a reply" onClick={this.onClick} />
<Comment value="This is another reply" onClick={this.onClick} />
<CreateReply quoteValue={this.state.replyValue} />
</>
);
}
}
// Comment.js
export class Comment extends React.Component {
render() {
return (
<div
className="comment"
onClick={() => this.props.onClick(this.props.value)}
>
{this.props.value}
</div>
);
}
}
// CreateReply.js
export class CreateReply extends React.Component {
constructor(props) {
super(props);
this.onValueUpdated = this.onValueUpdated.bind(this);
this.state = {
value: props.quoteValue
};
}
componentDidUpdate(prevProps) {
if (this.props.quoteValue !== prevProps.quoteValue) {
this.setState({
value: this.props.quoteValue
});
}
}
onValueUpdated = (newValue) => {
if (newValue !== this.state.value) {
this.setState({
value: newValue
});
}
};
render() {
return (
<>
<ReactQuill value={this.state.value} onChange={this.onValueUpdated} />
</>
);
}
}

Why do I always delete the first element while I am trying to delete others?

I am trying to write a function which can delete the component I selected and show the result after the deletion inside the search box. However, when I was deleting the other items, it will always delete the first one also.
Here is the higher order component which contains the search box component
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
hitsDisplay:false,
inputContent:"",
tags:[]
};
}
handleRemoveItem = (index) => {
this.setState(state => ({
tags: state.tags.filter((tag, i) => i !== index)
}));
}
handleSelect = value => {
this.setState(prevState => ({
tags:[...prevState.tags, value]
}));
this.setState({ selected:true })
}
openDisplay = () => {
this.setState({ hitsDisplay: true })
}
closeDisplay = () => {
this.setState({ hitsDisplay: false })
}
render() {
let result = (
<div className="container-fluid" id="results">
</div>
)
if (this.state.hitsDisplay) {
result = (
<Flexbox
flexDirection="column"
minHeight="100vh"
>
<div className="rows">
<MyHits handleSelect={this.handleSelect}/>
</div>
</Flexbox>
)
}
return (
<InstantSearch
appId="********"
apiKey="**************************"
indexName="tags"
>
<CustomSearchBox
handleRemoveItem={this.handleRemoveItem}
tags={this.state.tags}
styles={styles}
openDisplay={this.openDisplay}
closeDisplay={this.closeDisplay}
/>
{result}
</InstantSearch>
)
}
}
Here is the code for the search box component
import React, { Component } from 'react'
import { connectSearchBox } from 'react-instantsearch-dom';
const CustomSearchBox = ({ currentRefinement, refine, openDisplay, closeDisplay, styles, tags, handleRemoveItem, ...props }) => {
const handleChange = (e, refine) => {
const value = e.target.value
refine(value)
if (value !== "") {
openDisplay();
} else {
closeDisplay();
}
}
let inputTags = (
tags.map((tag, i) =>
<li key={i} style={styles.items}>
{tag}
<button
onClick={() => handleRemoveItem(i)}
>
(x)
</button>
</li>
)
)
return (
<label>
<ul style={styles.container}>
{inputTags}
<input
style={styles.input}
type="text"
value={currentRefinement}
onChange={e => handleChange(e, refine)}
/>
</ul>
</label>
)
}
export default connectSearchBox(CustomSearchBox);
I just wanted to delete the element which I press the button. So if I press the third delete button, the third element will be deleted and the first will persist.
I map my array in lower level component and pass the target to the higher level component. Even if I pass the name of the target itself instead of the index, it will delete the both the target and the first element. Compare to the possible duplicate question, its problem is deleting the last one, but mine will delete both the first one and the target one.

React Downshift autocomplete requests in an infinite loop

I have the following React component
class Search extends Component {
constructor(props){
super(props);
this.state = {
suggestions: []
};
this.getSuggestions = this.getSuggestions.bind(this);
}
renderSuggestion(){
return (
this.state.suggestions.map((suggestion, index) =>
<MenuItem component="div" key={index} value={index} >
{suggestion}
</MenuItem>
)
);
};
getSuggestions (value) {
const inputValue = deburr(value.trim()).toLowerCase();
if(inputValue.length >= 3){
axios.get('http://localhost:5001/api/v1/products',{
params: {
q: inputValue
}
}).then(response => {
this.setState({suggestions : response.data.data });
});
}
};
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<Downshift id="downshift-simple">
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField placeholder="Search a country (start with a)"
fullWidth={true}
onChange={this.getSuggestions(inputValue)}
{...getInputProps()}
/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{this.renderSuggestion}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
</div>
);
}
}
export default withStyles(styles)(Search);
The autocomletion wors as expected as long as i do not perform an axios request in getSuggestions(). It seems to perform the request in an infinite loop as long as i do not refresh the page. Any ideas why this strange behaviour occures?
Because you are calling that function instead of passing the function to onChange. Kindly change your function to arrow function. refer this link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
getSuggestions (e) {
let value = e.target.value
const inputValue = deburr(value.trim()).toLowerCase();
if(inputValue.length >= 3){
axios.get('http://localhost:5001/api/v1/products',{
params: {
q: inputValue
}
}).then(response => {
this.setState({suggestions : response.data.data });
});
}
};
<TextField placeholder="Search a country (start with a)"
fullWidth={true}
onChange={(e)=> this.getSuggestions(e)}
{...getInputProps()}
/>

React Redux add line-through on checkbox click

I am trying to add a line-through on after checking a checkbox. I'm using react and redux. The action and reducer works. I just need a way of adding this line-through when checked is true Please find the code i tried implementing this below. Thanks in advance.
/actions/items.js
export const CHECK_ITEM = "CHECK_ITEM"
export function checkItem(id) {
return {
type: CHECK_ITEM,
id
}
}
/reducers/items.js
case types.CHECK_ITEM:
return state.map((item) => {
if(item.id === action.id) {
return Object.assign({}, item,
{
checked: !item.checked
})
}
return item
})
/components/Editor.jsx
renderValue = () => {
const onDelete = this.props.onDelete
const onCheck = this.props.onCheck
return (
<div>
{onCheck ? this.renderCheckItem() : null}
<div onClick={this.props.onValueClick}>
<span className='value'>{this.props.value}</span>
{onDelete ? this.renderDelete() : null}
</div>
</div>
)
}
renderCheckItem = () => {
return (
<input
type="checkbox"
className='check-item checked'
defaultChecked={false}
onClick={this.props.onCheck}
/>
)
}
/components/Item.jsx
export default class Items extends React.Component {
render () {
const {items, onEdit, onDelete, onValueClick, onCheck, isEditing} = this.props
return (
<ul className="items">{items.map(item =>
<Item
className="item"
key={item.id}
id={item.id}>
<Editor
value={item.text}
onCheck={onCheck.bind(null, item.id)}
style={{textDecoration: item.checked ? 'line-through' : 'none'}}
/>
</Item>
)}</ul>
)
}
}
You need to connect your components to the redux store. Here's how to do it. In short you need something like:
export default connect(
state => {
return {items: state.items};
}
)(Items);
Where connect comes from react-redux.
I basically passed item.checked as item to my Editor component and used it like so
...
render() {
const {item, value, onEdit, onValueClick, isEditing, onCheck, ...props} = this.props
...
then in my Editor.jsx i did the following
/components/Editor.jsx
renderValue = () => {
const onDelete = this.props.onDelete
const onCheck = this.props.onCheck
const itemChecked = this.props.item
const isChecked = {textDecoration: itemChecked ? 'line-through' : 'none'}
return (
<div>
{onCheck ? this.renderCheckItem() : null}
<div onClick={this.props.onValueClick}>
<span style={isChecked} className='value'>{this.props.value}</span>
{onDelete && this.renderDelete()}
</div>
</div>
)
}
renderCheckItem = () => {
return (
<input
type="checkbox"
className='check-item'
defaultChecked={false}
onClick={this.props.onCheck}
/>
)
}
/components/Items.jsx
export default class Items extends React.Component {
render () {
...
return (
<ul className='items'>{items.map((item) =>
<Item
className='item'
key={item.id}
id={item.id}>
<Editor
item={item.checked}
isEditing={item.isEditing}
...

Resources