framer-motion: element stops animating when changing state the 3rd time - reactjs

Whenever addStory() is called, it displays and animates text, but on the 3rd time it only displays the text and not animate it. I tried changing the text and when I hit Ctrl S it animated it. How do I keep animating them whenever they are shown? I've been trying to solve this problem since yesterday but could not find a solution.
sandbox https://codesandbox.io/s/frosty-smoke-nt0we?file=/src/components/Game.tsx
ps: addStory() displays different text whenever it is called, so it should re-render and animate.
const addStory = (newStory: string) => {
setStory(
<>
{newStory === undefined ? (
<span>{story}</span>
) : (
<>
{newStory.split('\n').map((str, index) => (
<motion.p
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
>
{str.length > 1 && '=> '}
{str}
</motion.p>
))}
</>
)}
</>
);
};
the addStory() function is called like this
const whichChoice = () => {
if (choice === 'a1') {
return mainStory.a1;
} else if (choice === 'a2') {
return mainStory.a2;
} else if (choice === 'b1') {
return mainStory.b1;
} else if (choice === 'b2') {
return mainStory.b2;
} else if (choice === 'c1') {
return mainStory.c1;
} etc......
else return;
};
useEffect(() => {
const narrate = whichChoice();
addStory(narrate);
}, [choice]);
the choice is changed using onClick via useState hook
the mainStory.a1 etc, returns a string from an object. It looks like this:
export const mainStory: any = {
a: '=> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
a1: '\n Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
a2: '\n nisi ut aliquip ex ea commodo consequat',
b1: '\n Duis aute irure dolor in reprehenderit \n in voluptate velit esse cillum dolore eu fugiat nulla pariatur \n Excepteur sint occaecat cupidatat non proident',
b2: '\n sunt in culpa qui officia \n deserunt mollit anim id est laborum. \n Sed ut perspiciatis unde omnis iste natus \n voluptatem accusantium doloremque laudantium'
etc...
}

I think the duration property in motion.p indicates that the animation runs from 0secs to 1secs. You might be looking for transition={{yoyo: 10}}
EDIT
Just understood, that its not triggering on consecutive renders, it seems the react DOM tree gets updated with each state change, for this, Framer needs an AnimatePresence to inform framer that the tree has changed.
Reference : https://www.framer.com/docs/animate-presence/

Related

how to change the position of Checkbox in sweetalert 2

I'm looking for a way to find if it is possible to change the position of the checkbox in sweetalert 2 from the center to the upper position? I used this code:
(async () => {
const { value: accept } = await Swal.fire({
title: 'Terms and conditions',
input: 'checkbox',
inputValue: 1,
inputPlaceholder:
'Yes, I agree to receive an email, including educational materials, product and company announcements, and community even information, from T&C. I can unsubscribe at any time. I would like to opt into receiving marketing communications as outlined above.',
confirmButtonText:
'Continue <i class="fa fa-arrow-right"></i>',
inputValidator: (result) => {
return !result && 'You need to agree with T&C'
}
})
if (accept) {
Swal.fire('You agreed with T&C :)')
}
})()
The result of this code is this:
I would like to change the checkbox from the center to the position of the word "Yes".
Use the customClass param to achieve the custom placement for the input:
Swal.fire({
input: 'checkbox',
inputPlaceholder: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
customClass: {
input: 'top-checkbox'
}
})
.top-checkbox input {
align-self: flex-start;
margin: 3px 10px 0 0 !important;
}
<script src="https://cdn.jsdelivr.net/npm/sweetalert2#11"></script>

Change position of objects inside an array

I have a functional component which has a useState that contains the information i want to display and the images.
const [images, setImages] = useState(
[
{
"index": 0,
"img": Volleyball,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
},
{
"index": 1,
'img': Gaming,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
},
{
'index': 2,
'img': Coding,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
}
]
)
And this is the jsx that I have in the return body with a left button that prevImage function.
return (
<div className={classes.gallary}>
<div className={classes.direction}>
<button className={classes.leftArrow} onClick={prevImage}>Left</button>
<button className={classes.rightArrow} >Right</button>
</div>
<div className={classes.images}>
{images.map(current => {
return (
<div key={current.index}>
<img src={current.img} alt="" className={classes.image} />
</div>
)
})}
</div>
</div>
);
Here is the prevImage function :
const prevImage = () => {
const currentImgArray = [...images];
const sendToBack = currentImgArray.push(currentImgArray.pop());
setImages(sendToBack)
}
This is what it looks like before clicking the button and what I am trying to do is to place the first object as the last object in the array . If the function is called I get : "TypeError: images.map is not a function"
As has been pointed out, array::push returns the new length of the array, not an array. Basically you need to rotate the array left/right.
const prevImage = () => {
// moves last image to front of array
setImages(images => [images[images.length - 1], ...images.slice(0, -1)]);
};
const nextImage = () => {
// moves first image to end of array
setImages(images => [...images.slice(1), images[0]]);
};
Array.push returns new length of the array, so concat function is to be use on here:
const prevImage = () => {
const currentImgArray = [...images];
const sendToBack = currentImgArray.concat(currentImgArray.pop());
setImages(sendToBack)
}
Updated ans:
const prevImage = () => {
setImages(prevState => {const popArr = prevState.pop();
return prevState.concat(popArr);
})
}

