for some reason my exit and new animation is not working. I would like new animation to start every time user click on different menu link. I have also tried with " animate='visible'" , and I have tried also to put directly over motion, and it still not doing exit or starting new animation. I am using .map and framer motion together. Can someone please check it out.
This is the code
Thanks
const [forMapping, setForMapping] = useState(wines)
function menuHandler(index, alt) {
setIsActive(index)
if (alt === 'wine') {
setForMapping(wines)
} else if (alt === 'rakia') {
setForMapping(rakia)
}
}
const variants = {
visible: i => ({
y: 0,
transition: {
duration: .7
}
}),
hidden: {
y: '40%'
}
}
<AnimatePresence>
{forMapping.map((item, index) => {
const {
name,
description,
alt,
imageSrc,
price,
glass_price,
iconSrc,
alc
} = item;
return (
<motion.div
exit={{y: '100'}}
viewport={{once: true}}
custom={index}
whileInView='visible'
initial='hidden'
variants={variants}
key={index}
className='item'>
<div className="image">
<Image
width={200}
height={400}
objectFit='cover'
src={imageSrc}
alt={alt}/>
</div>
<div className="info">
<div className="info-header">
<header>
{name}
</header>
<p className="price">
{price.toFixed(2)} EUR
</p>
</div>
<p className="description">
{description}
</p>
<div className="bottom">
<p>
{alc} %VOL
</p>
<div className='image-price'>
<Image
width={18}
height={20}
objectFit='cover'
src={iconSrc}
alt='wine glass'/>
<p className="black">
{glass_price.toFixed(2)} EUR
</p>
</div>
</div>
</div>
</motion.div>
)
})}
</AnimatePresence>
You should not use the loop index as the key in your motion.div. Since the indices (and thus the keys) will always be the same, it doesn't let <AnimatePresence> track when elements have been added or removed in order to animate them.
Instead use a value like a unique id property for each element. This way React (and AnimatePresence) will know when it's rendering a different element (vs the same element with different data). This is what triggers the exit and enter animations.
Here's a more thorough explanation:
react key props and why you shouldn’t be using index
Related
I created a simple countdown with Alpine JS, but when timer is 0, I have an error.
First declare in x-data the function appTimer.
<div x-data="appTimer()">
<div x-show="active">
<template x-if="countdown > 0">
<div>
<div>Counting down</div>
<div x-text="countdown"></div>
</div>
</template>
<template x-if="countdown === 0">
Countdown completed!
</template>
</div>
</div>
This code JS, here set active, countdown and window.setInterval.
<script>
function appTimer()
{
return {
active: true,
countdown: 5,
init() {
window.setInterval(() => {
if(this.countdown > 0) this.countdown = this.countdown - 1; console.log(this.countdown)}, 1000)
}
}
}
</script>
The issue is that you're putting text in the template instead of html, alpine tries to get the html defined in the template but doesn't find any so it gets confused, to fix the issue you simply need to wrap your message in an html element, such as:
<template x-if="countdown === 0">
<div>Countdown completed!</div>
</template>
see this stackblitz reproduction
I think it depends on your compare. You compare strict but i dont know if countdown is an integer. Try to compare with ==
<template x-if="countdown == 0">
function AlignFeature(){
const [allMessages, setAllMessages] = useState([]);
useEffect(()=>{
MessageService.getAllMessages().then(res=>{
setAllMessages(res.data);
});
}, []);
return (
<Container>
<Row>
<Col md={{ span: 6, offset: 0}}>
<div>
<div><Button color='warning'
onclick={MessageService.deleteChat}>
delete chat</Button></div>
<div className='chat'>
<div className='sentMes'>{
allMessages.map(message =>
<div key = {message.id}
className='sentMessages'>
<p
> {message.message} </p>
</div>
)}
</div>
</div>
<div style={{position:'static'}}>
<MessageForm />
</div>
</div>
</Col>
<Col md={{ span: 3, offset: 0}}> <UploadFiles /></Col>
</Row>
</Container>
);
}
export default AlignFeature;
properties of message are(id, message, receiverEmail, senderEmail)
after the map function i want to render message.message either left or right by checking if message.receiverEmail compares to an email stored in localStorage. if message.receiverEmail == localStorage.getItem('email') display right else display left
There are two ways of going about this problem my sources are Composition vs Inheritance and this question about updating parent state in react. This is also a good resource for learning about state.
From your question you are wanting to compare against data gained later and update the UI of a previously generated component based on that data. With react everything is based upon updating the state which updates the presented information hooks just provide a different way of doing this.
An example that fits into your current way of doing things (hooks) would be to pass an update function that will update the parent state to a lower level component.
function AlignFeature() {
const [allMessages, setAllMessages, localMessages, setLocalMessages] = useState([]);
useEffect(() => {
MessageService.getAllMessages().then(res => {
setAllMessages(res.data);
});
}, []);
return (
<Container>
<Row>
<Col md={{ span: 6, offset: 0 }}>
<div>
<div><Button color='warning'
onclick={MessageService.deleteChat}>
delete chat</Button></div>
<div className='chat'>
<div className='sentMes'>{
allMessages.map(message => {
if (localMessages.some(m => message.receiverEmail == m.receiverEmail)) {
// when email matches local
<div key={message.id} className='sentMessages'>
<p style={{ float: 'right' }}> {message.message} </p>
</div>;
} else {
// when email does not match local
<div key={message.id} className='sentMessages'>
<p> {message.message} </p>
</div>
}
}
)}
</div>
</div>
<div style={{ position: 'static' }}>
<MessageForm />
</div>
</div>
</Col>
<Col md={{ span: 3, offset: 0 }}> <UploadFiles updateLocalMessages={(lMessages) => setLocalMessages(lMessages)} /></Col>
</Row>
</Container>
);
}
export default AlignFeature;
<UploadFiles/> will need to assign the local emails or a list of email addresses using the passed in method which needs to be defined.
Note: It may be a good idea to consider case comparisons when comparing email addresses by converting them to both lower or upper case to ensure they match if that fits your use case.
I have been developing a multi-level dropdown menu component for the intranet I am building. Deciding to go with React on this I thought that I multi-level menu would be simple. Apparently not :)
What I have developed so far works quite well with one exception: When clicking on a NavLink the open menu items do not collapse.
All the CSS classes I have added are appearance only with no positioning statements. I have built this using a JSON source file and Reactstrap.
Here is the code for my component.
class MenuBar extends Component {
constructor(props) {
this.NavListItem = this.NavListItem.bind(this);
}
NavListItem = (item, level) => {
if (item.children.length > 0) {
return (
<UncontrolledDropdown direction={level!==0?"right":"down"} nav inNavbar className="menu menu-UncontrolledDropdown" key={"UCD_" + item.pageId}>
<DropdownToggle nav caret
className="menu menu-dropdown-toggle item title"
key={"DDToggle_" + item.pageId}
id={"DDToggle_" + item.pageId}
>
{item.title}
</DropdownToggle>
<DropdownMenu className="menu menu-dropdown-container">
<Nav>
{item.children.map((listItem) => this.NavListItem(listItem, level + 1))}
</Nav>
</DropdownMenu>
</UncontrolledDropdown>
)
}
else {
return (
<NavItem className="menu menu-item-container" key={"DDNavItem_" + item.pageId}>
<NavLink onClick={() => { this.props.updateCurrentPage(item) }} className="menu menu-link" key={"DDNavLink_" + item.pageId}>{item.title}</NavLink>
</NavItem>
)
}
}
render() {
const setIsOpen = (value) => {
this.setState({ isOpen: value })
}
const toggle = () => setIsOpen(!this.state.isOpen);
return (
<div className="row">
<div className="col-2 p-3 menu">
<div onClick={() => this.props.updateCurrentPage(null)}
className="align-top met-logo">
</div>
</div>
<div className="col col-md-6 offset-1 menu ">
<Navbar color="light" light expand="md" className="menu menu-bar">
<NavbarToggler onClick={toggle} className="menu menu-toggler" />
<Nav className="mr-auto" navbar>
{this.props.siteMap.siteMapData.map((link) => this.NavListItem(link, 0))}
</Nav>
</Navbar>
</div>
</div>
);
}
}
The only problem I have now is the open items do not close when an option is clicked. I would like preferably to make this stateless and put it in as a functional component ultimately but for now I am pulling Sitemap from Redux.
Thanks.
I could not find a solution but did come up with a functional workaround that takes very little resource.
In my MainComponent of the SPA I am hosting both the component as well as the current page component. What I did, when I realized that clicking off of the menu closed it, was to add a little snippet into the componentDidUpdate() function:
componentDidUpdate(){
//work around for menu not closing.
document.getElementById("rootDiv").click();
}
This effectively causes the menu to close once the new page has begun loading. Problem solved.
even after I use this it still showing up arrows
<div uib-timepicker ng-model="mytime" arrowkeys="false" show-meridian="false"></div>
Here's the plunker : https://plnkr.co/edit/j5JlXWsoldsj0iEdSUMY?p=preview
How to disable them ? Anyone knows? Their documentation states that arrows can be hidden. Is this a bug?
Angular Bootstrap timepicker plugin: link
Thank you
If you're talking about the little up and down arrows above and below the hours, minutes, and (optionally) seconds input fields, you actually want to set show-spinners="false" on the directive.
<div uib-timepicker ng-model="myDate" show-spinners="false"></div>
The arrowkeys setting is just for whether you can press up and down arrows on the keyboard while focused within the text field to increase or decrease the values.
Actually there is a small misunderstanding happened from our side regarding the arrowkeys attribute of uib-timepicker. Actually while setting arrowkeys="false" will not hide the arrow keys instead it will block the up and down arrow key events inside the text box. On setting arrowkeys="true", you can increment or decrement the time values by up and down arrow keys, on setting it to false it wont happen.
arrowkeys (Defaults: true) : Whether user can use up/down arrowkeys
inside the hours & minutes input to increase or decrease it's values.
To achieve your requirement you will need to go for a hack.
I don't know whether this is the best way or not, but what about hiding the up and down arrows. If this could solve your problem, I have attached a sample code.
<!doctype html>
<html ng-app="ui.bootstrap.demo">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular-animate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-sanitize/1.5.9/angular-sanitize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.3.0/ui-bootstrap-tpls.min.js"></script>
<script>
angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('TimepickerDemoCtrl', function ($scope, $log) {
$scope.mytime = new Date();
$scope.hstep = 1;
$scope.mstep = 15;
$scope.options = {
hstep: [1, 2, 3],
mstep: [1, 5, 10, 15, 25, 30]
};
$scope.ismeridian = true;
$scope.toggleMode = function () {
$scope.ismeridian = !$scope.ismeridian;
};
$scope.update = function () {
var d = new Date();
d.setHours(14);
d.setMinutes(0);
$scope.mytime = d;
};
$scope.changed = function () {
$log.log('Time changed to: ' + $scope.mytime);
};
$scope.clear = function () {
$scope.mytime = null;
};
});
</script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
.timepickercontainer .uib-timepicker .btn-link {
display: none;
}
</style>
</head>
<body>
<div ng-controller="TimepickerDemoCtrl">
<div class="timepickercontainer">
<div uib-timepicker ng-model="mytime" ng-change="changed()" arrowkeys="false" hour-step="hstep" minute-step="mstep" show-meridian="false"></div>
</div>
<pre class="alert alert-info">Time is: {{mytime | date:'shortTime' }}</pre>
<div class="row">
<div class="col-xs-6">
Hours step is:
<select class="form-control" ng-model="hstep" ng-options="opt for opt in options.hstep"></select>
</div>
<div class="col-xs-6">
Minutes step is:
<select class="form-control" ng-model="mstep" ng-options="opt for opt in options.mstep"></select>
</div>
</div>
<hr>
<button type="button" class="btn btn-info" ng-click="toggleMode()">12H / 24H</button>
<button type="button" class="btn btn-default" ng-click="update()">Set to 14:00</button>
<button type="button" class="btn btn-danger" ng-click="clear()">Clear</button>
</div>
</body>
</html>
Just add a div with a class as the container of time picker say timepickercontainer
then set
.timepickercontainer .uib-timepicker .btn-link {
display: none;
}
In my current meteor.js project, user can create a project and add data nodes to it. I'm using D3 to display the nodes in force graph. When they click a particular node from the graph, the corresponding text in the side panel must be highlighted. For this, I need to track with node is selected. But, I don't want to store a "selected" field on the database.
I'm using this data transform to add selected field right now -
/lib/routes.js
Router.route('/project/:code', {
name: 'projectPage',
data: function() {
return {
project: Projects.findOne({code : this.params.code}),
nodes: Nodes.find({project: this.params.code}, {transform: function (doc) {
doc.selected = false;
return doc;
}})
}
}
});
The template is /client/templates/projectPage.html
<template name="projectPage">
<div class="project-page page">
<h3>{{project.title}}</h3>
<p>{{project.summary}}</p>
<div class="work-area">
<div class="map-space">
{{> nodeDisplay nodes=nodes}}
</div>
<div class="type-space">
{{> typeDisplay nodes=nodes}}
</div>
</div>
</div>
</template>
<template name="nodeDisplay">
<div id="svgdiv"></div>
</template>
<template name="typeDisplay">
{{#each nodesData}}
<p>{{text}}</p>
<br/>
{{/each}}
</template>
The click event is handled /client/js/projects.js
Template.nodeDisplay.events({
'click .node':function(event, template){
/*remove previous selection*/
d3.selectAll('.selected circle').attr("r",32);
d3.selectAll('.selected').each(
function(d){
d.fixed = false;
d3.select(this)
.classed('selected', false);
}
);
/*add new selections*/
d3.select(event.currentTarget)
.classed("selected", true)
d3.selectAll('.selected circle').attr("r",40);
var selected_id = $(event.currentTarget).data("id");
Nodes.update(selected_id.toString(), {$set: {selected: true}});
}
});
However, this updates the database to include the "selected" field.
Is there a better way to do this and keep reactivity?
The meteor way is to use session variables and helper functions.
So instead of
Nodes.update(selected_id.toString(), {$set: {selected: true}});
use
Session.set("selected_node", this._id);
and an accompnying helper in Template.typeDisplay.helpers
isNodeSelected: function() {
if(Session.get("selected_node") === this._id) {
return "selected"
}
}
in the template displaying each node (this code assumes that you want to select the corresponding text in the typeDisplay by applying the classname 'selected'):
<template name="typeDisplay">
{{#each nodesData}}
<p class="{{isNodeSelected}}">{{text}}</p>
<br/>
{{/each}}
</template>