I'm using the react-sortable-hoc library to sort my list of items. More than just listing I need to run a functionality when clicking the single item.
Listing and sorting and everything is working fine. How can I pass a props that should be called clickFunction() which consoles the name when I click the name listed through SortableItem?
import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc';
const SortableItem = SortableElement(({value}) => <li>{value.first_name}</li>);
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>
);
});
class Details extends React.Component {
clickFunction(name) {
console.log(name)
}
onSortEnd({oldIndex, newIndex}) {
this.setState({
testlist: arrayMove(this.state.testlist, oldIndex, newIndex),
});
};
render() {
return (
<div>
<SortableList items={this.state.testlist} onSortEnd={this.onSortEnd.bind(this)} pressDelay="200" />
</div>
)
}
}
You can pass the function from Details component and receive it in props of the SortableList and SortableItem like
import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc';
const SortableItem = SortableElement(({value, clickFunction}) => <li onClick={() => clickFunction(value)}>{value.first_name}</li>);
const SortableList = SortableContainer(({items, clickFunction}) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} clickFunction={clickFunction} value={value} />
))}
</ul>
);
});
class Details extends React.Component {
clickFunction(name) {
console.log(name)
}
onSortEnd({oldIndex, newIndex}) {
this.setState({
testlist: arrayMove(this.state.testlist, oldIndex, newIndex),
});
};
render() {
return (
<div>
<SortableList items={this.state.testlist} onSortEnd={this.onSortEnd.bind(this)} pressDelay="200" clickFunction={this.clickFunction} />
</div>
)
}
}
This is an example of passing props to SortableContainer:
const SortableList = SortableContainer(({items, props, handleClickFunction, fieldSelector}) => {
const subComponentList = [];
items.map((value, index) =>
subComponentList.push(
<SortableItem
key={`item-${index}`}
index={index}
value={value}
props={props}
handleClickFunction={handleClickFunction}
fieldSelector={fieldSelector}
/>)
);
return (
<ul>{subComponentList}</ul>
);
});
Related
I have this working stateful component in React:
import React, {Component} from "react";
class MyComponent extends Component {
constructor() {
super();
this.myRef = React.createRef();
this.state = {
array: [],
noresults: false
};
}
loadData = () => {
let data = this.myRef.current.value;
let url = "someurl="+data;
if(!data) {
return;
}
fetch(url)
.then((res) => res.json())
.then((json) => {
if(json.data.length > 0) {
this.setState({
array: json.data,
noresults: false
});
} else {
this.setState({
noresults: true
});
}
})
}
render() {
const { array, noresults } = this.state;
return (
<div>
<section>
<input ref={this.myRef} type="number"/>
<button onClick={this.loadData}>Click Here</button>
</section>
<ul>
{
array.map((e) => (
<li key = { e.key } >
{ e.data }
</li>
))
}
</ul>
{noresults && <div>No Records</div>}
</div>
);
}
}
export default MyComponent
I want to convert this to stateless like this:
function MyComponent() {
return (
<div>
<section>
<input type="number"/>
<button>Click Here</button>
</section>
<ul>
<li></li>
</ul>
<div>No Records</div>
</div>
);
}
export default MyComponent
Now how can I pass data of input to my method to make API call. Also how to pass the response from API to display as ul and li elements?
Just pass it as component props:
const MyComponent = ({array = [], loadData = () => {}}) => {
const [inputValue, setInputValue] = useState();
const handleInputChange = (evt) => {
setInputValue(evt.target.value);
};
return (
<div>
<section>
<input type="number" onChange={handleInputChange} />
<button onClick={e => loadData(e, inputValue)}>Click Here</button>
</section>
<ul>
{array.map((e) => (<li key={e.key}>{e.data}</li>))}
</ul>
{array.length === 0 && <div>No Records</div>}
</div>
);
}
For input, I created a local state which is updated on input change and passed it to loadData function. You can access the current value by parametrizing loadData function:
loadData = (e, currentInputValue) => { ... };
I am working on my react app specifically on removing one item at a time using my handleRemoveOption function:
handleRemoveOption(optionToRemove){
this.setState((prevState) => ({
options: prevState.options.filter((option) => optionToRemove !== option)
}));
}
Here's my full code:
class Box extends React.Component{
constructor(props){
super(props);
this.state = {
options: ['one', 'two', 'three']
}
this.handleRemoveAllOptions = this.handleRemoveAllOptions.bind(this);
this.handleDecision = this.handleDecision.bind(this);
this.handleAddOption = this.handleAddOption.bind(this);
this.handleRemoveOption = this.handleRemoveOption(this);
}
handleRemoveAllOptions(){
this.setState({
options: []
});
}
handleDecision(){
const randomNum = Math.floor(Math.random() * this.state.options.length);
const option = this.state.options[randomNum];
alert(option);
}
handleAddOption(option){
this.setState((prevState) => ({
options: prevState.options.concat(option)
}));
}
handleRemoveOption(optionToRemove){
this.setState((prevState) => ({
options: prevState.options.filter((option) => optionToRemove !== option)
}));
}
render(){
const title = 'Indecision app';
const subtitle = 'Put your life..';
return(
<div>
<Header
title={title}
subtitle={subtitle}
/>
<Action
handleDecision={this.handleDecision}
hasOptions={this.state.options.length === 0}
/>
<Options
options={this.state.options}
hasOptions={this.state.options.length === 0}
handleRemoveAllOptions={this.handleRemoveAllOptions}
handleRemoveOption={this.handleRemoveOption}
/>
<AddOption handleAddOption={this.handleAddOption} />
</div>
);
}
}
const Header = (props) => {
return(
<div>
<h1>{props.title}</h1>
<h3>{props.subtitle}</h3>
</div>
);
};
const Action = (props) => {
return(
<div>
<button
onClick={props.handleDecision}
disabled={props.hasOptions}>
Decide for me
</button>
</div>
);
};
const Options = (props) => {
return(
<div>
<button
onClick={props.handleRemoveAllOptions}
disabled={props.hasOptions}>
Remove All
</button>
<ol>
{ props.options.map((option) => (
<Option
key={option}
optionText={option}
handleRemoveOption={props.handleRemoveOption}
/>
))
}
</ol>
</div>
);
};
const Option = (props) => {
return (
<div>
<li>
<span>{ props.optionText }</span>
<button
onClick={(e) => {
props.handleRemoveOption(props.optionText);
}}>
Remove Option
</button>
</li>
</div>
);
};
class AddOption extends React.Component{
constructor(props){
super(props);
this.handleAddOption = this.handleAddOption.bind(this);
}
handleAddOption(e){
e.preventDefault();
const option = e.target.option.value.trim();
this.props.handleAddOption(option);
}
render(){
return(
<div>
<form onSubmit={this.handleAddOption}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
</div>
);
}
}
ReactDOM.render(<Box />, document.getElementById('app'))
When I click on single button of each item it always say Uncaught TypeError: props.handleRemoveOption is not a function
What am I doing wrong here?
In your constructor of the Box component, all functions are bound to this, but handleRemoveOption is not.
Notice the missing this.handleRemoveOption.**bind**(this).
Editing line 13 to
this.handleRemoveOption = this.handleRemoveOption.bind(this);
will fix your issue!
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>
);
}
}
When I write my React applications I keep all state, props calculations in separate functions. I call them selectors, though I'm not sure if those are selectors. By definition, selectors are functions which return subsets of state or props. I have several questions concerning this issue:
1) Can selectors return jsx objects?
For example, I would like to populate <Item /> into another component
selectorItem = (state, props) => {
const { items } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
return {
items: ItemsJSX
}
}
class Page extends Component {
render() {
return (
<List
{...selectorItem(this.state, this.props)}
/>
)
}
}
Is this a valid selector?
If not, how can I populate JSX into another component using selectors?
2) Should I write selector per component (selector returns object with multiple props) or per prop (separate selector per prop)?
selectorItemsComments = (state, props) => {
const { items } = props;
const { comments } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
const CommentsJSX = comments.map((comment) => {
<Comment
comment={comment}
key={comment.id}
/>
});
return {
items: ItemsJSX,
comment: CommentsJSX
}
}
versus
selectorItems = (state, props) => {
const { items } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
return {
items: ItemsJSX
}
}
selectorComments = (state, props) => {
const { comments } = props;
const CommentsJSX = comments.map((comment) => {
return (
<Comment
comment={comment}
key={comment.id}
/>
)
});
return {
comment: CommentsJSX
}
}
Thank you
These are not selectors the are just functions returning components
TL;DR
The short answer is yes you can return Components from functions.
Full Answer
In your simpler first example you should just return the component array from your selectorItem (I have renamed this to renderItems) function:
const renderItems = (items) => items.map((item) =>
<Item
item={item}
key={item.id}
/>
);
const renderComments = (comments) => comments.map((comment) =>
<Comment
item={comment}
key={comment.id}
/>
);
class Page extends Component {
render() {
const {items, comments} = this.props;
return (
<List>
{renderItems(items)}
{renderComments(comments)}
</List>
);
}
}
I would recommend converting renderItems to a Component and then you could do something like:
<List>
<Items items={items} />
<Comments comments={comments} />
</List>
If I understand correctly, your selector is still a React component. This pattern is called "Smart vs Dumb" component, or Presentational and Container Components.
Is HOCs (Higher Order Component) what you want ?
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}
...