I want to add the value to an array in state onClick event in React.
So far with the onSelectRooms function, I can get the value with e.target.id but It doesn't append the value to the array selectedRooms that's on state.
onSelectRooms = (e) => {
const newItem = e.target.id;
this.setState({
selectedRooms: [...this.state.selectedRooms, newItem]});
}
export default class ExportReportRoomSelectionModal extends React.Component {
constructor(props) {
super(props);
const roomOrder = configContext.value.roomOrder;
this.state = {
rooms: roomOrder,
selectedRooms: [],
};
this.onSelectRooms = this.onSelectRooms.bind(this);
}
onSelectRooms = (e) => {
const newItem = e.target.id;
this.setState({
selectedRooms: [...this.state.selectedRooms, newItem]});
}
render() {
return (
<Modal>
<Modal.Header>
<Modal.Title>Title</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Number of rooms: {this.state.rooms.length}</p>
<p>Rooms:</p>
<Grid fluid={true}>
<Row className="show-grid">
{ this.state.rooms.map((name, i ) =>
<Col key={i}>
<Panel onClick={this.onSelectRooms}>
<Panel.Heading id={name}>
{name}
</Panel.Heading>
</Panel>
</Col>
)}
</Row>
</Grid>
</Modal.Body>
</Modal>);
}
}
Create a Input field and define a name for this input field.
And collect data from this input field by this.refs.inputFieldName.value
and push this inside your declared array variable.
Below Git Project link with full crud and Documentation.
Try it
https://gitlab.com/ripon52/reactcrud
You can pass index of clicked room to onSelectRooms function.
export default class ExportReportRoomSelectionModal extends React.Component {
constructor(props) {
super(props);
const roomOrder = configContext.value.roomOrder;
this.state = {
rooms: roomOrder,
selectedRooms: [],
};
}
onSelectRooms = (e, index) => {
this.setState(prevState => ({
selectedRooms: [...prevState.selectedRooms, index]
}));
}
render() {
return (
<Modal>
<Modal.Header>
<Modal.Title>Title</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Number of rooms: {this.state.rooms.length}</p>
<p>Rooms:</p>
<Grid fluid={true}>
<Row className="show-grid">
{ this.state.rooms.map((name, i ) =>
<Col key={i}>
<Panel onClick={() => this.onSelectRooms(i)}>
<Panel.Heading id={name}>
{name}
</Panel.Heading>
</Panel>
</Col>
)}
</Row>
</Grid>
</Modal.Body>
</Modal>);
}
}
You can try this,
const joined = this.state.selectedRooms.concat(newItem);
this.setState({ selectedRooms: joined })
change onSelectRooms() with below code and check
onSelectRooms = (e) => {
const newItem = e.target.id;
let roomArray = this.state.selectedRooms;
roomArray.push(newItem); //add value in local array
this.setState({selectedRooms: roomArray}); //replace selectedRooms with local arry
}
enjoy !
Related
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} />
</>
);
}
}
I need to select images in an image grid. Currently, I can only select one image at a time. When selecting one, the other unselects. I'd like to select every image I want (many images selected at a time). I can't activate a isToggle boolean on each element with onClick event.
class Gallery extends Component{
constructor(props) {
super(props);
this.state = {
//isToggleOn: true,
selected: '',
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleClick = key => {
this.setState(state => ({
//isToggleOn: !state.isToggleOn,
selected: key,
}));
//console.log(this.state)
}
render(){
//const classes = useStyles();
return (
<Container>
<Row >
<Col>
<div style={styles.root}>
<GridList cellHeight={160} style={styles.gridList} cols={3}>
{tileData.map((tile,i) => (
<GridListTile key={i} cols={tile.cols || 1} onClick={() => this.handleClick(tile)} >
<img src={tile.img} alt={tile.title} style={this.state.selected === tile ? styles.inputClicked:styles.inputNormal} />
</GridListTile>
))}
</GridList>
</div>
</Col>
</Row>
</Container>
);
}
}
export default Gallery;
I can only select one image at a time. I expect to select many at a time.
When the tile gets clicked, you need to add to an array of selected tiles like this:
handleClick = tile => {
this.setState({
selected: this.state.selected.concat([tile]),
});
}
then when rendering, you can check if the tile is selected something like this:
<img src={tile.img} alt={tile.title} style={isSelected(this.state.selected, tile) ? styles.inputClicked:styles.inputNormal} />
with:
isSelected = (selected, tile) => {
// some logic to check that the tile is already selected e.g.
return selected.find(tileItem => tileItem.title === tile.title)
}
Ensure to initialise your selected state to [] in constructor :
this.state = {
selected: [],
};
UPDATE:
If you want to remove tiles too, you'd have to do something like this:
handleClick = tile => {
const { selected } = this.state;
if(selected.find(item ==> item.title === tile.title)) {
// need to remove tile
this.setState({
selected: selected.filter(item => item.tile !== tile.title),
});
} else {
this.setState({
selected: this.state.selected.concat([tile]),
});
}
}
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>
);
}
}
I have a list of courses with student icon on them. When clicked on student icon it opens a modal and displays all assigned students on the course.
The problem I am having is when I click on one of the course to open the modal, it opens the modal for all the other courses. I know its to do with the state behaviour but I can't seem to figure out how best to tackle this problem.
Following is my code:
class CourseDetails extends React.Component {
constructor(props) {
super(props);
autobind(this);
this.state = { openStudentsAssignedToCourseModal: false };
}
closeStudentsAssignedToCourseModal() {
this.setState({ openStudentsAssignedToCourseModal: false });
}
render() {
const { store } = this.props;
const { openStudentsAssignedToCourseModal } = this.state;
return store.allCourses.map((course) => {
return (
<Container key={course.id}>
<p>{course.name}</p>
<UsersIcon
size={25}
onClick={() => {
if (course.listOfStudents.length > 0)
this.setState({
openStudentsAssignedToCourseModal: true
});
}}
/>
{openStudentsAssignedToCourseModal && (
<StudentsOnCourseModal
course={course}
isOpen
close={() => {
this.closeEmployeesAssignedModal();
}}
/>
)}
</Container>
);
});
}
}
Modal:
class StudentsOnCourseModal extends React.Component {
constructor() {
super();
autobind(this);
}
render() {
const { course, isOpen, close } = this.props;
const s = course.listOfStudents.length === 1 ? '' : 's';
return (
<Modal
isOpen={isOpen}
close={close}
width="large"
bgDismiss={false}
>
<ModalHeader>
<h2>Assigned students</h2>
</ModalHeader>
<ModalBody>
<p>
There {s === '' ? 'is' : 'are'}{' '}
<b>{course.listOfStudents.length}</b> student{s}{' '}
currently assigned to the course <b>{course.name}</b>.
</p>
<StudentsContainer>
{course.listOfStudents.map(student => (
<StudentItem key={student.id}>
<StudentCard
name={student.name}
link={`/student-profile/${
student.id
}/personaldetails`}
imageHref={
student._links.image
? student._links.image.href
: undefined
}
/>
</StudentItem>
))}
</StudentsContainer>
<OutlineButton
onClick={e => {
e.preventDefault();
close();
}}
>
Close
</OutlineButton>
</ModalBody>
</Modal>
);
}
}
I wasn't capturing the selected course so it was opening all of them. Fixed it by introducing a new state to capture the selected value and passed that into the modal.
I am trying to implement react-virtualized into my project. I have a side panel with a list of subdivisions. I would like to have an accordion-like functionality when the user selects an item. When the page first loads it looks like it is working correctly.
However when I start to scroll down the list looks like this
and here is code
const mapStateToProps = state => ({
windowHeight: state.dimensions.windowHeight,
sbds: new Immutable.List(state.sbds.data),
sbd: state.sbd.data
})
#connect(mapStateToProps)
export default class SBD extends Component {
static propTypes = {
windowHeight: PropTypes.number,
sbds: PropTypes.instanceOf(Immutable.List),
sbd: PropTypes.object
}
constructor(props) {
super(props)
this.state = {
listHeight: props.windowHeight - 250,
listRowHeight: 60,
overscanRowCount: 10,
rowCount: props.sbds.size,
scrollToIndex: undefined,
collapse: true
}
}
componentWillUnmount() {
}
shouldComponentUpdate(nextProps, nextState) {
const {sbds} = this.props
if (shallowCompare(this, nextProps, nextState))
return true
else return stringify(sbds) !== stringify(nextProps.sbds)
}
_handleSelectRow = selected => {
sbdRequest(selected)
const obj = {[selected.id]: true}
this.setState(obj)
}
render() {
const {
listHeight,
listRowHeight,
overscanRowCount,
rowCount,
scrollToIndex
} = this.state
return (
<div>
<SearchGroup />
<Card className='border-0 mt-10 mb-0'>
<CardBlock className='p-0'>
<AutoSizer disableHeight>
{({width}) => (
<List
ref='List'
className=''
height={listHeight}
overscanRowCount={overscanRowCount}
rowCount={rowCount}
rowHeight={listRowHeight}
rowRenderer={this._rowRenderer}
scrollToIndex={scrollToIndex}
width={width}
/>
)}
</AutoSizer>
</CardBlock>
</Card>
</div>
)
}
_getDatum = index => {
const {sbds} = this.props
return sbds.get(index % sbds.size)
}
_rowRenderer = ({index}) => {
const {sbd} = this.props
const datum = this._getDatum(index)
return (
<span key={datum.id}>
<Button
type='button'
color='link'
block
onClick={() => this._handleSelectRow(datum)}>
{datum.name}
</Button>
<Collapse isOpen={this.state[datum.id]}>
<Card>
<CardBlock>
FOO BAR
</CardBlock>
</Card>
</Collapse>
</span>
)
}
}
You're not setting the style parameter (passed to your rowRenderer). This is the thing that absolutely positions rows. Without it, they'll all stack up in the top/left as you scroll.
https://bvaughn.github.io/forward-js-2017/#/32/1