setTimeout inside useEffect with useState - javascript

I have array of objects inside my useState hook, and I would like to change one property of each array element let's say it looks like:
const array = [{id:1, isDisplayed: false}, {id:2, isDisplayed: false}, {id:3, isDisplayed: true}]
and while Im trying to use setTimeout inside useEffect hook to change property displayed everywhere where it's not true to isDisplayed: true , it waits for the dedicated time, and changes everything at once, what I want to achieve is to change each element with its own delay. I mean something like
const DELAY = 2000 and inside setTimeout for instance setTimeout(() => ... , DELAY * id)
because in place when I render jsx, everything appears all at once, and i just want to make small delays between appearing each element. For instance, first element appears after 2s, second after 3s (not 3s after first one)
My current code looks like:
React.useEffect(() => {
setTimeout(() => {
setArray(array.map((item)=> !item.isDisplayed ? {...item, displayed: true} : item))
}, DELAY * Math.floor(Math.random() * 5);
}, [])

You can set a timeout and trigger an update, if some items are not visible.
And track if new items are revealed with revealed and if no new items were revealed, stop the flow.
function TodoApp() {
const [items, setItems] = React.useState([
{ id: 1, isDisplayed: false },
{ id: 2, isDisplayed: false },
{ id: 3, isDisplayed: false },
]);
React.useEffect(() => {
let currentTimeout = null;
const displayMoreItems = () => {
setItems(prevItems => {
let revealed = false;
const nextItems = prevItems.map(item => {
if (!revealed && !item.isDisplayed) {
revealed = true;
return { ...item, isDisplayed: true };
}
return item;
});
if (revealed) {
currentTimeout = setTimeout(() => {
displayMoreItems();
}, 1000);
}
return nextItems;
});
};
currentTimeout = setTimeout(() => {
displayMoreItems();
}, 1000);
return () => {
if (currentTimeout) {
clearTimeout(currentTimeout);
}
};
}, [setItems]);
return <div>{items.map(item => (item.isDisplayed ? item.id : null))}</div>;
}
ReactDOM.render(<TodoApp />, document.querySelector('#app'));
Here is a fiddle

const DELAY = 2000;
React.useEffect(() => {
let count = 1;
array.forEach((item) => {
if (!item.displayed) {
setTimeout(() => {
item.displayed = true;
setArray([...array]);
}, DELAY * count);
count ++;
}
})
}, [])

Related

Does React rendering effects the setTimeout or setInterval function wrapped in a useRef and useRef is good way to achieve pause, stop, update question

I'm trying to achieve the slider functionality, where I want to slider to move to the next question after every three seconds [this part is working fine]
Features i'm trying to achieve:
OnMouseEnter: Timer stops, css animation stops(working)
OnMouseleave: Start the timer from remaining time left
OnClick: Change Question number to the selected items (working) and start animation
The issue is when I changed the question_number by onClick event, the next question start showing after every 1 seconds instead of 3 seconds. I think the problem is somehow with the React Re-rendering.
setSlider is simple useState has object:
const [timeSlider, setTimeSlider] = useState({
question_length: 4,
question_number: 0,
current_time: 0
});
I'm using useEffect to check the viewport width so react Re-render the components everytime, Viewport changes
I tried to store the Interval for timer in a react useRef. but still not working. Here is my code:
I use useRef, so I can Resume, Pause and updateNumber outSide where the first time setInterval started
const interval = useRef(null);
After EverySecond update the useState: Seconds
useEffect(() => {
setRemaining({
start_time: Date.now(),
stop_time: 0
});
interval.current = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
pauseSlider();
return () => clearInterval(interval);
}, []);
And when ever the seconds state changed, I make sure after every three seconds. question_number increased only when its less than 3
useEffect(() => {
if (seconds === 3) {
setSeconds(seconds => 0);
setTimeSlider(prev => prev.question_number === 3 ? { ...timeSlider,
question_number: 0
} : { ...timeSlider,
question_number: timeSlider.question_number + 1
});
}
}, [seconds]);
Below are the pause, resume and update function.
function pauseSlider() {
setSliderHover(true)
setRemaining({
start_time: 0,
stop_time: Date.now()
});
clearInterval(interval.current);
}
function resumeSlider() {
setSliderHover(false)
setRemaining(prev => ({ ...prev,
start_time: prev.stop_time,
stop_time: 0
}));
interval.current = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
}
function updateNumber(num) {
setSliderHover(false)
setSeconds(0)
setRemaining({
start_time: 0,
stop_time: 0
});
clearInterval(interval.current);
interval.current = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
setTimeSlider({ ...timeSlider,
question_number: num
});
}
The Problem could be in these functions, I tried to use setTimeout instead of setInterval same results.
Below is the picture of slider, so you have more clear idea what i'm trying to achieve.
Instead of using the JS, I used the GSAP for this here is my solution for it
useLayoutEffect(() => {
const ctx = gsap.context((self) => {
TimelineRef.current = gsap.timeline({
defaults: {
repeat: -1,
ease: "none",
}
})
.to(".timerSlier_left",
{
x: "0%",
duration: 10,
onRepeat: () => UpdateToNextQuestion(timeSlider),
}
)
}, ContainerRef); // <- Scope!
return () => ctx.revert(); // <- Cleanup!
}, []);
const [ remaining, setRemaining ] = useState(0);
useEffect(()=>{
if ( isInViewport ){
resumeSlider()
}else{
pauseSlider()
}
}, [ isInViewport ]);
const UpdateToNextQuestion = () => {
settimeSlider( prev => prev.question_number === 3 ? ({...prev, question_number: 0}) : ({...prev, question_number: prev.question_number+1}))
}
function pauseSlider(){
console.log("stop slider")
setSliderHover(true)
TimelineRef.current.pause();
}
function resumeSlider(){
setSliderHover(false)
TimelineRef.current.resume();
}
function updateNumber(num){
TimelineRef.current.restart();
settimeSlider( timeSlider => ({ ...timeSlider, question_number: num }) );
console.log("RESUMING")
setSliderHover(false)
TimelineRef.current.resume();
}
I'm handing timeout functionality from GSAP timeline.

ag-grid-react: getSortModel is not a function

I'm trying to get sort model from ag-grid-react component using getSortModel() but I'm getting getSortModel is not a function
my code
onSortChanged={useCallback(e => console.log(e.api.getSortModel(), 'im from sort'))}
"#ag-grid-community/react": "27.3.0",
"#ag-grid-enterprise/all-modules": "27.3.0",
After spend some time found params.api.getSortModel() is deprecated after version 24.0.0.
Using Column state for get Sort model and set Sort model in the following way
getSortModel:
const onSortChanged = useCallback(() => {
const value = gridParams.columnApi.getColumnState().find(s => s.sort != null)
if (value) {
setSortModel([ value ])
} else {
setSortModel([])
}
}, [ gridParams, setSortModel ])
setSortModel:
useEffect(() => {
if (sortModel.length > 0) {
const curretSortModel = gridParams.columnApi.getColumnState()
const mergeSortModel = curretSortModel.map(o1 => sortModel.find(o2 => o2.colId === o1.colId) || o1)
gridParams.columnApi.setColumnState(mergeSortModel)
}
}, [gridParams, sortModel]
As per this plunkr, you can retrieve and apply sort with the following example: https://plnkr.co/edit/?open=index.jsx&preview
const sortByAthleteDesc = useCallback(() => {
gridRef.current.columnApi.applyColumnState({
state: [{ colId: 'athlete', sort: 'desc' }],
defaultState: { sort: null },
});
}, []);
const saveSort = useCallback(() => {
var colState = gridRef.current.columnApi.getColumnState();
var sortState = colState
.filter(function (s) {
return s.sort != null;
})
.map(function (s) {
return { colId: s.colId, sort: s.sort, sortIndex: s.sortIndex };
});
savedSort = sortState;
console.log('saved sort', sortState);
}, []);

After adding in Array element change oher element but not adding to array

i've got an array:
dataSet: [
{ name: "Имя1", image: "img.jpeg", author: "Александр Полтавченко", date: "21.02.2020", id: 1 },
{ name: "Имя2", image: "img.png", author: "Александр Полтавченко", date: "21.02.2020", id: 2 },
],
addedToCart: []
and here is the function which put value from dataSet to addedToCart according ID from props:
added = (id) => {
this.setState (( { addedToCart, dataList } )=>{
const newItem = dataList.filter(el=>el.id===id);
const testArr = [...addedToCart ];
const filteredATC = testArr.filter((item, el)=>{
if(addedToCart.indexOf(item)===el){
item.count++
return item, el
}
else {
return item
}
it is works well (only one element with count ++) but if click add to another element it is just change element in array (with correct count surprisingly).
How to put another element into addedToCart, just like
[
{el1},
{el2}
]
filter returns an array instead of the desired element, you should use find instead.
I believe you would desire an approach like this:
added = (id) => {
this.setState (( { addedToCart, dataList } ) => {
const newItem = dataList.find(el=> el.id === id);
const testArr = [...addedToCart ];
const filteredATCIndex = testArr.findIndex((_item, id) => newItem.id === id)
// if there is an added item
if (filteredATCIndex !== -1) {
const count = testArr[filteredATCIndex].count + 1
testArr[filteredATCIndex] = { ...testArr[filteredATCIndex], count }
return { addedToCart: testArr }
}
// for new item
const newItemAdded = { ...newItem, count: 1 }
testArr.push(newItemAdded)
return { addedToCart: testArr }
})
}
though this approach duplicates data, which is not desirable. I suggest you consider to change addedToCart to an object where key value pairs are the id and count respectively from added items. This way you would avoid duplicating data.
then your update state would look like:
added = (id) => {
this.setState (( { addedToCart } ) => {
const count = typeof addedToCart[id] === 'undefined' ? 1 : ++addedToCart[id]
return { addedToCart: { ...addedToCart, [id]: count } }
})
}

How do I update an array of objects in component state?

I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}

In Rx.js, how can I distinguish which stream triggers the combineLatest method?

I'm writing my own version of who to follow?. Clicking refreshButton will fetching suggestions list and refresh <Suggestion-List />, and closeButton will resue the data from suggestions list and refresh <Suggestion-List-Item />.
I want to let the closeClick$ and suggestions$ combine together to driving subscribers.
Demo code here:
var refreshClick$ = Rx.Observable
.fromEvent(document.querySelector('.refresh'), 'click')
var closeClick$ = Rx.Observable.merge(
Rx.Observable.fromEvent(document.querySelector('.close1'), 'click').mapTo(1),
Rx.Observable.fromEvent(document.querySelector('.close2'), 'click').mapTo(2),
Rx.Observable.fromEvent(document.querySelector('.close3'), 'click').mapTo(3)
)
var suggestions$ = refreshClick$
.debounceTime(250)
.map(() => `https://api.github.com/users?since=${Math.floor(Math.random()*500)}`)
.startWith('https://api.github.com/users')
.switchMap(requestUrl => Rx.Observable.fromPromise($.getJSON(requestUrl)))
Rx.Observable.combineLatest(closeClick$, suggestions$, (closeTarget, suggestions) => {
if (/* the latest stream is closeClick$ */) {
return [{
target: clickTarget,
suggestion: suggestions[Math.floor(Math.random() * suggestions.length)]
}]
}
if (/* the latest stream is suggestions$ */) {
return [1, 2, 3].map(clickTarget => ({
target: clickTarget,
suggestion: suggestions[Math.floor(Math.random() * suggestions.length)]
}))
}
})
Rx.Observable.merge(renderDataCollectionFromSuggestions$, renderDataCollectionFromCloseClick$)
.subscribe(renderDataCollection => {
renderDataCollection.forEach(renderData => {
var suggestionEl = document.querySelector('.suggestion' + renderData.target)
if (renderData.suggestion === null) {
suggestionEl.style.visibility = 'hidden'
} else {
suggestionEl.style.visibility = 'visible'
var usernameEl = suggestionEl.querySelector('.username')
usernameEl.href = renderData.suggestion.html_url
usernameEl.textContent = renderData.suggestion.login
var imgEl = suggestionEl.querySelector('img')
imgEl.src = "";
imgEl.src = renderData.suggestion.avatar_url
}
})
})
You can find it in JsFiddle.
You should note the comments in condition judgment, closeClick$ emits [{ target: x, suggestion: randomSuggestionX }], suggestions$ emits [{ target: 1, suggestion: randomSuggestion1 }, { target: 2, suggestion: randomSuggestion2 }, { target: 3, suggestion: randomSuggestion3 }]. Subsriber render interface according to the emitted data.
May there are some ways/hacks to distinguish the latest stream in combineLatest or elegant modifications?
I think the easiest way would be to use the scan() operator and always keep the previous state in an array:
Observable.combineLatest(obs1$, obs2$, obs3$)
.scan((acc, results) => {
if (acc.length === 2) {
acc.shift();
}
acc.push(results);
return acc;
}, [])
.do(states => {
// states[0] - previous state
// states[1] - current state
// here you can compare the two states to see what has triggered the change
})
Instead of do() you can use whatever operator you want of course.
Or maybe instead of the scan() operator you could use just bufferCount(2, 1) that should emit the same two arrays... (I didn't test it)

Categories

Resources