We are working on a project with some challenging animation requirements.We are trying to do the animations in CSS and not get JS involved in the animation work. For example we have the following component (simplified for clarity):
var mainMenu = React.createClass({
mixins: [PureRenderMixin],
render: function () {
var _this = this;
var currItem;
var items = this.props.itemList.map(function (item) {
if (!item.get('isOpen')) {
currItem = <MainMenuClosed key={item.get('MenuItemId')}
menuTypeId={item.get('MenuTypeId')}
title={item.get('ItemTitle')}
text={item.get('ItemText')}
imageUrl={item.get('ImageId')}
clickHandler={_this.props.closedClickHandler} />;
} else {
currItem = <MainMenuOpen menuTypeId={item.get('MenuTypeId')}
key={item.get('MenuItemId')}
clickHandler={_this.props.openClickHandler}
items={item.get('children')}
/>;
}
return (
currItem
);
})
return (
<ReactCSSTransitionGroup component="div" className="row" transitionName="mainMenuItems">
{items}
</ReactCSSTransitionGroup>
);
}
});
When a menu item is clicked an action is called, which fetches some stuff from the server and sends it on to the stores. The problem is that once the re-render happens the leaving component fades out for example and the entering component fades in at the same time. I would like to let the leaving component finish its animation sequence and then have the entering menu item begin its animation. Is there a builtin way to create such a sequence?
Related
Let's say I need to add an element to the navbar when the user have scrolled past the header of the site. How can I do something like this in React without using jQuery?
You can do some thing like this: (this function was copied from my own react-sticky-dynamic-header that I created before: https://github.com/thinhvo0108/react-sticky-dynamic-header )
componentDidMount() {
var h1 = parseInt(this.refs.header.offsetHeight);
window.addEventListener('scroll', this._calcScroll.bind(this, h1));
}
componentWillUnmount() {
window.removeEventListener('scroll', this._calcScroll)
}
_calcScroll(h1) {
var _window = window;
var heightDiff = parseInt(h1);
var scrollPos = _window.scrollY;
if (scrollPos > heightDiff) {
// here this means user has scrolled past your header,
// you may rerender by setting State or do whatever
this.setState({
//stateKey: stateValue,
});
} else {
// here the user has scrolled back to header's territory,
// it's optional here for you to remove the element on navbar as stated in the question or not
this.setState({
//stateKey: stateValue,
});
}
}
render() {
return (
<div ref="header">YOUR HEADER HERE</div>
);
}
For a smooth animation when your element added or removed from the navbar, you can just add this into the element's CSS style:
#your-element{
transition: opacity 0.3s ease-in;
}
You can try to install my library to see if it can extend your needs:
https://www.npmjs.com/package/react-sticky-dynamic-header
Feel free to post here some errors if any, thanks
I don't think I fully understand how the Parent/Child relationship works in React. I have two components, Column and Space. When a Column is rendered, it creates some Spaces. I thought that meant that the Column would be the parent to those Spaces, but either I'm wrong or I'm using some part of React.Children.count(this.props.children) incorrectly - it always tells me that any given Column has 0 children.
Simplified models:
var Column = React.createClass({
getInitialState: function() {
return {childCount: '', magicNumber: this.props.magicNumber};
},
componentDidMount: function() {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
},
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
);
}
});
var Space = React.createClass({
render: function() {
return (
<div className="Space">
space here
</div>
);
}
});
It seems like no matter where I put React.children.count(this.props.children) in a Column, it tells me there are 0 children. I would expect the Column generated in the example code to have five children, since five Spaces are created within it.
I figured maybe I was trying to count the Children before they were fully loaded, so I tried writing a click handler like this:
//in Column
setChildCount: function () {
var newCount = React.Children.count(this.props.children);
this.setState({childCount: newCount});
}
Then adding a button like this in my Column:
...
render: function() {
return (
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
<button onClick={this.setChildCount} />
{this.state.childCount}
</div>
);
But my childCount is always and forever 0. Can anyone see where I'm going wrong?
Edit: Ultimately, I want to get a count of all children elements who have X attribute in their state set to Y value, but I am clearly a step or three away from that.
The Column component doesn't have any children on that code. Childrens are components which are wrapped by the parent component. So imagine:
<Column>
<Space/>
<Space/>
<Column/>
In this case the parent Column has two children Space
On your code:
<div className='Column'>
{
Array.from({ length: this.state.magicNumber }, (x,y) => <Space key={y}/>)
}
</div>
You are creating new components inside a divnot inside the component Column
You are rendering Space components as part of the Column component. The parent/child relationship captured by this.props.children looks like this:
var Column = React.createClass({
render: function() {
<div className="column">
{this.props.children}
</div>
}
});
ReactDOM.render(
<Column>
<Space /> //little space children
<Space />
</Column>
);
To get at your specific problem, you don't need anything like this.props.children because you have everything right there in your render method.
So the answer to your question is: apply the same logic as when you render them.
I am new to reactJS, but thought I had understood the minimal change and update of DOM. Apparently I was wrong. I have a ReactJS-class Gallery, which renders a list of images (I included two functions of this class). The url of one image (in the images-list) is dependent on the Gallery state (index), so I thought that the image-url would change (forcing a new get request to the server), since it is dependent on the index-value of Gallery, by changing the gallery state.index to something else. How is my thinking incorrect? how can I update an image-url from the gallery class?
getInitialState: function(){
var self = this;
window.addEventListener('keypress', function(e){
if(e.keyCode == 39){
if(self.state.index < self.state.size){
self.setState({index: self.state.index+1});
console.log(self.state.index);
}
}
if(e.keyCode == 37){
if(self.state.index > 0){
self.setState({index: self.state.index-1});
console.log(self.state.index);
}
}
})
render:function(){
var images= [];
var url = "/patient/" + this.state.index.toString();
images.push(<Image src={url} width={500} height={520} left={150} top={this.state.base} />);
return (
<div>
{images}
</div>
);
}
might be a very relevant link (investigating now): Reactjs: how to modify child state or props from parent?
I have some data with datetime fields , i want to show the relative date time using momentJS fromNow(). However after the initial load it shows timestamp as a few seconds ago. But this will not be updated until a next state change triggered. Is it a good practice to keep another state in the state-tree & control via a timer function setInterval in componentDidUpdate?
render()
{
// get the new prop value here which triggered from a setInterval -> action -> reducer -> state change -> propagate to connected components
const text = comment.get('text');
const dateTime = moment(comment.get('dateTime')).fromNow();
return (
// add the new prop into the component
<div key={id}>
<Comment
text = {text}
dateTime = {dateTime}
</div>
}
I scribbled down a component that takes an epoch time timestamp and display a momentjs text for it.
The text is updates via inner component state every 300ms which you can change however you'd like.
You can notice on this fiddle, every new text is logged in the console. After 45 seconds you should see the text change from "a few seconds ago" to "a minute ago".
Fiddle here, this is the code:
var MomentTime = React.createClass({
getInitialState: function() {
return {text: ""};
},
componentWillMount: function() {
this._updateMomentText();
this.interval = setInterval(this._updateMomentText, 300);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
_updateMomentText: function() {
var text = moment(this.props.timestamp).fromNow()
console.log(text)
if(text !== this.state.text) {
this.setState({text: text});
}
},
render: function() {
return <div>{this.state.text}</div>;
}
});
ReactDOM.render(
<MomentTime timestamp={new Date().getTime()} />,
document.getElementById('container')
);
I have a top-level component that consists of a few different components.
InventoryBox - designates the space that an inventory contains
InventoryList - designates the list of items in the inventory
Item - a single item in the inventory list
InventoryBox is the top-level component, so I have wrapped it in a DragDropContext. The issue I am running into is that the connectDragSource function that I specified in my collect function is not injecting the method into my item components.
My Collect Function
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
}
My Item Component
var Item = React.createClass({
render: function(){
var id = this.props.id;
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
return (
<div className="item">
{this.props.children}
</div>
);
}
});
My ultimate goal would be to drag the Item components from the list to another Inventory Box.
When you are using Item in the Item inventoryList you are just using the Item not the wrapped one. var Item = React.... but you need to declare a variable.
var ItemWrapped = DragSource(Types.INVENTORY, itemSource, collect)(Item);
// Use the ItemWrapped instead.... the same goes for all
The DragSource returns this in the source
return function decorateSource(DecoratedComponent) {
return decorateHandler({
connectBackend: (backend, sourceId) => backend.connectDragSource(sourceId),
containerDisplayName: 'DragSource',
createHandler: createSource,
registerHandler: registerSource,
createMonitor: createSourceMonitor,
createConnector: createSourceConnector,
DecoratedComponent,
getType,
collect,
options
});
};
So you need to handle the returned function