Jump to content
GreenSock

Stephen Marshall

Members
  • Posts

    8
  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

Stephen Marshall's Achievements

2

Reputation

  1. Got it! I did play around with moving setState calls for filtered data sets into the handle methods...this did not solve the null refs issue, but sounds like best practice to implement going forward! It may be necessary in my case to run the filters when fetching the data, but I agree 100% that I don't need to store more than one array of data in application state! In any case, I've arrived at a solution to share with the community! Remove the null refs from your array! I did this using the loDash _.compact() method, which removes falsey values from an array, and I implemented this code when setting my animation targets. You could probably do this in componentDidUpate, but with how often that lifecycle method is invoked I think it would be overkill, as opposed to doing it when you call the animations and create a new timeline instance. Should work unless you have booleans with deliberately false values, and you are trying to animate them lol ? I've updated my codepen here, and you can see this at work: https://stackblitz.com/edit/react-lzbh3m?embed=1&file=map.js To explain a bit more what you're dealing with when the React Docs say: To me, that's not quite what I was seeing? Let's say you have an array of data, and you use .map() to loop through and generate a <div> for each one that you want to animate...and the end-user can interact with your app , sending new props from a parent that updates your child component (without unmounting and remounting, which would probably work fine since the refs would be re-created?) If you were to log your refs, something like console.log("Refs: " + this.myArrayOfDivs); ...and you were going back to a previously selected set of data, you would see something like this in the console: [object HTMLDivElement], [object HTMLDivElement], [object HTMLDivElement], ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, You can see the null values ,,,,,,,, appear to be appended to the end of the array, instead of returning a separate array/returning the array twice, first with null values and then with the actual DOM nodes? I suspect that checking for them, killing, and restarting the animations might not work in this circumstance? So - how do you get the null values out of the array? There's lots of ways to look for them, including other loDash methods like _.isEmpty, _.isNull etc...and loDash had several methods I tried first that will return a new array without the falsey values (_.pickBy or _.omitBy are good examples). These did let me client-side filtering methods work, but would somehow mutate the array of refs so the animation target would be obfuscated (no "null target" error, they just didn't work?) What worked for me looks something like this: myAnimationMethod = () => { this.tl = new TimelineMax(); //when a new timeline instance is created, remove any null or falsey values from the array of refs const myArrayOfTweens = _.compact(this.myRefToAnArrayOfDOMElements) //animate them! this.tl.staggerTo(myArrayOfTweens, 1, {//do stuff}, etc. } since _.compact(myArray) removes the falsey values without returning a new array, you're in business! Go figure it'd be a one-liner... Thanks again Rodrigo! Until the next!
  2. Thanks Rodrigo these are good ideas, and I'll take them out for a spin ? I like the idea of filtering the data on demand instead of doing so by default, but I'm afraid it may be necessary? Got to work this morning and I have current data...yesterday I was running 4-8 hours behind most of the day. With unpredictable amount of lag in updating our databases, my data-fetching method does its filtering, and then something likeif(past5minData.length === 0){//move onto the next one} - that way the visualization always has something to load into onTheMap and then animate it. Perhaps I can run the filters, but move the setState calls into the client-side data selection methods so I'm not storing duplicate data in application state? I'll update the thread once I've found a way to get this working 100% of the time!
  3. Thanks again @Rodrigo as always - you're the king! If I could ask another question concerning one final bug...this time it's a kind-of edge case with ref callbacks in React...getting an error about 'cannot tween a null target' I had mentioned: First of all - I've figured out onClick event handlers, and have added this my codepen here: https://stackblitz.com/edit/react-lzbh3m?embed=1&file=map.js So on to client-side filters...to avoid posting huge blocks of code, I'll leave that for the codepen, but a general description of what I'm trying to do: In production, my map fetches the newest data every 5 minutes. However, we've had some lag in updating our databases, and sometimes I don't have data that current! As a result, I go back several hours, and then use the array filter method to split up the JSON response into smaller arrays like past 15, past 30 minutes, past hour etc. The end-user can select which data set they want to see. I call a setState for all of these sub-arrays when I fetch the data...but ultimately the props passed to the map component are an array called onTheMap - so if the user picks one data set versus the other, a method is invoked like so: handleData15Min = () => {this.setState({ onTheMap: this.state.past15min}) } I don't think this is best practice to turn this.state.onTheMap into this.state.pastXXXminutes, not sure if this is mutating state? Because this is React issue as well I do have a post going in stack overflow too, FYI. Nonetheless, this works fine, except if a data set has been previously selected! If the user picks 15 minutes, then 30 minutes...then goes back to 15 minutes, the app crashes! Unfortunately, I could not reproduce this exact error in stackblitz. I did implement some client-side filtering of the data, and this crashes every time, not just on 2nd time the user selects a sub-array of data, but does not give me a 'null target' error like I get in production. Per the React docs, I believe this is what is happening: So - if I create references to the map markers like so: ref={e => (this.MapMarkers[i] = e)} I can call the animations in componentDidMount, and everything works fine (unless the end-user behavior described above happens - d'oh!) I've tried a lot of things and spent more time than I'd like on this problem - this is the final bug before deployment! Seeing this in the React docs made me think maybe there's some tricks to ref callbacks with GSAP that I'm missing - hoping someone can help! The latest thing I tried was, like the quote above, to define the ref callback as a bound method on the class, like so: constructor() { super(); this.setMarkerRef = i => e => { this.MapMarkers[i] = e; }; ... And then bind in the constructor: this.setMarkerRef = this.setMarkerRef.bind(this); And then call ref={this.setMarkerRef} to get the DOM element inside of render...which I must be doing something wrong because the refs log as undefined. So...with this approach, I can pick any dataset I want, as many times as I want - no crash - but also no animations due to the refs being undefined...but no error about "cannot tween a null target" either it just renders everything? I feel like I'm getting close - what am I missing here?
  4. This sounds about right...around the end of the day yesterday I was making some progress by adding some conditional logic to the componentDidUpdate method - i'll update the codepen shortly, but this is mostly solved and the range slider is working! One more question remains...you said: How do I pause the timeline, register the current progress, and restart it? below I am using this line of code below, but this will in fact restart the timeline? this.tl.pause(this.tl.progress()).play(); EDIT: I figured this one out too, I was close...all I needed was: this.tl.pause().play(); Other than that just about everything is working! I may have to pick your brain about handling onClick events to get the cards to pop-up when you click on a map marker...and some weird business with client-side filters I built. As always I appreciate the help and feedback! Here is my code in componentDidUpdate() if anyone is curious...this from my production code, where I have a method called animateMapMarkers that is called conditionally. Just about everything works other than pausing and resuming the timeline? PS _.isNull, _.isEqual, and _.isUndefined are from LoDash - very helpful library for this sort of thing! componentDidUpdate(prevProps, prevState) { console.log("===============\nUpdated\n=============="); if (_.isNull(this.tl)) { console.log("Timeline is null on update, calling animation"); this.animateMapMarkers(); } else if (_.isUndefined(this.tl)) { console.log("Timeline Undefined on update, calling animation"); this.animateMapMarkers(); } else if ( this.tl.isActive() && //onTheMap is the array of data passed down the component tree _.isEqual(prevProps.onTheMap, this.props.onTheMap) && //the state of the viewport has changed - zoom/pan etc !_.isEqual(prevState.viewport, this.state.viewport) ) { console.log( "Timeline isActive on update,viewport changed, no new data - pause and resume" ); //This does not break...but timeline restarts? How can I change this line of code below? this.tl.pause(this.tl.progress()).play(); } else if ( this.tl.isActive() && //When there's new data...kill and restart the timeline !_.isEqual(prevProps.onTheMap, this.props.onTheMap) ) { console.log( "Timeline isActive on update, new data and props, kill and restart" ); //kills the timeline when data set updates this.tl.pause(this.tl.duration()).kill(); this.animateMapMarkers(); } }
  5. I wanted to reply to myself here (I was formerly @UnioninDesign until I convinced my company to get me a Club Greensock membership) that I never really solved the issue, but I did find a working piece of code... componentWillUnmount() { //kills the timeline when data set updates this.tl.pause(this.tl.duration()).kill(); } So...if you are going back to fetch new data...and in this example, you would be doing that higher up in your component tree, and passing props to this component...Upon receiving new data, React will pass the new props down the component tree, causing the component to unmount before it re-renders with the new props. Those lines of code up there were a big help! But...they do not give you control over how to smoothly transition from one data set to the next! This will kill the timeline as soon as the new data is available, and start a new one with the new data set. Works - yes! Elegant? Maybe kinda sorta? PS I've continued a similar thread here if anyone is interested or curious!
  6. Hi friends! It's been many months since my last post...and this post piggybacks off this one here: Before @Rodrigo was _super_ helpful! I decided to start a new thread since that one was getting rather long and went off in a lot of different directions (Hint: I was pretty new to GSAP and React etc...PS at that time @UnioninDesign was me before I had my boss get us an enterprise license - same guy!) I also had _lots_ of code in there but no working demo...I've made one in stackblitz that replicates many of the troubles I am experiencing: https://stackblitz.com/edit/react-lzbh3m?embed=1&file=map.js I do feel that these issues are the results of unpredictable behavior from 3rd party component libraries (react-map-gl), and React's component lifecycle methods for class components...what I am hoping for is that someone here may be able to suggest some methods to help keep track of which timeline instance is occuring, and are there ways to keep that timeline instance intact with pause() and resume() methods etc? Maybe some new bleeding edge React Hooks solution for GSAP lol? If you open the codepen, I am trying to simulate what one of my live booking maps looks like...every few seconds a map marker with some data pops up, with a card/tooltip showing additional data. Of course, I am using sample data, and the timeline repeats infinitely instead of fetching new data on an interval (ask me about that if you're curious!) **Issues: ** In the codepen, everything looks like it's working...until the user interacts with the map! Any zooming or panning causes the timeline to go all haywire! Map markers and cards tend to double in speed, it seems like the wrong ones are showing up...sometimes they try to render multiple times quickly I think what is going on is that the state of the viewport updates, calling the componentDidUpate method again, which in turn fires the animations again...without proper garbage collection things get weird? Tweens are being called midway through their cycle etc? I've tried to add some code to the method that updates the viewport - not working - any ideas for something I might try besides this.tl.pause().resume()? //Mapbox viewport for resize, zoom pan etc _onViewportChange = viewport => { //TODO timeline restarts on viewport change with zoom, panning... //Always runs this function on initial load, tl will be null if (_.isNull(this.tl)) { console.log("tl is null on viewport change"); this.setState({ viewport }); } else { //if there's a valid timeline instance, pause and resume it console.log("tl is valid on viewport change, pause and resume"); //Below does not work this.tl.pause().resume(); this.setState({ viewport }); } }; Normally I call the animations in the componentDidMount method, but in this case the timeline will not start and all markers render at once. I believe the map calls the method to update the state of the viewport immediately onload, that way the map gets the initial state for zooming, which coordinates are at the center of the screen etc...and this kills the original timeline instance...hence calling a new one again in componentDidUpdate? Timeline Controls / Range Slider: like the original post I referenced, @Rodrigo created pen with a working range slider that gives the user feedback of where you are at in the timeline: https://stackblitz.com/edit/react-rc-slider-gsap?file=index.js This has been working for several months! Thank you! However, I'm making a new world map using a different library (react-map-gl ) and things are wonky! In the codepen, you can click and drag along the range slider and it works! the timeline onUpdate method is creating an infinite loop! in this case the timeline instance is undefined...about 60x per second...and chrome crashes hard! So you have a nearly working slider, but the dot does not move along with the timeline as it is not getting a value between 0 and 1 for the progress() method? Play, Pause buttons etc work...but only if the original timeline is running. After any zooming or panning (and a few other state updates in the production app that trigger a re-render) these buttons no longer function as expected. So far I've also tried to write a debugging method that I've called in various places when the component mounts or updates, and I've also called this method below inside the timeline's onUpdate - not really get any results? PS These are LoDash methods to check if the timeline instance is still null (initial state) or undefined (something has updated...) timelineProgressChecker() { if (_.isUndefined(this.tl)) { console.log("Timeline Undefined"); } else if (_.isNull(this.tl)) { console.log("Timeline is null"); } else { console.log("Timeline Progress? " + this.tl.progress()); } } Would appreciate any help - whether that's refactoring my React code, or maybe some GSAP methods I didn't know about, or did not use properly!
  7. Hello and thanks for the speedy reply...a few follow-up questions as I have not resolved the error yet: 1. if I use the method below, React throws a warning about not actually referencing 'DrawSVGPlugin' in your code, since in your animation code you would be using something like drawSVG: "100%" etc. Is that correct? In React at least, that syntax would only work if I was importing and using a component named <DrawSVGplugin/> somewhere in the same js or jsx file? import DrawSVGPlugin from "../../gsapPlugins/DrawSVGPlugin.js"; 2. I tried adding the files from several different folders and still get the same error, but with many versions I'm not sure I got the right one? The unzipped download has an src directory, and inside that you have src/esm <-- is that the es6 module version of the files? also tried src/bonus-files-for-npm-users...both of these give me the same error I've described. Same error with the files in src/uncompressed 3. I also tried the minified version, and I'm getting the error(s) below and the page won't load. It seems counter-intuitive to add a minified version of TweenLite to my gsapPlugins directory when Tweenlite already exists in node_modules directory via npm install gsap? I tried adding the TweenLite.min.js file to this same directory anyway but got the same errors, with some extra goodies too: Anyway, since the Dev Tools were working at one point, I'll play around with DrawSVG a bit more over the weekend in case this is what we call in the business a 'pebkac' issue (problem exists between keyboard and chair - lol). Will keep everyone updated if I figure it out!
  8. Rather than making a new thread I wanted to chime in here, very similar issue about 6 months after these posts ...using React. PS all of my previous posts were under @UnioninDesign but I've since convinced my boss to purchase the business green license - woo hoo! For both GSdevtools and drawSVG plugin, I get the following error: ``` Uncaught SyntaxError: Unexpected token < ``` I get that error on line 1 for both files? from my research on this error this usually points to some kind of importing or bundling issue? I'm using create-react-app...which I agree with OSUBlake does not give you much visibility of the bundling process! - Other posts in this thread are spot on - depending on your deployment flow, the bonus plugins cannot be dropped into the node_modules/gsap directory...these are dropped if you run 'npm run build' or in my case to sync to an AWS S3 bucket, npm run build && aws s3 sync /build <yourBucketNameURL>...you'll get 404 errors in production, the files are indeed not available. - while the NPM usage in the docs are very helpful, I've also tried the approach of adding a directory called 'gsapPlugins' to my src folder, and then importing the files like so (the relative filepath is correct FYI - thanks to vsCode!). Still get the same error? import "../../gsapPlugins/DrawSVGPlugin.js"; - Here's the weird part...I did get the GSAP dev tools working, but never got this error to go away? I'm wondering if I'm just not using drawSVG properly? I'll save that for a different thread when I have time to make reduced codepen, but curious if anyone has seen the Uncaught SyntaxError, and were able to get it cleared out? Excited to get these new plugins added to the toolbelt - any help is appreciated!
×