In Angular JS, is there a way to watch for changes to the DOM without using scope.watch?

I am attempting to create an angularjs bootstrap accordion that scrolls the accordion to the top when opened.
These solutions are close to what I would like to do:
AngularJS / ui-bootstrap accordion - scroll to top of active (open) accordion on click
https://www.peterbouda.eu/making-an-angular-ui-bootstrap-accordion-scrolling-to-top-when-opening.html
However, they use a timeout or scope watches. I would like to avoid using these unless absolutely necessary.
Is there a way to accomplish this without using $watch or setTimeout?
Here is a plunk of what i am trying to do, this is using the $watch: https://plnkr.co/edit/XQpUdrdjqaCGom4L9yIJ
app.directive( 'scrollTop', scrollTop );
function scrollTop() {
return {
restrict: 'A',
link: link
};
}
function link( scope, element ) {
scope.collapsing = false;
var jqElement = $( element) ;
scope.$watch( function() {
return jqElement.find( '.panel-collapse' ).hasClass( 'collapsing' );
}, function( status ) {
if ( scope.collapsing && !status ) {
if ( jqElement.hasClass( 'panel-open' ) ) {
$( 'html,body' ).animate({
scrollTop: jqElement.offset().top - 30
}, 500 );
}
}
scope.collapsing = status;
} );
}
The directive can be simplified to:1
app.directive( 'scrollTop', function scrollTop($timeout) {
return {
restrict: 'A',
link: postLink
};
function postLink(scope, elem, attrs) {
elem.on("click", function(e) {
if (scope.status.isOpen) {
$timeout(function() {
$( 'html,body' ).animate({
scrollTop: elem.offset().top - 30
}, 500 );
});
}
});
}
})
<uib-accordion>
<div heading="Section Title" is-open="status.isOpen"
ng-repeat="section in vm.sections"
scroll-top
uib-accordion-group>
<uib-accordion-heading>
<div ng-class="{isOpen: vm.isOpen}">
<h3>{{section.sectionTitle}}</h3>
<p>{{section.sectionSubHeader}}</p>
</div>
</uib-accordion-heading>
<div class="clearfix">
<b>Index+1={{$index+1}}</b>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</div>
</uib-accordion>
The $timeout is necessary because the browser needs to render the DOM with the newly opened and closed elements before it can compute the proper scroll offset.
The DEMO on PLNKR
I have found a way to do this from the controller.
I added a function that is triggered on ng-click to report the is-open status of the accordion.
Using the component lifecycle hook $doCheck I was able to watch for changes to the state of vm.isOpen. $doCheck runs on the end of every digest cycle, so I did not need to set a $scope.watch or $timeOut
The $doCheck runs essentially the same code as the directive in the question
app.controller('homeController', function($state, $element, sections, $transitions) {
var vm = this;
vm.$onInit = function() {
vm.sections = sections.getSections();
};
function updateOpenStatus() {
vm.collapsing = false;
vm.isOpen = vm.sections.some(function(item) {
return item.isOpen;
});
}
vm.$doCheck = function() {
if (vm.isOpen) {
var elem = $element.find('.panel-collapse');
var status = elem.hasClass('collapsing');
if (vm.collapsing && !status) {
var parentElem = elem.closest('.panel-open');
if (elem.parent().hasClass('panel-open')) {
$('html,body')
.stop()
.animate({
scrollTop: parentElem.offset().top - 52
}, 'fast');
}
}
vm.collapsing = status;
}
};
});
I updated the uib-accordion to call the function in the controller
<uib-accordion>
<div heading="Section Title" is-open="section.isOpen" ng-repeat="section in vm.sections" scroll-top uib-accordion-group>
<uib-accordion-heading>
<div ng-class="{isOpen: section.isOpen}" ng-click="vm.toggleOpen()">
<h3>{{section.sectionTitle}}</h3>
</div>
</uib-accordion-heading>
<div class="clearfix">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</div>
</uib-accordion>
Updated Plnkr: https://plnkr.co/edit/5EqDfmVOa0hzFfaQqdI0?p=preview

