I have a page with a search input, once the user click on submit results come up.
There can be a lot of results and I don't want to load them all at once, how can I fetch more data into the page using Lodash throttle on mouse move?
This is my react component:
const getContacts = async (searchString) => {
const { data: contactsInfo} = await axios.get(`api/Contats/Search?contactNum=${searchString}`);
return contactsInfo;
};
export default class Home extends React.Component {
state = {
contactsInfo: [],
searchString: '',
};
handleSubmit = async () => {
const { searchString } = this.state;
const contactsInfo = await getContacts(searchString);
this.setState({ contactsInfo });
};
onInputChange = e => {
this.setState({
searchString: e.target.value,
});
};
onMouseMove = e => {
};
render() {
const { contactsInfo, searchString, } = this.state;
return (
<div css={bodyWrap} onMouseMove={e => this.onMouseMove(e)}>
<Header appName="VERIFY" user={user} />
{user.viewApp && (
<div css={innerWrap}>
<SearchInput
searchIcon
value={searchString || ''}
onChange={e => this.onInputChange(e)}
handleSubmit={this.handleSubmit}
/>
{contactsInfo.map(info => (
<SearchResultPanel
info={info}
isAdmin={user.isAdmin}
key={info.id}
/>
))}
</div>
)}
<Footer />
</div>
);
}
}
I supposed that, using getContacts() you retrieve ALL the contacts, and then you just want to show them at some rate, like showing the first 20, then when you reach the last one, another 20s appear.
Just asking because this is really different from "let's fetch the first 20 contacts, show them, and when the user reaches the last one, fetch another 20s".
So, if the first assumption I've made it's correct, I can raccomend you to use the Intersection Observer API https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
This is really useful in case like yours (it's even written in the documentation "Lazy-loading of images or other content as a page is scrolled.").
The idea is that you should add this Intersection Observer, and start the observation on the last image: this observator will run a callback as soon as the last image appears on the screen (you can even decide the percentage of the image that must be on the screen).
For example, you can say that, as soon as 1px of the image appear on the screen, you add another 20s images!
Notice that, once another 20s images are shown, you must unobserve the currect observed image, and observe the new last image!
I can also suggest to not put the observer on the last image, but maybe on the third last.
EDIT: I'm not sure this answers your question. It does if I consider the title "Fetch more content as user is scrolling down", but it does not actually use mouseover (even though I think this implementation is the best one for your goal).
EDIT2: There it goes, I've added the fiddle, and here there is the codepen: https://codepen.io/Gesma94/pen/OqpOQb
Note that I've simulated the contacts with divs of different color. What is going on is that, when the third last contact (div) appear on the screen, new contacts are added in the state. Right now the contacts are just empty objects, but you can run a fetch or doing whatever you want inside fetchMoreContent(). Is this clear enough? :) I've commented the code too.
/* Just a function that create a random hex color. */
function randomColor() {
let randomColor = '#';
const letters = '0123456789ABCDEF';
for (let i = 0; i < 6; i++) {
randomColor += letters[Math.floor(Math.random() * 16)];
}
return randomColor;
}
class Home extends React.Component {
contactList = null; // Ref to the div containing the contacts.
contactObserved = null; // The contact which is observed.
intersectionObserver = null; // The intersectionObserver object.
constructor(props) {
super(props);
this.contactList = React.createRef();
this.state = {
loading: true,
contactsToShow: 0,
contacts: []
};
}
componentDidMount() {
/* Perform fetch here. I'm faking a fetch using setTimeout(). */
setTimeout(() => {
const contacts = [];
for (let i=0; i<100; i++) contacts.push({});
this.setState({loading: false, contacts, contactsToShow: 10})}, 1500);
}
componentDidUpdate() {
if (!this.state.loading) this.handleMoreContent();
}
render() {
if (this.state.loading) {
return <p>Loading..</p>
}
return (
<div ref={this.contactList}>
{this.state.contacts.map((contact, index) => {
if (index < this.state.contactsToShow) {
const color = contact.color || randomColor();
contact.color = color;
return (
<div
className="contact"
style={{background: color}}>
{color}
</div>
);
}
})}
</div>
);
}
handleMoreContent = () => {
/* The third last contact is retrieved. */
const contactsDOM = this.contactList.current.getElementsByClassName("contact");
const thirdLastContact = contactsDOM[contactsDOM.length - 3];
/* If the current third last contact is different from the current observed one,
* then the observation target must change. */
if (thirdLastContact !== this.contactObserved) {
/* In case there was a contact observed, we unobserve it and we disconnect the
* intersection observer. */
if (this.intersectionObserver && this.contactObserved) {
this.intersectionObserver.unobserve(this.contactObserved);
this.intersectionObserver.disconnect();
}
/* We create a new intersection observer and we start observating the new third
* last contact. */
this.intersectionObserver = new IntersectionObserver(this.loadMoreContent, {
root: null,
threshold: 0
});
this.intersectionObserver.observe(thirdLastContact);
this.contactObserved = thirdLastContact;
}
}
loadMoreContent = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let contactsCounter = this.state.contacts.length;
let contactsToShow = this.state.contactsToShow + 10;
if (contactsToShow > contactsToShow) contactsToShow = contactsToShow;
this.setState({contactsToShow});
}
})
}
}
ReactDOM.render(<Home />, document.getElementById('root'));
#import url(https://fonts.googleapis.com/css?family=Montserrat);
body {
font-family: 'Montserrat', sans-serif;
}
.contact {
width: 200px;
height: 100px;
border: 1px solid rgba(0,0,0,0.1);
}
.contact + .contact {
margin-top: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id='root'></div>
Related
I have a list that I can sort with drag and drop using react, and it works fine. The way it works is onDragEnter, the items get replaced. What I want to do though, is show a placeholder element once the dragging item is hovering over available space. So the final placement would happen in onDragEnd. I have two functions that handle dragging:
const handleDragStart = (index) => {
draggingItem.current = index;
};
const handleDragEnter = (index) => {
if (draggingWidget.current !== null) return;
dragOverItem.current = index;
const listCopy = [...rows];
const draggingItemContent = listCopy[draggingItem.current];
listCopy.splice(draggingItem.current, 1);
listCopy.splice(dragOverItem.current, 0, draggingItemContent);
if (draggingItem.current === currentRowIndex) {
setCurrentRowIndex(dragOverItem.current);
}
draggingItem.current = dragOverItem.current;
dragOverItem.current = null;
setRows(listCopy);
};
and in react jsx template, I have this:
{rows.map((row, index) => (
<div
key={index}
draggable
onDragStart={() => handleDragStart(index)}
onDragEnter={() => handleDragEnter(index)}
onDragOver={(e) => e.preventDefault()}
onDragEnd={handleDragEndRow}
>
...
</div>
Can anyone come with any tips as to how I might solve this?
To display a placeholder indicating where you are about to drop the dragged item, you need to compute the insertion point according to the current drag position.
So dragEnter won't do, dargOver is best suited to do that.
When dragging over the first half of the dragged overItem, the placeholder insertion point will be before the dragged over item, when dragging over the second half, it will be after. (see getBouldingClientRect, height/2 usages, of course if dragging horizontally width will need to be accounted for).
The actual insertion point (in the data, not the UI), if drag succeeds, will depend on if we're dropping before or after the initial position.
The following snippet demonstrate a way of doing that with the following changes in your initial code:
Avoided numerous refs vars by putting everything in state, especially because changing these will have an effect on the UI (will need rerender)
Avoided separate useState calls by putting all vars in a common state variable and a common setState modifier
Avoided unnecessary modifications of the rows state var, rows should change only when drag ends as it's easier to reason about it => the placeholder is not actually part of the data, it serves purpose only in the ui
Avoided defining handler in the render code onEvent={() => handler(someVar)} by using dataset key data-drag-index, the index can retrieved after using this key: const index = element.dataset.dragIndex. The handler can live with the event only which is automatically passed.
Avoided recreating (from the children props point of view) these handlers at each render by using React.useCallback.
The various css class added show the current state of each item but serves no functionnal purpose.
StateDisplay component also serves no purpose besides showing what happens to understand this answer.
Edit: Reworked and fixed fully working solution handling all tested edge cases
const App = () => {
const [state,setState] = React.useState({
rows: [
{name: 'foo'},
{name: 'bar'},
{name: 'baz'},
{name: 'kazoo'}
],
draggedIndex: -1,
overIndex: -1,
overZone: null,
placeholderIndex: -1
});
const { rows, draggedIndex, overIndex, overZone, placeholderIndex } = state;
const handleDragStart = React.useCallback((evt) => {
const index = indexFromEvent(evt);
setState(s => ({ ...s, draggedIndex: index }));
});
const handleDragOver = React.useCallback((evt) => {
var rect = evt.target.getBoundingClientRect();
var x = evt.clientX - rect.left; // x position within the element.
var y = evt.clientY - rect.top; // y position within the element.
// dataset variables are strings
const newOverIndex = indexFromEvent(evt);
const newOverZone = y <= rect.height / 2 ? 'top' : 'bottom';
const newState = { ...state, overIndex: newOverIndex, overZone: newOverZone }
let newPlaceholderIndex = placeholderIndexFromState(newOverIndex, newOverZone);
// if placeholder is just before (==draggedIndex) or just after (===draggedindex + 1) there is not need to show it because we're not moving anything
if (newPlaceholderIndex === draggedIndex || newPlaceholderIndex === draggedIndex + 1) {
newPlaceholderIndex = -1;
}
const nonFonctionalConditionOnlyForDisplay = overIndex !== newOverIndex || overZone !== newOverZone;
// only update if placeholderIndex hasChanged
if (placeholderIndex !== newPlaceholderIndex || nonFonctionalConditionOnlyForDisplay) {
newState.placeholderIndex = newPlaceholderIndex;
setState(s => ({ ...s, ...newState }));
}
});
const handleDragEnd = React.useCallback((evt) => {
const index = indexFromEvent(evt);
// we know that much: no more dragged item, no more placeholder
const updater = { draggedIndex: -1, placeholderIndex: -1,overIndex: -1, overZone: null };
if (placeholderIndex !== -1) {
// from here rows need to be updated
// copy rows
updater.rows = [...rows];
// mutate updater.rows, move item at dragged index to placeholderIndex
if (placeholderIndex > index) {
// inserting after so removing the elem first and shift insertion index by -1
updater.rows.splice(index, 1);
updater.rows.splice(placeholderIndex - 1, 0, rows[index]);
} else {
// inserting before, so do not shift
updater.rows.splice(index, 1);
updater.rows.splice(placeholderIndex, 0, rows[index]);
}
}
setState(s => ({
...s,
...updater
}));
});
const renderedRows = rows.map((row, index) => (
<div
key={row.name}
data-drag-index={index}
className={
`row ${
index === draggedIndex
? 'dragged-row'
: 'normal-row'}`
}
draggable
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
>
{row.name}
</div>
));
// there is a placeholder to show, add it to the rendered rows
if (placeholderIndex !== -1) {
renderedRows.splice(
placeholderIndex,
0,
<Placeholder />
);
}
return (
<div>
{renderedRows}
<StateDisplay state={state} />
</div>
);
};
const Placeholder = ({ index }) => (
<div
key="placeholder"
className="row placeholder-row"
></div>
);
function indexFromEvent(evt) {
try {
return parseInt(evt.target.dataset.dragIndex, 10);
} catch (err) {
return -1;
}
}
function placeholderIndexFromState(overIndex, overZone) {
if (overZone === null) {
return;
}
if (overZone === 'top') {
return overIndex;
} else {
return overIndex + 1;
}
}
const StateDisplay = ({ state }) => {
return (
<div className="state-display">
{state.rows.map(r => r.name).join()}<br />
draggedIndex: {state.draggedIndex}<br />
overIndex: {state.overIndex}<br />
overZone: {state.overZone}<br />
placeholderIndex: {state.placeholderIndex}<br />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
.row { width: 100px; height: 30px; display: flex; align-items: center; justify-content: center; }
.row:nth-child(n+1) { margin-top: 5px; }
.row.normal-row { background: #BEBEBE; }
.row.placeholder-row { background: #BEBEFE; }
.row.normal-row:hover { background: #B0B0B0; }
.row.placeholder-row:hover { background: #B0B0F0; }
.row.dragged-row { opacity: 0.3; background: #B0B0B0; }
.row.dragged-row:hover { background: #B0B0B0; }
.state-display { position: absolute; right: 0px; top: 0px; width: 200px; }
<html><body><div id="root"></div><script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.0/umd/react-dom.production.min.js"></script></body></html>
Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}
I currently have something like this:
const socket = require('socket.io-client')('https://example.com');
(....)
// Listen to the channel's messages
socket.on('m', message => {
// this is a Redux action that updates the state
this.props.updateTrades(message);
});
The reducer looks like this:
case actions.UPDATE_TRADES:
return {
...state,
trades: [
...state.trades,
action.trade
]
};
I've tried not using redux and just to the following:
socket.on('m', message => {
this.setState(state => {
if (state.trades.length > 99) {
state.trades.splice(0, 1);
}
return {
trades: [
...state.trades,
message
]
});
});
I don't need to keep increasing my trades array. I'm happy just to keep around 100 items or so...
Socket is sending around 15 messages / second.
My problem is: I can't seem to render the messages in real-time! It just freezes. I guess the stream is just too fast? Any suggestions?
Thanks in advance!
The thing is to do the minimum possible and when the trades change only draw what has change and not all of the elements of the array.A technique that I use is to keep a cache map of already drawn obj, so in the render method I only render the new incoming elements.
Take a look at https://codesandbox.io/s/wq2vq09pr7
class RealTimeList extends React.Component {
constructor(props) {
super(props);
this.cache = [];
}
renderRow(message, key) {
return <div key={key}>Mesage:{key}</div>;
}
renderMessages = () => {
//let newMessages=this,props.newMessage
let newElement = this.renderRow(this.props.message, this.cache.length);
this.cache.push(newElement);
return [...this.cache];
};
render() {
return (
<div>
<div> Smart List</div>
<div className="listcontainer">{this.renderMessages()}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "hi" };
}
start = () => {
if (this.interval) return;
this.interval = setInterval(this.generateMessage, 200);
};
stop = () => {
clearTimeout(this.interval);
this.interval = null;
};
generateMessage = () => {
var d = new Date();
var n = d.getMilliseconds();
this.setState({ title: n });
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.start}> Start</button>
<button onClick={this.stop}> Stop</button>
<RealTimeList message={this.state.message} />
</div>
);
}
}
The class RealTime List have a cache of elements.Let me know if this helps.
It's probably not a good idea to try to render all of the changes. I think you should try rendering them in batches so you only update once every few seconds, that should help.
For about 7 hours now, Am working with infinite Scrolling using react redux.
This code works very well by displaying 20 database records as user scroll-down the page. But am currently face with two issues.
1.) I cannot get the application to display a message "No more records" once record gets finished displaying from database.
I have tried
get finished() {
console.log(this.props.users_scroll.length);
if (this.row >= this.props.users_scroll.length ) {
return (<li key={'done'}>No More Message to Load.</li>);
}
return null;
}
but console give values undefined for this line of code
console.log(this.props.users_scroll.length);
I have also tried
get finished() {
console.log(this.loadMore.length);
if (this.row >= this.loadMore.length ) {
return (<li key={'done'}>No More Message to Load.</li>);
}
return null;
}
but console give values 0 for this line of code console.log(this.loadMore.length); as a result the application will immediately
shows No more records whereas they are still about 15 records in the database.
2.) When all the 20 records from database gets display, there is still continuous Call from server/database as long as the user
keeps on scrolling down the Page. I have tried this but it does not stop the unecessary Server/database Calls. it seems that this.users_scroll
is either empty or undefined
if(this.users_scroll !='' ){
loadMore();
}
Here is the code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import request from "superagent";
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class InfinitescrollPage extends React.Component {
constructor(props) {
super(props);
// Sets up our initial state
this.state = {
users_scroll: [],
};
//set parameters
this.row = 0;
this.rowperpage = 5;
this.cText = "";
this.loadMore = this.loadMore.bind(this);
// Binds our scroll event handler
window.onscroll = () => {
const {
loadMore,
state: {
},
} = this;
// Checks that the page has scrolled to the bottom
if (
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight
) {
//if(this.users_scroll !='' ){
loadMore();
//}
}
};
}
componentWillMount() {
// Loads some users on initial load
this.props.dispatch(userActions.getAll_Infinitescroll(this.row));
}
loadMore() {
this.cText = "content is Loading ...";
this.row+=this.rowperpage;
setTimeout(()=>{
this.len =this.props.dispatch(userActions.getAll_Infinitescroll(this.row));
this.cText = "";
},100);
}
get finished() {
//alert('database rows lengths ' +this.props.users_scroll.length);
//if (this.row >= this.loadMore.length ) {
if (this.row >= this.props.users_scroll.length ) {
return (<li key={'done'}>No More Message to Load.</li>);
}
return null;
}
render() {
//const {} = this.state;
const {user_scroll, users_scroll } = this.props;
return (
<div>
<h1>Infinite Users!</h1>
<p>Scroll down to load more!!</p>
{users_scroll.items2 && users_scroll.items2.map(user_scroll => (
<Fragment key={user_scroll.username}>
<hr />
<div style={{ display: 'flex' }}>
<img
alt={user_scroll.username}
src='http://localhost/apidb_react/2.png'
style={{
borderRadius: '50%',
height: 72,
marginRight: 20,
width: 72,
}}
/>
<div>
<h2 style={{ marginTop: 0 }}>
#{user_scroll.uuid}
</h2>
<p>Name: {user_scroll.name}</p>
<p>Email: {user_scroll.email}</p>
<p>Counting: {user_scroll.rcount}</p>
</div>
</div>
</Fragment>
))}
{this.finished}
{this.cText}
<input type="text" className="form-control" name="this.row" id="this.row" value={this.row} onChange={this.handleChange} />
<hr />
</div>
);
}
}
const container = document.createElement("div");
document.body.appendChild(container);
render(<InfinitescrollPage />, container);
users.service
function getAll_Infinitescroll(us) {
const request = {
method: 'POST',
body: JSON.stringify({us});
};
return fetch(`/users.php`, request).then(handleResponse)
.then(users_scroll => {
if (users_scroll) {
console.log(users_scroll);
}
return users_scroll;
});
}
This has been resolved. I need to get the row counts from the database and the compare it with the passing row data.
1.) To display No more message it becomes
this.count='20';
or
this.count=this.props.dispatch(userActions.getRowCount());
get finished() {
if (this.row >= this.count ) {
return (<li key={'done'}>No More Message to Load.</li>);
}
return null;
}
2.) To prevent unnecessary scrolling when there is no more data to display
loadMore() {
if (this.row != this.count ) {
this.cText = "content is Loading ...";
this.row+=this.rowperpage;
setTimeout(()=>{
this.len =this.props.dispatch(userActions.getAll_Infinitescroll(this.row));
this.cText = "";
},100);
}
}
Background I am trying to create a container for a collection of elements, each of which can be removed from the collection. When an element is removed, I want to animate its exit, and I am trying to achieve this using React Motion.
Here's a diagram of the above:
Problem I thought of using React Motion's TransitionMotion component, and got stuck trying to write a function that needs to be passed to it. Here is my — incorrect — code:
class Container extends Component {
state = {
elementStyles: {}
}
componentDidMount() {
this.getElementStyles();
}
componentDidUpdate() {
this.getElementStyles();
}
getElementStyles() {
if (!this.props.children.length || this.hasElementStyles()) return;
// this assumes all elements passed to the container are of equal dimensions
let firstChild = this.refs.scroller.firstChild;
let elementStyles = {
width: firstChild.offsetWidth,
height: firstChild.offsetHeight,
opacity: 1
};
this.setState({
elementStyles
});
}
hasElementStyles() {
return !isEmpty(this.state.elementStyles); // lodash to the rescue
}
willLeave() {
return { width: spring(0), height: spring(0), opacity: spring(0) }
}
getChildrenArray() {
return Children.toArray(this.props.children); // that's React's util function
}
getModifiedChild(element, props) {
if (!element) return;
return React.cloneElement(
element,
{style: props.style}
);
}
getInitialTransitionStyles() {
let elements = this.getChildrenArray();
let result = elements.map((element, index) => ({
key: element.key,
style: this.state.elementStyles
}));
return result;
}
render() {
if (this.hasElementStyles()) {
return (
<TransitionMotion
willLeave={this.willLeave}
styles={this.getInitialTransitionStyles()}
>
{ interpolatedStyles => {
let children = this.getChildrenArray();
return <div ref="scroller" className="container">
{ interpolatedStyles.map((style, index) => {
return this.getModifiedChild(children[index], style);
})
}
</div>
}}
</TransitionMotion>
);
} else {
return (
<div ref="scroller" className="container">
{ this.props.children }
</div>
);
}
}
}
Notice this line inside the map function in the TransitionMotion component: return this.getModifiedChild(children[index], style). It is wrong, because once an element is removed from the collection, this.props.children will change, and indices of the children array will no longer correspond to the indices of the styles array calculated from those children.
So I what I need is either some clever way to track the props.children before and after an element has been removed from the collection (and I can't think of one; the best I could come up with was using a find function on the array return this.getModifiedChild(children.find(child => child.key === style.key), style);, but that will result in so many loops within loops I am scared even to think about it), or to use some completely different approach, which I am not aware of. Could you please help?
This solution's delete animation slides the deleted item (and the rest of the "cards" in the row) horizontally left beneath the card on the left (using it's lower z-index).
The initial render sets up the < Motion/> tag and the cards display as a row.
Each card shares a delete button method which setStates the index of the array element and starts a timer that slices the array. The setState triggers the render which animates the cards before the timer does the slice.
class CardRow extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
cardToRemove: 99,
};
}
findCard = (_id) => {
return this.myArray.find((_card) => {
return _card.id === _id;
});
};
removeCardClick = (evt) => {
const _card = this.findCard(evt.target.id);
setTimeout(() => { // wait for the render...
this.myArray.splice(this.myArray.indexOf(_card), 1);
this.setState({cardToRemove: 99});
}, 200);
this.setState({cardToRemove: this.myArray.indexOf(_card)});
};
render() {
let items = this.myArray.map((item, index) => {
let itemLeft = 200 * index; // card width 200px
if (this.state.cardToRemove <= index) {
itemLeft = 200 * (index - 1);
}
// .cardContainer {position: fixed}
return <div key={item.id}>
<Motion style={{left: spring(itemLeft)}}>
{({left}) =>
<div className="cardContainer" style={{left: left}}>
<div className="card">
<button className="Button" id={item.id} onClick={this.removeCardClick}>Del</button>
</div>
</div>
}
</Motion>
</div>;
});
items = items.reverse();
return <div>
{items}
</div>;
}
}
That's about it! Thanks and enjoy,