Use Vertical Steps component from antd with in a ScrollPanel from PrimeReact - reactjs

I would like to use the Vertical Steps from antd(https://ant.design/components/steps/) within a primeReact ScrollPanel(https://www.primefaces.org/primereact/#/scrollpanel). I would also like to use the "Next" and "Previous" button as shown in the "antd" example. All works fine till here, but when I have a lot of steps and the click on "Next" moves to next step but the Scroller doesn't move i.e. the highlighted step moves out of View. How to make the scroll also move so the selected Step is in centre View?
Code for vertical Steps inside ScrollPanel:
class VerticalStepsWithContent extends Component {
constructor(props) {
super(props);
this.state = { current: 0 };
}
next() {
const current = this.state.current + 1;
this.setState({ current });
}
prev() {
const current = this.state.current - 1;
this.setState({ current });
}
onChange = current => {
console.log("onChange:", current);
this.setState({ current });
};
render() {
const { Step } = Steps;
const steps = [
{
title: "First",
content: "First-content"
},
{
title: "Second",
content: "Second-content"
},
{
title: "Last",
content: "Last-content"
}
];
const { current } = this.state;
return (
<div>
<div className="steps-action">
{current < steps.length - 1 && (
<Button type="primary" onClick={() => this.next()}>
Next
</Button>
)}
{current === steps.length - 1 && (
<Button
type="primary"
onClick={() => message.success("Processing complete!")}
>
Done
</Button>
)}
{current > 0 && (
<Button style={{ marginLeft: 8 }} onClick={() => this.prev()}>
Previous
</Button>
)}
</div>
<ScrollPanel
header="Summary"
style={{ height: "400px" }}
className="custom"
>
<Steps
direction="vertical"
current={current}
onChange={this.onChange}
>
{steps.map(item => (
<Step key={item.title} title={item.title} />
))}
</Steps>
</ScrollPanel>
{/* <div className="steps-content">{steps[current].content}</div> */}
</div>
);
}
}
export default VerticalStepsWithContent;

You didn't bind class functions to this instance (which is a common bug):
class VerticalStepsWithContent extends Component {
constructor(props) {
super(props);
this.state = { current: 0 };
this.next = this.next.bind(this);
// bind rest of the functions
}
}
You should avoid the use of constructor, instead use class properties:
class VerticalStepsWithContent extends Component {
state = { current: 0 };
next = () => {
const current = this.state.current + 1;
this.setState({ current });
};
// ...
}
export default VerticalStepsWithContent;

Related

React-images gallery

I am trying to build an image gallery with React-images(https://github.com/jossmac/react-images).
Here is my code so far.
https://codesandbox.io/s/gallant-yalow-7srs6
Here I am trying to achieve two things:
Implement the modal and the modal will open with current select image from the base gallery.
Change the "of" inside active view of total view on footer. i.e. currently its "1 of 4" so I need this like "1 / 4"
Could anyone please help me? I am kind of lost :(
Thanks in advance.
So I was able to achieve your requirement,
Working example : https://codesandbox.io/s/xenodochial-dawn-scjsv
And here is the code:
class gall extends React.Component {
state = { modalIsOpen: false, currentIndex: 0 };
toggleModal = () => {
this.setState(state => ({ modalIsOpen: !state.modalIsOpen }));
};
onImageChange = (index) => {
console.log(index)
this.setState(state => ({ currentIndex: index }));
};
render() {
const { modalIsOpen } = this.state;
const CustomModalFooter = ({ currentIndex, views }) => {
const activeView = currentIndex + 1;
const totalViews = views.length;
if (!activeView || !totalViews) return null;
return (
<span class="react-images__footer__count css-w6xjhe css-1ycyyax">
{activeView} / {totalViews}
</span>
);
};
return (
<>
<button
type="button"
className="btn-fullScreen"
onClick={this.toggleModal}
>
Open Modal
</button>
<ModalGateway>
{modalIsOpen ? (
<Modal onClose={this.toggleModal}>
<Carousel
currentIndex={this.state.currentIndex}
components={{ FooterCount: CustomModalFooter }}
views={images}
/>
</Modal>
) : null}
</ModalGateway>
<Carousel
onClick={this.onImageClick}
trackProps={{onViewChange:(index) => this.onImageChange(index)}}
components={{ FooterCount: CustomModalFooter }}
views={images}
/>
</>
);
}
}
export default gall;

React.js: state is not updating in parent component

I have search filter and categories. I just want to have a possibility to reset state in single page application.
Due to React.js I guess I do everything correct to pass state from parent to child and then from child to parent. But, unfortunately, something is going wrong. I tried a lot and what I discovered, that onAddCategory() in DropdownGroup doesn't update current state.
Sorry in advance, I add whole code, my be something there could affect this. But i guess you can see first halfs of two codes and it will be enough.
Thank you in advance.
I have parent component:
class DropdownGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [], // we have empty array, that pass to CategoryDropdown
};
this.onAddCategory = this.onAddCategory.bind(this);
}
onAddCategory(newCategory) {
this.setState(() => ({
categories: newCategory,
}));
}
onSelectCategory(path) {
this.props.onChangeEvents(path);
}
render() {
const months = ['January', 'February' ... ];
const eventsType = ['Party', 'Karaoke ... ];
const { categories } = this.state;
return (
<ButtonToolbar className="justify-content-center pb-4 pt-4">
{ console.log(categories) }
<CategoryDropdown
items={eventsType}
homePath="events"
path="events/categories/"
categories={categories} // here we pass our empty array (or updated later)
addCategories={this.onAddCategory} // this is what helps to update our array
onApply={(path) => this.onSelectCategory(path)}
/>
<MyDropdown
id="sort-by-month"
name="By month"
items={months}
onSelect={(e) => this.onSelectCategory(`events/month/${e}`)}
/>
<DropdownWithDate
oneDate="events/date/"
rangeDate="events/dates?from="
onApply={(path) => this.onSelectCategory(path)}
/>
<Button
onClick={() => this.setState({ categories: [] })} // here we can reset the value of our array
className="m-button ml-5"
>
Reset
</Button>
</ButtonToolbar>
);
}
}
DropdownGroup.propTypes = {
onChangeEvents: PropTypes.any.isRequired,
};
export default DropdownGroup;
and this is child component
class CategoryDropdown extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
selected: this.props.categories, // here we get values from props (now empty, then updated values)
};
this.onVisibleChange = this.onVisibleChange.bind(this);
}
onVisibleChange(visible) {
this.setState({
visible: visible,
});
}
saveSelected(selectedKeys) {
this.setState({
selected: selectedKeys,
});
}
addCategories() {
this.props.addCategories(this.state.selected); // here props are updated
}
confirm() {
const { selected } = this.state;
this.addCategories(this.state.selected);
const { homePath, path } = this.props;
if (selected.length > 0) {
this.props.onApply(path + selected);
} else {
this.props.onApply(homePath);
}
this.onVisibleChange(false);
}
render() {
const { visible } = this.state;
const { items } = this.props;
const menu = (
<Menu
multiple
onSelect={(e) => { this.saveSelected(e.selectedKeys); }}
onDeselect={(e) => { this.saveSelected(e.selectedKeys); }}
>
{items.map((item) => (
<MenuItem
key={item.replace('\u0020', '\u005f').toLowerCase()}
>
{item}
</MenuItem>
))}
<Divider />
<MenuItem disabled>
<Container
className="text-center "
style={{
cursor: 'pointer',
pointerEvents: 'visible',
}}
onClick={() => {
this.confirm();
}}
>
Select
</Container>
</MenuItem>
</Menu>
);
return (
<Dropdown
trigger={['click']}
onVisibleChange={this.onVisibleChange}
visible={visible}
closeOnSelect={false}
overlay={menu}
>
<Button className="m-button">By Category</Button>
</Dropdown>
);
}
}
CategoryDropdown.propTypes = {
onApply: PropTypes.any.isRequired,
items: PropTypes.any.isRequired,
path: PropTypes.string.isRequired,
homePath: PropTypes.string.isRequired,
categories: PropTypes.array.isRequired,
addCategories: PropTypes.any.isRequired,
};
export default CategoryDropdown;

