I wish to add spinner animation after clicking on button, when get response, spinner is supposed to disappear. So far works fine but the problem is that I render list with many elements and every element has own delete button, while clicking on one, animation is added to all elements of the list. I wish it to appear only once, next to this particular clicked element of the list.
const displayCertificateList = (
classes,
mainStatus,
handleDeleteSingleCertificate,
animateDelete
) => {
return mainStatus.map((el, i) => {
return (
<div className={classes.certificatesListContainer} style={{border:'none'}}>
<List key={i} style={{padding: '10px'}}>
<ListItem style={{ padding: "0 0 0 20px" }}>
<ListItemText
className={classes.certificatesList}
primary={
<Typography type="body2" style={{ fontWeight: "bold" }} className={classes.certificatesListFont}>
Valid until:
</Typography>
}
secondary={
<Typography
type="body2"
className={classNames(
classes.certificatesListSecondArgument,
classes.certificatesListFont,
el.expiresIn > 90 ? classes.green : classes.red
)}
>
{el.validUntil.slice(0,9)} ({el.expiresIn} days)
</Typography>
}
/>
</ListItem>
</List>
<div className={classes.certificatesBtn}>
<Button
variant="contained"
size="small"
color="secondary"
className={classes.button}
onClick={() => {
if (
window.confirm(
`Are you really sure?
)
)
handleDeleteSingleCertificate(el, i);
}}
>
<DeleteIcon className={classes.leftIcon} />
Delete
</Button>
<div style={{left: '-50%',top: '30%'}} className={classNames(animateDelete ? classes.spinner : null)}></div>
</div>
</div>
);
});
} else {
return (
<div>
<Typography component="h1" variant="h6">
The applet is not innitialized, please initialize it first
</Typography>
</div>
);
};
And in parent component:
handleDeleteSingleCertificate = (el, i) => {
this.setState({animatingDelete: true})
this.make_call(
this.state.selected,
(res) => {
console.log(res)
this.setState({animatingDelete: false})
}
)
}
And pass it like this:
{this.state.view === 'certificates' && this.state.certificates && displayCertificates(classes, fakeData, this.handleDeleteSingleCertificate, this.state.animatingDelete)}
I suggest to make displayCertificateList function component to stateful component and store the animatingDelete in it - `cause it is the state of that particular item in deed.
class ListItem extends React.Component {
state = {
isDeleting: false
}
handleDelete = () => {
const { onDelete, id } = this.props;
onDelete(id);
this.setState({
isDeleting: true
})
}
render(){
const { isDeleting } = this.state;
return (
<li>
<button onClick={this.handleDelete}>Delete {isDeleting && '(spinner)'}</button>
</li>
)
}
}
class List extends React.Component {
state = {
listItems: [
{id: 1},
{id: 2}
]
}
handleDelete = id => {
console.log('delete ' + id);
// do the async operation here and remove the item from state
}
render(){
const { listItems } = this.state;
return (
<ul>
{listItems.map(({id}) => (
<ListItem id={id} key={id} onDelete={this.handleDelete} />
))}
</ul>
)
}
}
ReactDOM.render(<List />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
In my opinion, it's better to use count instead of animatingDelete to mark. You can plus 1 when click on the delete button and then when it's done minus 1. when count equals to 0, hide spining otherwise show it.
Related
I have an array of items that i want to show with a map function, and every item is shown as a card.
I'm trying to show two kinds of cards with a different content, one if "isHover" is false, and the other if it true using onMouseEnter/onMouseOver.
I made "isHover" as an array in order to know which item to show/hide.
(The "isHover" array has the same length that the items' array has).
The problem is that when I hover one card it dissappears and nothing is shown in place of it. :(
The code:
function TeachersShow(props) {
const [isHover, setIsHover] = useState(null);
const updateIsHover = (index, isHover1) => {
let newArray = isHover;
newArray[index] = isHover1;
setIsHover([...newArray]);
console.log(isHover[index]);
};
return (
<div>
{isHover[index] === false && (<Card className="teacher-card"
onMouseEnter={() => { updateIsHover(index, true) }}
key={index}
item={item}
onClick={() => navigateToTeacher(item)}
>
<Card.Img className="teachersImg" src={item.photoURL}>
</Card.Img>
<Card.Title className=" teachersName">
{item.username}
</Card.Title>
</Card>)}
{isHover[index] === true && (
<Card className="card-hover"
onMouseleave={() => { updateIsHover(index, false) }}
key={index}
item={item}
onClick={() => navigateToTeacher(item)}
>
<Card.Title className=" teachersName">
{item.username}
</Card.Title>
<Card.Subtitle className="proTeacher">
{`${item.profession} teacher`}
</Card.Subtitle>
<Card.Text className="teacherDesc">
{item.teacher_description}
</Card.Text>
</Card>)}
</Col>
))}
<Col></Col>
</Row>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TeachersShow);
It's little hard to tell what's wrong, since a lot of information is missing from the code. But try to create a component Let's say <Teacher />, and let it be responsible for hovering action. Try this:
function Teacher(item) {
const [hover, setHover] = useState(false);
const renderCardData = () => {
if (!hover) {
return (
<Card.Img className="teachersImg" src={item.photoURL} />
<Card.Title className="teachersName">
{item.username}
</Card.Title>
);
}
return (
<Card.Title className=" teachersName">
{ item.username }
</Card.Title>
<Card.Subtitle className="proTeacher">
{ `${item.profession} teacher` }
</Card.Subtitle>
<Card.Text className="teacherDesc">
{ item.teacher_description }
</Card.Text>
);
};
return (
<Card
className={ hover
? 'card-hover'
: 'teacher-card' }
onMouseEnter={ () => setHover(true) }
onMouseLeave={ () => setHover(false) }
>
{ renderCardData() }
</Card>
);
}
export default Teacher;
And you render it like that:
function TeachersList(teachers) {
return teachers.map(Teacher);
};
This is my scenario
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} button className={classes.mainTagItem}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
when i click on my ListItem ( that becomes selected ) i want the element <div className={classes.mainTagCircle}> has an active class
For Example:
<div classes={{ root: !!listItemSelected ? classes.mainTagCircleActive : classes.mainTagCircle, }}></div>
I have already a method onClick in my ListItem, how can i apply this logic?
given that you have a mainTag state you could compare with your element tagId to define which class to select. If it's the same as your state then active class wil be returned:
<div className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}>
</div>
Solution with a library
You could try with this library clsx. Then do something like this:
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className=
{clsx([classes.mainTagItem, mainTag === mainTagItem.tag.tagId ? :
'activeClass': 'defaultClass' ])}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
)
}
Solution without libraries
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}
/>
</ListItem>
)
})}
</List>
)
}
I have a list with some items that are added to a state on click, Im using native-base, how can I change the style of the listitem when I press it, or add the "selected" attribute to the list item?
code
const [data, setData] = useState([]);
const _renderItem = ({ item, index }) => {
return (
<ListItem
button={true}
onPress={() => handleItemSelect(item)}
>
<Left>
<Text>{item.Name}</Text>
</Left>
<Right>
<Icon name="add" style={{ paddingHorizontal: 5 }} />
</Right>
</ListItem>
);
};
return(
<Container>
<List>
<FlatList
data={data}
renderItem={_renderItem}
/>
</List>
</Container>
);
Im wondering how am I going to add a style and distinguish between the different list items that I have, if this isn't possible how can I use native-base "selected" and append it to the listitem?
the handleItemSelect adds the item id to a state so im currently managing which items are selected, how can I use this info, or any other way to highlight the selected items?
Edit:
I figured how to easily do this since I have the selected items id's
<ListItem
selected={selectedItems.some((prevItem) => prevItem._id === item._id)}
style={sameasabove ? style1 : style2}
button={true}
onPress={() => handleItemSelect(item)}
>
</ListItem>
You can do some thing like this:
Example
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [
{ name: "Interstellar" },
{ name: "Dark Knight" },
{ name: "Pop" },
{ name: "Pulp Fiction" },
{ name: "Burning Train" },
],
setData: []
};
for (var i = 0; i < this.state.data.length; i++) {
this.state.setData[i] = "red";
}
this.setState({
setData: this.state.setData
})
}
handleItemSelect(item, index) {
this.state.setData[index] = "green";
this.setState({
setData: this.state.setData
})
}
renderItem = ({ item, index }) => {
return (
<ListItem button={true}
onPress={() => this.handleItemSelect(item, index)} style={{ marginLeft: 0 }}>
<Body>
<Text style={{ color: this.state.setData[index] }}>{item.name}</Text>
</Body>
</ListItem>
);
};
render() {
return (
<FlatList style={{ marginTop: 30 }}
data={this.state.data}
renderItem={this.renderItem}
/>
);
}
}
You can set a color to your ListItem initially and then you can change the color on a click event.
Hope this helps!
I am trying to update the list when my redux store changes but for some odd reason it isn't. I have to manually refresh the page to see my changes. Here's the snippet of my List component and rowRenderer.
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={visibleRequest.length}
>
{({ onRowsRendered, registerChild }) => (
<AutoSizer>
{({ height, width }) => (
<List
ref={registerChild}
className="List"
height={height}
rowHeight={listRowHeight}
onRowsRendered={onRowsRendered}
rowCount={rowCount}
rowRenderer={this._rowRenderer}
width={width}
/>
)}
</AutoSizer>
)}
</InfiniteLoader>
_rowRenderer = ({ index, key, style }) => {
const { loadedRowsMap, selected } = this.state;
const row = this.getDatum(index);
let content;
if (loadedRowsMap[index] === STATUS_LOADED) {
content = row;
} else {
content = (
<div className="placeholder" style={{ width: _.random(100, 200) }} />
);
}
return (
<PendingChat
key={key}
content={content}
style={style}
row={row}
{...this.props}
/>
);
};
Yeah, I ran into the same problem. Its because the references to your objects don't change when you do
const row = this.getDatum(index);
let content;
if (loadedRowsMap[index] === STATUS_LOADED) {
content = row;
}
Take a look at immutability.
Got a small project that I've been working on for past couple weeks. Basic premise is that there is a stepper with steps. User can click on each of them and get a description of what are his to-do's of each step.
There is also a button to complete the step. At the moment I am stuck on the part with making the button work properly.
What I want: When user clicks button to complete step, that stepper item changes its styling (This case classname) to appear as if its complete, and the button get disabled for next step.
I'm now at the point with many if/elses that I no longer have any clue how to finish this task and even if I was to rewrite the if's and else's in a different way I have no clue how as I am pretty new to react.
Some code snippets:
My parent component.
class UserPage extends Component {
state = {
currentStep: 1,
pendingApproval: false
}
clickHandler(e, step) {
e.preventDefault();
this.setState({ currentStep: step })
}
incrimentStep(e, step) {
e.preventDefault();
this.setState({ currentStep: step, pendingApproval: true })
}
render() {
return (
<Grid>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Stepper
clickHandler={this.clickHandler.bind(this)}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Steps currentStep={this.state.currentStep} steps={this.props.user.process.steps} />
<StepButton
incrimentStep={this.incrimentStep.bind(this)}
currentStep={this.state.currentStep}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
</Grid>
)
}
}
export default UserPage;
My child component that I am trying to fix:
class UserStepper extends Component {
render() {
const steps_length = this.props.steps.length;
let handler = this.props.clickHandler;
// let incompleteStepsArray = []
// let incompleteID = -1;
let pendingApproval = this.props.pendingApproval;
return (
<div id="stepper-container">
{this.props.steps.map(function (step, index) {
if (step.isDone) {
if (index !== steps_length - 1) {
return (
<div key={index} className="completed">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="completecontainer"
/>
<CircleCompleted />
</div>
)
}
else {
return (
<div key={index} className="completed">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="completecontainer"
/>
</div>
)
}
}
else {
{/* incompleteID = incompleteID +1; */}
{/* console.log(incompleteStepsArray) */}
if (index !== steps_length - 1) {
return (
<div key={index} className="incompleted">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="incompletecontainer"
/>
<CircleIncompleted />
</div>
)
}
else {
return (
<div key={index} className="incompleted">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="incompletecontainer"/>
</div>
)
}
}
})}
</div>
)
}
}
//Functional component to decide wether the stepper should be filled or left
blank (Complete or Incomplete)
const StepperFill = (props) => {
return (<div onClick={(e) => props.click(e, props.index)} className=
{props.class_name}>
<p>Step {props.index}</p>
</div>
)
}
const CircleCompleted = () => {
return (
<div className='circle_completed'>
<Icon.Group size='big'>
<Icon size='small' name='checkmark' color='green' />
</Icon.Group>
</div>
)
}
const CircleIncompleted = () => {
return (
<div className='circle_incompleted'>
</div>
)
}
export default UserStepper;
Sorry for the long code, no other idea how to show it otherwise that it would make sense as to what is happening. Any suggestions are appreciated.
Theres many ways you can refactor this but to disable the button a simple approach would be to add a isNextBtnDisabled to your state and pass it down to the button. Whenever you want your button disabled simple set that variable to true.
class UserPage extends Component {
state = {
currentStep: 1,
pendingApproval: false,
isNextBtnDisabled: false,
}
clickHandler(e, step) {
e.preventDefault();
this.setState({ currentStep: step, isNextBtnDisabled: this.state.pendingApproval })
}
incrimentStep(e, step) {
e.preventDefault();
this.setState({ currentStep: step, pendingApproval: true, isNextBtnDisabled: true })
}
render() {
return (
<Grid>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Stepper
clickHandler={this.clickHandler.bind(this)}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Steps currentStep={this.state.currentStep} steps={this.props.user.process.steps} />
<StepButton
incrimentStep={this.incrimentStep.bind(this)}
currentStep={this.state.currentStep}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
disabled={this.state.isNextBtnDisabled}
/>
</Grid.Column>
</Grid.Row>
</Grid>