How to stop React-spring from running an animation on re-render - reactjs

I am using react-spring to animate my components. But I want to only run the animation when mounting then the leaving animation when unmounting. But my animation runs everytime a component re-renders. every prop change causes the animation to run again which is not how I expect it to work. thanks for the help in advance.
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react'
import { Transition, animated, config } from 'react-spring/renderprops'
class Animation extends React.Component {
constructor(props) {
super(props)
this.shouldAnimate = props.shouldAnimate
}
render() {
const Container = animated(this.props.container)
return (
<Transition
items={this.props.items}
native
initial={this.props.initial}
from={this.props.from}
leave={this.props.leave}
enter={this.props.enter}
config={config.gentle}
>
{(visible) =>
visible &&
((styles) => (
<Container
style={styles}
{...this.props.containerProps}
>
{this.props.children}
</Container>
))
}
</Transition>
)
}
}
export default Animation

I have found a solution to my very question after carefully looking at my component and logging the life-cycle method calls. I found out that each prop change was causing creation of a new container component and therefore causing the component to unmount and remount which in turn caused the animation to play. and the solution was easy after realizing this. I just changed my file to this and now it works just fine.
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react'
import { Transition, animated } from 'react-spring/renderprops'
class Animation extends React.PureComponent {
constructor(props) {
super(props)
this.shouldAnimate = props.shouldAnimate
this.container = animated(this.props.container)
}
render() {
const Container = this.container
return (
<Transition
items={this.props.items}
native
initial={this.props.initial}
from={this.props.from}
update={this.props.update}
leave={this.props.leave}
enter={this.props.enter}
>
{(visible) =>
visible &&
((styles) => (
<Container
style={styles}
{...this.props.containerProps}
>
{this.props.children}
</Container>
))
}
</Transition>
)
}
}
export default Animation

Related

How to create a new ref for each component instance

How to create a ref for each instance of a component
I've extracted some code into it's own component. The component is a PlayWhenVisible animation component that plays/stops the animation depending on whether the element is in view.
I'm creating a ref inside the component constructor but since I'm getting some lag when using 2 instances of the component I'm wondering if I should create the refs outside the component and pass them in as props or whether there's a way to create a new instance for each compoenent instance.
import VisibilitySensor from "react-visibility-sensor";
class PlayWhenVisible extends React.Component {
constructor(props) {
super(props);
this.animation = React.createRef();
this.anim = null;
}
render() {
return (
<VisibilitySensor
scrollCheck
scrollThrottle={100}
intervalDelay={8000}
containment={this.props.containment}
onChange={this.onChange}
minTopValue={this.props.minTopValue}
partialVisibility={this.props.partialVisibility}
offset={this.props.offset}
>
{({ isVisible }) => {
isVisible ? this.anim.play() : this.anim && this.anim.stop();
return (
// <div style={style}>
<i ref={this.animation} id="animation" className={this.props.class} />
);
}}
</VisibilitySensor>
);
}
}
The issue was caused by the VisibilityChecker component which was overflowing the container and causing it to be erratic when firing.

react-google-maps StandaloneSearchBox unmounting

