I have a very simple example about useTransition, my expectation is whenever i click on the shuffle button, the items below swap around by a smooth animation. But i doesn't work, the item does swapping but also the pos property. I think my understand about key in useTransition has something wrong, but i can't find it.
my current code: https://codesandbox.io/s/wonderful-solomon-c0sve?file=/src/index.jsx
what im trying to do is something like this
function App() {
const [items, setState] = useState([
{ name: 'C' },
{ name: 'D' },
{ name: 'E' },
{ name: 'F' },
{ name: 'G' },
{ name: 'A' },
{ name: 'B' },
]);
let index = -1;
const gridItems = items.map((item) => {
index += 1;
return { ...item, pos: index * 60 };
});
const transitions = useTransition(gridItems, item => item.name, {
from: () => ({ pos: -100 }),
enter: ({ pos }) => ({ pos }),
udpate: ({ pos }) => ({ pos }),
leave: () => ({ pos: -100 }),
});
return (
<div>
This is app<br/>
<button onClick={ () => setState(Lodash.shuffle) }>shuffle</button><br/><br/>
<div>
{transitions.map(({ item, props, key }) => {
return (
<animated.div
key={key}
className="item"
style={{ transform: props.pos.interpolate(pos => `translateY(${pos}px)`) }}
>
{`${item.name}`}
</animated.div>
)
})}
</div>
</div>
)
}
It was an age to figuring it out. You made a typo.
Try with this one:
update: ({ pos }) => ({ pos }),
Related
I have such situation:
There is an array:
const arr = [
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
]
Then I use this array in my React component to get values, but here I also need to change status to true or false in current element (not for all objects)
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => !status}>{div.title}</div> // here
</div>
)
})
So how can I make this?
How about simply (if you have the access to the arr variable) you have an option of getting an index of the array as the 2nd parameter in the map function.
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => {
arr[ix].status = !arr[ix].status
}}>{div.title}</div> // here
</div>
)
})
or pull it out in a function (better way) as
function handleClick (ix) {
const currentStatus = arr[ix].status
arr[ix].status = !currentStatus
}
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => handleClick(ix)}>{div.title}</div> // here
</div>
)
})
EDIT: Didn't see it was a reactJS, my bad, in that case the best case is to manage it with state.
You can try:
const handleClick = (el) => () => {
el.status = !el.status
}
arr.map(el => {
return (
<div key={el.id}>
<div onClick={handleClick(el)}>{div.title}</div> // here
</div>
)
})
Maintain array in react state;
const [arr,setArr]=useState([
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
])
Write a function to handle the change in state
const changeStatus =id=>{
const newArr = arr.map((el,index)=>{
if(el.id===id){
return {...el,status:!el.status}
}
else return;
})
setArr(newArr)
}
call this function on onClick.
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => changeStatus(el.id)}>{div.title}</div> // here
</div>
)
})
Simply i have a list item, that contain a list of names, clicking any list item change the color of that item, this is my logic:
const App = () => {
const items = [
{
name: 'peter',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'john',
id: 3
}
]
const [id, setId] = useState(null);
const [names, setNames] = useState(items)
const setClickedItemId = (id) => {
setId(id)
}
const turnItemRed = () => {
setNames(prev => prev.map(i => i.id === id ? {...i, color: 'red' } : i))
}
return (
<div className="app">
<ul className="items">
{
names.map(i => {
return (
<Item
item={i}
setClickedItemId={setClickedItemId}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
function Item({item, ...props}) {
const { name, id} = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
className={`${item.color === 'red' ? 'red' : ''}`}
onClick={() => {
setClickedItemId(id);
turnItemRed()
}}
>{name}</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
This renders a list of items, i need two clicks to have an item turning into red, which means the child component doesn't catch the most recent version of state, but:
Just adding that line of code before the return statement in parent components,
const showingItems = names.map(i => i.id === id ? {...i, color: 'red'} : i)
and then using that variable showingItems to render the list instead of state variable names make it right and don't know why
So, why the child components Items doesn't get the most recent version of the state while storing the state in a variable makes it work??
State updates are batched and your onClick triggers 2 functions which does state updates. The second function doesn't receive updated value due to the async behaviour.
Just pass the id to turnItemRed function instead of grabbing it from state.
App
const turnItemRed = (id) => { //<----take the id
setNames(prev => prev.map(i => i.id === id ? {...i, color: 'red' } : i))
}
Item
function Item({item, ...props}) {
const { name, id} = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
className={`${item.color === 'red' ? 'red' : ''}`}
onClick={() => {
setClickedItemId(id);
turnItemRed(id) //<---- pass the id
}}
>{name}</li>
)
}
Edit
A quick demo of the above issue and the fix is here in the demo. . Just adding this so it might help other readers in future.
import React,{useState} from 'react';
export default () => {
const items = [
{
name: 'peter',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'john',
id: 3
}
]
const [id, setId] = useState(null);
const [names, setNames] = useState(items)
const setClickedItemId = (id) => {
setId(id);
turnItemRed(id);
}
const turnItemRed = (id) => {
setNames(prev => prev.map(i => i.id === id ? { ...i, color: 'red' } : i))
}
return (
<div className="app">
<ul className="items">
{
names.map(i => {
return (
<Item
item={i}
setClickedItemId={setClickedItemId}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
function Item({ item, ...props }) {
const { name, id } = item;
const { setClickedItemId, turnItemRed } = props;
return (
<li
style={{ color: item.color === 'red' ? 'red' : ''}}
onClick={() => {
setClickedItemId(id);
}}
>{name}</li>
)
}
I have list items, clicking a list item should turn it red, i want every clicked item to wait for 3000ms to restore its black color again, this is my code
my trial is to use setTimeout in useEffect and setting all red fields to false but this didn't work, especially i need to make every red item to wait 3000ms from turning red to turn black again
The first item to click turn to black after 3000ms correctly, but after that items going black faster!!
const App = () => {
const items = [
{
name: 'mark',
id: 1,
red: false
},
{
name: 'peter',
id: 2,
red: false
},
{
name: 'john',
id: 3,
red: false
}
]
const [names, setNames] = useState(items);
const turnItemRed= (id) => {
setNames(
names.map(i => i.id === id ? {...i, red: true} : i))
}
// this doesn't work
useEffect(() => {
setTimeout(() => {
setNames( prev => prev.map( i => ({...i, red: false})))
}, 3000)
})
return (
<div class="items-cont">
<ul class="items">
{
names.map(i => {
return (
<Item
item={i}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
const Item = ({ item, ...props }) => {
const { turnItemRed } = props;
return (
<li
className={`${item.red ? 'red' : ''}`}
onClick={() => {
turnItemRed(item.id)
}}
>
{item.name}
</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
CSS:
.red {
color: red
}
Because your code basically set all item back to original color at once.
Try the following code:
Note: This is simple way but the color when you can see it red then turn back to original color is not guarantee 3000 ms, because it take abit time to re-render component to show red
import React from "react";
import "./styles.css";
const App = () => {
const items = [
{
name: 'mark',
id: 1,
red: false
},
{
name: 'peter',
id: 2,
red: false
},
{
name: 'john',
id: 3,
red: false
}
]
const [names, setNames] = useState(items);
const turnItemRed= (id) => {
setNames(
names.map(i => i.id === id ? {...i, red: true} : i))
// move set timout here
setTimeout(() => {
setNames( prev => prev.map( i => i.id === id ? {...i, red: false} : i))
}, 3000)
}
return (
<div class="items-cont">
<ul class="items">
{
names.map(i => {
return (
<Item
item={i}
turnItemRed={turnItemRed}
/>
)
})
}
</ul>
</div>
)
}
const Item = ({ item, ...props }) => {
const { turnItemRed } = props;
return (
<li
className={`${item.red ? 'red' : ''}`}
onClick={() => {
turnItemRed(item.id)
}}
>
{item.name}
</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
First of all, your useEffect is running every 3 seconds, which means your component is re-rendering every 3 seconds with no UI actual updates.
useEffect(() => {
setTimeout(() => {
setNames( prev => prev.map( i => ({...i, red: false})))
}, 3000)
})
You need to change your useEffect to run only when names changes. Also make sure it runs only if some name has red property set to `true.
useEffect(() => {
if (names.some(item => item.red)) {
setTimeout(() => {
setNames(prev => prev.map(i => ({ ...i, red: false })));
}, 3000);
}
}, [names]);
Have an array of objects and want to update only the stock property of the object with an increaseQuantity() function. how can I go about that?
increaseQuantity = (id) => {
let newData = this.state.data.map(obj => {
if (obj.id === id) {
//Block of Code
}
return obj
})
this.setState({ newData })
console.log(newData)
}
sample data are as follows:
const Items = [
{ id: '0', text: 'Alvaro Beer', stock: 500 },
{ id: '1', text: 'Malta Guinnesse', stock: 200 }
Render Element :
<FlatList
data={Items}
renderItem={({ item }) => (
<Products
id={item.id}
text={item.text}
price={item.price}
stock={item.stock}
onSwipeIncrease={() => this.increaseQuantity()}
onSwipeDecrease={() => console.log('Sub')}
onSwipeAddToCart={() => console.log('Cart')}
/>
)}
keyExtractor={item => item.id}
/>
Check this simple solution
increaseQuantity = id => {
let updateItems = [...this.state.items];
// get the index according to id
let index = this.state.items.findIndex(obj => obj.id == id);
// check whether passed id exits in the items
if (!!index) {
// suppose you want to change stock value to 2000
updateItems[index].stock = 2000;
this.setState({
items: updateItems
});
}
};
In order to do that your state should like this
state = {
items: [
{ id: "0", text: "Alvaro Beer", stock: 500 },
{ id: "1", text: "Malta Guinnesse", stock: 200 }
]
}
Hope this helps you. Feel free for doubts.
The goal of my small React experiment is "clear the initial value of this.state.numString (outputs an empty string), then concatenate the clicked numbers into this.state.numString". To make it execute asynchronously, I took advantage of this.setState's callback where the concatenation of number strings happen.
class App extends Component {
state = {
numString: '12'
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
this.setState({
numString: ''
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
The result was not what I expected; it only adds the current number I click into the empty string then change it into the one I click next, no concatenation of string numbers happens.
Here is one way of doing this. As I said in my comment you are resetting the string in every setState. So, you need some kind of condition to do that.
class App extends React.Component {
state = {
numString: '12',
resetted: false,
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if ( !this.state.resetted ) {
this.setState({
numString: '',
resetted: true,
}, () => {
this.setState( prevState => ({
numString: prevState.numString.concat(num)
}))
})
} else {
this.setState(prevState => ({
numString: prevState.numString.concat(num)
}))
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = { padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem' };
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</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>
You can do something like below to clear the state immediately and concatenate the state with previous state value
this.setState({
numString: ''
}, () => {
this.setState( prevState => ({
numString: prevState.numString + num
}));
});
The code above in your question , in first setState you are setting variable to empty and in the second setState it is concatenating new value with empty string state. Thats why it is not working.
Try something like below:
class App extends Component {
state = {
numString: '12',
isFirstTime: true
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if(this.state.isFirstTime){
this.setState({
numString: '',
isFirstTime: false
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}else{
this.setState({
numString: this.state.numString.concat(num)
})
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data- num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}