Api-platform accessing embedded relations in hydra:member

I'm using Api-Platform and React as client.
I have a Forum entity which contains other objects : an author (User entity) and a category (Category entity).
I embedded these entities with #Groups annotations to retrieve them more easily.
For instance, when I fetch /api/forums/ on Postman I get this result :
{
"#context": "/api/contexts/Forum",
"#id": "/api/forums",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/forums/79",
"#type": "Forum",
"id": 79,
"subject": "Sujet numéro 0",
"message": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"category": {
"#id": "/api/categories/183",
"#type": "Category",
"title": "Category 2"
},
It seems good : we can see my category object is linked to Forum in hydra:member array, and a "title" field.
But I noticed that the result I really get is different in my react app for all my nested objects.
I only get the IRIs..
For instance with the same request :
...
fetch('forums')
.then(response =>
response
.json()
.then(retrieved => ({ retrieved, hubURL: extractHubURL(response) }))
)
.then(({ retrieved, hubURL }) => {
retrieved = normalize(retrieved);
...
I get this result :
{
"#context": "/api/contexts/Forum",
"#id": "/api/forums",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/forums/79",
"#type": "Forum",
"id": 79,
"subject": "Sujet numéro 0",
"message": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"category": "/api/categories/183",
Every other fields have disapeared on "category", and I don't get why.
Could anyone help me with that ?
Thanks in advance
I found the solution (how silly of me), so I share you the tip :
In src/utils/dataAccess file, there is a normalize function :
export function normalize(data) {
if (has(data, 'hydra:member')) {
// Normalize items in collections
data['hydra:member'] = data['hydra:member'].map(item => normalize(item));
return data;
}
// Flatten nested documents
return mapValues(data, value =>
Array.isArray(value)
? value.map(v => get(v, '#id', v))
: get(value, '#id', value)
);
}
It appears that this function is called after each fetch :
return fetch(id)
.then(response =>
response
.json()
.then(retrieved => ({ retrieved, hubURL: extractHubURL(response) }))
)
.then(({ retrieved, hubURL }) => {
//retrieved = normalize(retrieved);
dispatch(loading(false));
dispatch(success(retrieved));
....
As you can see, I just commented the normalize line (useless in my case). Do that wherever it is needed, and everything should work again !

How to pass an Argument to Method in Perl

I'm trying to use this Perl Method: HTML::Highlight - A module to highlight words or patterns in HTML documents.
The methode itself isn't acctualy the problem but the way how I can pass a Attribute.
Example which works:
use HTML::Highlight;
$text = 'Lorem ipsum Velit ullamco ex anim quis Duis laboris ut proident velit eu dolor Ut amet proident aliqua minim officia sunt commodo veniam dolor id reprehenderit reprehenderit non nulla incididunt mollit exercitation minim commodo ut quis laboris ex proident.';
# create the highlighter object
my $hl = new HTML::Highlight (
words => [
'ex',
'ul',
],
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);
my $hl_document = $hl->highlight($text);
print $hl_document;
What I'd like to do is something like this:
use HTML::Highlight;
$text = 'Lorem ipsum Velit ullamco ex anim quis Duis laboris ut proident velit eu dolor Ut amet proident aliqua minim officia sunt commodo veniam dolor id reprehenderit reprehenderit non nulla incididunt mollit exercitation minim commodo ut quis laboris ex proident.';
# create the highlighter object
#keywords = "ex", "ul";
my $hl = new HTML::Highlight (
words => #keywords,
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);
my $hl_document = $hl->highlight($text);
print $hl_document;
As you can see in the code snippet above, I'd like to pass an existing array to the object.
How can I do that correctly?
Currently I get an exeption like that:
HTML::Highlight - "words" and "wildcards" parameters must be references to arrays at C:\Skripts\Perl\syntax_highlight.pl line 8.
As said in error message, pass a reference:
my $hl = HTML::Highlight->new(
words => \#keywords,
# here __^
wildcards => [
undef,
],
colors => [
'red; font: bold',
],
debug => 0
);

Resources