I am trying to use the StandaloneSearchBox Component from https://www.npmjs.com/package/react-google-maps
After looking at the docs and some other answers I implemented the component like this:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withScriptjs } from "react-google-maps";
import StandaloneSearchBox from "react-google-maps/lib/components/places/StandaloneSearchBox";
import { Input } from "semantic-ui-react";
import API_KEY from "../config/googleAPIkey";
class AddressSearchbox extends Component {
constructor(props) {
super(props);
this.searchboxRef = null;
}
onSearchBoxMounted = ref => {
this.searchboxRef = ref;
};
onPlacesChanged = () => {
const places = this.searchboxRef.getPlaces();
this.props.onPlaceSelect(places[0]);
};
render() {
const Searchbox = withScriptjs(props => (
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
onPlacesChanged={props.onPlacesChanged}
>
<Input
type="text"
placeholder="Type address or google place name"
icon="search"
/>
</StandaloneSearchBox>
));
return (
<Searchbox
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${API_KEY}&v=3.exp&libraries=geometry,drawing,places`}
loadingElement={<div style={{ height: `100%` }} />}
onPlacesChanged={this.onPlacesChanged}
onSearchBoxMounted={this.onSearchBoxMounted}
/>
);
}
}
AddressSearchbox.propTypes = {
onPlaceSelect: PropTypes.func.isRequired
};
export default AddressSearchbox;
I use the component in a signup form where all the other input fields update the state on input change causing re-rendering of the whole form.
When the AddressSearchbox component gets re-rendered it seems that it gets unmounted and then remounts causing flickering. The component itself works fine.
EDIT: When logging the ref parameter passed in onSearchBoxMounted() it prints null and then the SearchBox object after every re-render, so according to this the SearchBox component gets unmounted
I'm not sure if it's still actual, but to fix this you need to extract this part from the render function before your class definition:
const Searchbox = withScriptjs(props => (....))
So it will look like this:
imports ...
const Searchbox = withScriptjs(props => (....))
class AddressSearchbox extends Component {
...
render() {
return (
<Searchbox .../>
);
}
}
In practice, most React apps only call ReactDOM.render() once.
Source: https://reactjs.org/docs/rendering-elements.html
You see this flickering because ReactJS runs render() function each time when your state changes.

Watching state from child component React with Material UI

New to React. Just using create-react-app and Material UI, nothing else.
Coming from an Angular background.
I cannot communicate from a sibling component to open the sidebar.
I'm separating each part into their own files.
I can get the open button in the Header to talk to the parent App, but cannot get the parent App to communicate with the child LeftSidebar.
Header Component
import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationMenu from 'material-ui/svg-icons/navigation/menu';
class Header extends Component {
openLeftBar = () => {
// calls parent method
this.props.onOpenLeftBar();
}
render() {
return (
<AppBar iconElementLeft={
<IconButton onClick={this.openLeftBar}>
<NavigationMenu />
</IconButton>
}
/>
);
}
}
export default Header;
App Component -- receives event from Header, but unsure how to pass dynamic 'watcher' down to LeftSidebar Component
import React, { Component } from 'react';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
// components
import Header from './Header/Header';
import Body from './Body/Body';
import Footer from './Footer/Footer';
import LeftSidebar from './LeftSidebar/LeftSidebar';
class App extends Component {
constructor() {
super() // gives component context of this instead of parent this
this.state = {
leftBarOpen : false
}
}
notifyOpen = () => {
console.log('opened') // works
this.setState({leftBarOpen: true});
/*** need to pass down to child component and $watch somehow... ***/
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<Header onOpenLeftBar={this.notifyOpen} />
<Body />
<LeftSidebar listenForOpen={this.state.leftBarOpen} />
<Footer />
</div>
</MuiThemeProvider>
);
}
}
export default App;
LeftSidebar Component - cannot get it to listen to parent App component - Angular would use $scope.$watch or $onChanges
// LeftSidebar
import React, { Component } from 'react';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
class LeftNavBar extends Component {
/** unsure if necessary here **/
constructor(props, state) {
super(props, state)
this.state = {
leftBarOpen : this.props.leftBarOpen
}
}
/** closing functionality works **/
close = () => {
this.setState({leftBarOpen: false});
}
render() {
return (
<Drawer open={this.state.leftBarOpen}>
<IconButton onClick={this.close}>
<NavigationClose />
</IconButton>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
);
}
}
export default LeftSidebar;
Free your mind of concepts like "watchers". In React there is only state and props. When a component's state changes via this.setState(..) it will update all of its children in render.
Your code is suffering from a typical anti-pattern of duplicating state. If both the header and the sibling components want to access or update the same piece of state, then they belong in a common ancestor (App, in your case) and no where else.
(some stuff removed / renamed for brevity)
class App extends Component {
// don't need `constructor` can just apply initial state here
state = { leftBarOpen: false }
// probably want 'toggle', but for demo purposes, have two methods
open = () => {
this.setState({ leftBarOpen: true })
}
close = () => {
this.setState({ leftBarOpen: false })
}
render() {
return (
<div className="App">
<Header onOpenLeftBar={this.open} />
<LeftSidebar
closeLeftBar={this.close}
leftBarOpen={this.state.leftBarOpen}
/>
</div>
)
}
}
Now Header and LeftSidebar do not need to be classes at all, and simply react to props, and call prop functions.
const LeftSideBar = props => (
<Drawer open={props.leftBarOpen}>
<IconButton onClick={props.closeLeftBar}>
<NavigationClose />
</IconButton>
</Drawer>
)
Now anytime the state in App changes, no matter who initiated the change, your LeftSideBar will react appropriately since it only knows the most recent props
Once you set the leftBarOpen prop as internal state of LeftNavBar you can't modify it externally anymore as you only read the prop in the constructor which only run once when the component initialize it self.
You can use the componentWillReceiveProps life cycle method and update the state respectively when a new prop is received.
That being said, i don't think a Drawer should be responsible for being closed or opened, but should be responsible on how it looks or what it does when its closed or opened.
A drawer can't close or open it self, same as a light-Ball can't turn it self on or off but a switch / button can and should.
Here is a small example to illustrate my point:
const LightBall = ({ on }) => {
return (
<div>{`The light is ${on ? 'On' : 'Off'}`}</div>
);
}
const MySwitch = ({ onClick, on }) => {
return (
<button onClick={onClick}>{`Turn the light ${!on ? 'On' : 'Off'}`}</button>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightOn: false
};
}
toggleLight = () => this.setState({ lightOn: !this.state.lightOn });
render() {
const { lightOn } = this.state;
return (
<div>
<MySwitch onClick={this.toggleLight} on={lightOn} />
<LightBall on={lightOn} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React Transition Group

I have been reading about React Transition Group. 95% of the material talks about CSSTransitionGroup. My understanding is that CSSTransitionGroup just builds off of TransitionGroup which simply provides callback methods that correspond to various animation events.
So I've wrapped my component up in a TransitionGroup element and given it an animation event but it is never fired.
import React, { Component } from "react";
import { TransitionGroup, Transition } from "react-transition-group";
class Test extends Component {
componentWillAppear(cb) {
console.log('componentWillAppear')
cb()
}
render() {
return <div> test </div>
}
}
class App extends Component {
render() {
return (
<TransitionGroup>
<Test />
</TransitionGroup>
)
}
}
export default App;
You can use Transition or CSSTransition without TransitionGroup, but you can't use TransitionGroup without one of the others.
From the react-transition-group docs:
The <TransitionGroup> component manages a set of <Transition> components in a list. Like with the <Transition> component, <TransitionGroup>, is a state machine for managing the mounting and unmounting of components over time.
...As items are removed or added to the TodoList the in prop is toggled automatically by the <TransitionGroup>.
Try changing your Test component's render to something like this:
render() {
return (
<Transition timeout={150}>
{(status) => (
<div className={`fade fade-${status}`}>
test
<div>
)}
</Transition>
)
}

react - material-ui appbar icon touch event doesn't fire

When I click on the element AppBar, icon on the left, _handleClick() method should execute.
I can't get console message.
I'm using material-ui framework and the attribute onLeftIconButtonTouchTap is provided for a callback function for when the left icon is selected via a touch tap.
import React, { Component } from 'react'
import { AppBar, IconButton } from 'material-ui'
import MoreVertIcon from 'material-ui/lib/svg-icons/navigation/more-vert';
let injectTapEventPlugin = require("react-tap-event-plugin");
//Needed for onTouchTap
//Can go away when react 1.0 release
//Check this repo:
//https://github.com/zilverline/react-tap-event-plugin
injectTapEventPlugin();
class Header extends Component {
constructor(props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.preventDefault();
// Show/Hide the LeftMenu
window.console.log("Click!");
}
render() {
return (
<AppBar title="Arasaaccc"
iconElementLeft={ <IconButton>
<MoreVertIcon/>
</IconButton> }
onLeftIconButtonTouchTap={ this._handleClick }
isInitiallyOpen={ true } />
)
}
}
export default Header
However it works with another component:
class Prueba extends Component {
constructor(props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.preventDefault();
window.console.log("Click!");
}
render (){
return (
<h1 onClick={this._handleClick}>Prueba Prueba Prueba</h1>
)
}
}
export default Prueba;
If you specify an icon for the AppBar component, onLeftIconButtonTouchTap event does not work.
Either you don't specify an icon:
<AppBar title="Arasaaccc"
onLeftIconButtonTouchTap={ this._handleClick }
isInitiallyOpen={ true } />
Or you apply the event on the IconButton component:
<AppBar title="Arasaaccc"
iconElementLeft={ <IconButton onTouchTap={ this._handleClick } >
<MoreVertIcon />
</IconButton> }
isInitiallyOpen={ true } />
Edit: Note that, according to this GitHub issue, the problem should be solved. You still can't have a a _handleClick on both of iconElementLeft and onLeftIconButtonTouchTap, either one or the other.
I can't see any problems with your code, so my guess is you'll need the React-Tap-Event-Plugin. The docs say this dependency is temporary and will go away once react v1.0 is released.

Resources