React component opens all child states

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.

having trouble seeing increment button in react component

I have two componenets , counter and counters. I have a box that shows the value when you click the increment button in my counter component thats not being displayed. I refactored my code so that that my counter component is a controlled component instead of an uncontrolled component so it gets its data from my props object. I will paste the code down below.
Update: I am now able to see the box that has the number of increments but when i click Increment I get Nan displayed in the box for the value.
counter component
import React, { Component } from "react";
class Counter extends Component {
// styles for our bootstrap
styles = {
fontSize: 30,
fontWeight: "bold"
};
render() {
console.log("props", this.props);
return (
<div>
<span className={this.getBadgeColor()}>{this.formatCount()}
</span>
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className="btn btn-secondary btn-md"
>
Increment
</button>
<button
onClick={() => this.props.onDelete(this.props.counter.id)}
className="btn btn-danger btn-sm m-2"
>
Delete
</button>
</div>
);
}
getBadgeColor() {
let classes = "badge m-2 badge-";
classes += this.props.counter.value === 0 ? "warning" :
"primary";
return classes;
}
formatCount() {
const { value } = this.props.counter;
return value === 0 ? <h2> Zero </h2> : value;
}
}
export default Counter;
counters component
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 5 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleDelete = counterId => {
const counters = this.state.counters.filter(c => c.id !==
counterId);
this.setState({ counters });
};
handleReset = () => {
const counters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState.counters = { counters };
};
handleIncrement = counter => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counters };
counters[index].value++;
this.setState({ counters });
};
render() {
return (
<div>
<button
onClick={this.handleReset}
className="btn btn-primary btn-sm m-2"
>
Reset
</button>
{this.state.counters.map(counters => (
<Counter
key={counters.id}
onDelete={this.handleDelete}
counter={counters}
onIncrement={this.handleIncrement}
/>
))}
</div>
);
}
}
export default Counters;
you are seeing NaN because in the counters component you should assign values of state .

How to use react-virtualized with dynamic list height

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

Resources