Render Marker at certain interval - reactjs

In context to my previous question,
I'm looking at rendering marker on map at certain intervals, like 10sec or 20 sec.
Below is the code ...
{
setInterval(() => {
this.tmcount += 1;
console.log(this.tmcount);
console.log(this.state.geoData[this.tmcount]);
return (
<Marker key={this.tmcount} position={[this.state.geoData[this.tmcount].lat, this.state.geoData[this.tmcount].lng]}>
<Popup>
{this.tmcount} Hi!!!
</Popup>
</Marker>
)
}, 1000)
}
I tried in above way but its not rendering..

Your question needs a lot of work. I have no idea of the context of where this setInterval is running, or where your actual map component is. That being said, you need to think of this differently. You want your map to update periodically, which means you need to have some state variable that keeps track of what's updating, and the map should respond in kind. Let's set up a component that has an array of markers in its state, and a map that renders what's in that array at any given time:
const MapComponent = () => {
const [markers, setMarkers] = useState([]) // start with nothing
return (
<MapContainer center={center} zoom={zoom}>
{markers.map((marker, index) => {
const key = {index} // bad idea here
const {lat, lng} = marker
return <Marker key ={} position={} />
})}
</MapContainer>
)
}
Initially, you'll have no markers, because your state variable is an empty array. But your component expects that if there are markers, they will have a .lat and .lng property on each entry in that array. Now, assuming you have some datasource which is an array of objects containing location info, you can set up a useEffect to progressively update your state variable with new entries from that dataset:
const [markers, setMarkers] = useState([])
useEffect(() => {
const interval = setInterval(() => {
// clone state variable
const updatedMarkers = [...markers]
// add next marker entry from source dataset
updatedMarkers.push(geoData[markers.length])
// setMarkers(updatedMarkers)
}, 1000) // 1 second interval
return () => { clearInterval(interval) } // clean up interval on unmount
}, [])
return ( ... )
So now, on component mount, a timer is set up to update the state every second with the next entry from your source data. The map will respond in kind and add the next marker every second. I haven't tested this, but hopefully its enough to give you an idea of what to do.

Related

Proper on implementing incremental values

Now I know the title may be a bit vague so let me help you by explaining my current situation:
I have an array worth of 100 object, which in turn contain a number between 0 and 1. I want to loop through the array and calculate the total amount e.g (1 + 1 = 2).
Currently using .map to go through every object and calaculate the total. When I am counting up using the useState hook, it kinda works. My other approach was using a Let variabele and counting up like this. Although this is way to heavy for the browser.
I want to render the number in between the counts.
const[acousticness, setAcousticness] = useState(0);
let ids = [];
ids.length == 0 && tracks.items.map((track) => {
ids.push(track.track.id);
});
getAudioFeatures(ids).then((results) => {
results.map((item) => {
setAcousticness(acousticness + item.acousticness)
})
})
return (
<div>
Mood variabele: {acousticness}
</div>
)
What is the proper way on doing this?
I think this is roughly what you are after:
import {useMemo, useEffect, useState} from 'react';
const MiscComponent = ({ tracks }) => {
// Create state variable / setter to store acousticness
const [acousticness, setAcousticness] = useState(0);
// Get list of ids from tracks, use `useMemo` so that it does not recalculate the
// set of ids on every render and instead only updates when `tracks` reference
// changes.
const ids = useMemo(() => {
// map to list of ids or empty array if `tracks` or `tracks.items` is undefined
// or null.
return tracks?.items?.map(x => x.track.id) ?? [];
}, [tracks]);
// load audio features within a `useEffect` to ensure data is only retrieved when
// the reference of `ids` is changed (and not on every render).
useEffect(() => {
// create function to use async/await instead of promise syntax (preference)
const loadData = async () => {
// get data from async function (api call, etc).
const result = await getAudioFeatures(ids);
// calculate sum of acousticness and assign to state variable.
setAcousticness(result?.reduce((a, b) => a + (b?.acousticness ?? 0), 0) ?? 0)
};
// run async function.
loadData();
}, [ids, setAcousticness])
// render view.
return (
<div>
Mood variabele: {acousticness}
</div>
)
}

Make a momentary highlight inside a virtualized list when render is not triggered

I have an extensive list of items in an application, so it is rendered using a virtual list provided by react-virtuoso. The content of the list itself changes based on API calls made by a separate component. What I am trying to achieve is whenever a new item is added to the list, the list automatically scrolls to that item and then highlights it for a second.
What I managed to come up with is to have the other component place the id of the newly created item inside a context that the virtual list has access to. So the virtual list looks something like this:
function MyList(props) {
const { collection } = props;
const { getLastId } useApiResultsContext();
cosnt highlightIndex = useRef();
const listRef = useRef(null);
const turnHighlightOff = useCallback(() => {
highlighIndex.current = undefined;
}, []);
useEffect(() => {
const id = getLastId();
// calling this function also resets the lastId inside the context,
// so next time it is called it will return undefined
// unless another item was entered
if (!id) return;
const index = collection.findIndex((item) => item.id === if);
if (index < 0) return;
listRef.current?.scrollToIndex({ index, align: 'start' });
highlightIndex.current = index;
}, [collection, getLastId]);
return (
<Virtuoso
ref={listRef}
data={collection}
itemContent={(index, item) => (
<ItemRow
content={item}
toHighlight={highlighIndex.current}
checkHighlight={turnHighlightOff}
/>
)}
/>
);
}
I'm using useRef instead of useState here because using a state breaks the whole thing - I guess because Virtuouso doesn't actually re-renders when it scrolls. With useRef everything actually works well. Inside ItemRow the highlight is managed like this:
function ItemRow(props) {
const { content, toHighlight, checkHighligh } = props;
const highlightMe = toHighlight;
useEffect(() => {
toHighlight && checkHighlight && checkHighligh();
});
return (
<div className={highlightMe ? 'highligh' : undefined}>
// ... The rest of the render
</div>
);
}
In CSS I defined for the highligh class a 1sec animation with a change in background-color.
Everything so far works exactly as I want it to, except for one issue that I couldn't figure out how to solve: if the list scrolls to a row that was out of frame, the highlight works well because that row gets rendered. However, if the row is already in-frame, react-virtuoso does not need to render it, and so, because I'm using a ref instead of a state, the highlight never gets called into action. As I mentioned above, using useState broke the entire thing so I ended up using useRef, but I don't know how to force a re-render of the needed row when already in view.
I kinda solved this issue. My solution is not the best, and in some rare cases doesn't highlight the row as I want, but it's the best I could come up with unless someone here has a better idea.
The core of the solution is in changing the idea behind the getLastId that is exposed by the context. Before it used to reset the id back to undefined as soon as it is drawn by the component in useEffect. Now, instead, the context exposes two functions - one function to get the id and another to reset it. Basically, it throws the responsibility of resetting it to the component. Behind the scenes, getLastId and resetLastId manipulate a ref object, not a state in order to prevent unnecessary renders. So, now, MyList component looks like this:
function MyList(props) {
const { collection } = props;
const { getLastId, resetLastId } useApiResultsContext();
cosnt highlightIndex = useRef();
const listRef = useRef(null);
const turnHighlightOff = useCallback(() => {
highlighIndex.current = undefined;
}, []);
useEffect(() => {
const id = getLastId();
resetLastId();
if (!id) return;
const index = collection.findIndex((item) => item.id === if);
if (index < 0) return;
listRef.current?.scrollToIndex({ index, align: 'start' });
highlightIndex.current = index;
}, [collection, getLastId]);
return (
<Virtuoso
ref={listRef}
data={collection}
itemContent={(index, item) => (
<ItemRow
content={item}
toHighlight={highlighIndex.current === index || getLastId() === item.id}
checkHighlight={turnHighlightOff}
/>
)}
/>
);
}
Now, setting the highlightIndex inside useEffect takes care of items outside the viewport, and feeding the getLastId call into the properties of each ItemRow takes care of those already in view.

Is my usage of useEffect to generate array correct here?

I want to generate a 16-length array of random prizes using prizes array that is passed as a prop in Board component, and display them.
prizes array -
[
{
prizeId: 1,
name: 'coupon',
image: 'img/coupon.svg',
},
{
prizeId: 2,
name: 'gift card',
image: 'img/gift-card.svg',
},
// more prizes
]
In Board.js -
const Board = ({ prizes }) => {
const [shuffledPrizes, setShuffledPrizes] = useState(null)
useEffect(() => {
setShuffledPrizes(shuffleArray(populatePrize(16, prizes)))
}, [prizes])
return (
<div>
{
shuffledPrizes && shuffledPrizes.map((prize) => (
<Prize
key={prize.id}
prize={prize}
/>
))
}
</div>
)
}
In populatePrize function, I have to add id to use as React key because already existed prizeId can't be used, as prizes will be duplicated -
import { nanoid } from 'nanoid'
const populatePrize = (noOfBlock, prizeArray) => {
const arrayToPopulate = []
let index = 0
for (let i = 0; i < noOfBlock; i += 1, index += 1) {
if (index === prizeArray.length) {
index = 0
}
arrayToPopulate.push({
id: nanoid(),
prizeId: prizeArray[index].prizeId,
name: prizeArray[index].name,
image: prizeArray[index].image,
})
}
return arrayToPopulate
}
Is using useState and useEffect necessary here? Because, I don't think generating an array and shuffling it is a side effect, and I can just use a variable outside of Board function like -
let shuffledPrizes = null
const Board = ({ prizes }) => {
if (!shuffledPrizes)
shuffledPrizes = shuffleArray(populatePrize(16, prizes))
}
return (
<div>
{
shuffledPrizes.map((prize) => (
<Prize
key={prize.id}
prize={prize}
/>
))
}
</div>
)
}
But, with that way, every <Board /> component references and display the same shuffledPrizes array, not randomly for each Board component like I want.
Reusing Board is not a requirement, but I read in React docs about components being pure functions and I don't think mine is one. I am also confused in when to use a variable outside or inside of a component, and when to use state.
Although my question might be about using useEffect, I want to learn how to improve this code in proper React way.
This in indeed not a good use case of useEffect.
Effects are an escape hatch from the React paradigm. They let you
“step outside” of React and synchronize your components with some
external system like a non-React widget, network, or the browser DOM.
If there is no external system involved (for example, if you want to
update a component’s state when some props or state change), you
shouldn’t need an Effect. Removing unnecessary Effects will make your
code easier to follow, faster to run, and less error-prone.
You can shuffle the array when you pass it trough props.
const BoardContainer = () => <div>
<Board prizes={shuffleArray(populatePrize(16, prices))}/>
<Board prizes={shuffleArray(populatePrize(16, prices))}/>
</div>
You can also use the lazy version of useState that is only evaluated during the first render
const Board = ({prizes}) => {
const [shuffledPrizes,] = useState(() => shuffleArray(populatePrize(16, prizes)))
return (
<div>
<ul>
{
shuffledPrizes && shuffledPrizes.map((prize) => (
<Prize
key={prize.id}
prize={prize}
/>
))
}
</ul>
</div>
)
}
Your prizes are given in props, so they can potentially be updated ? By a fetch or something like that.
In that case, you can :
cont defaultArray = []; // avoid to trigger useEffect at each update with a new array in initialization
const Board = ({ prizes = defaultArray }) => {
const [shuffledPrizes, setShuffledPrizes] = useState([])
useEffect(() => {
if(prizes.length) {
setShuffledPrizes(shuffleArray(populatePrize(16, prizes)));
}
}, [prizes]);
return (
<div>
{
shuffledPrizes.map((prize) => (
<Prize
key={prize.id}
prize={prize}
/>
))
}
</div>
)
}
If you do :
const Board = ({ prizes }) => {
const shuffledPrizes = shuffleArray(populatePrize(16, prizes))
return (
<div>
{
shuffledPrizes.map((prize) => (
<Prize
key={prize.id}
prize={prize}
/>
))
}
</div>
)
}
populatePrize and shuffleArray will be called at each render. Maybe it could works if your only props is prices and you use React.memo. But it's harder to maintain, I think.
Making a variable out of your component like that, will not let your component listen to this variable modifications. You can do this for constants.
Each render you test !shuffledPrizes so when it will be filled once, your variable will be filled too and your component will render correctly. But if you change prizes, shuffledPrizes will not be updated. It's not a good practice.
With a different condition, you can continue to update your out component variable listening to prop changes that trigger a render. But useEffect is the better way to listen if your prop changes.
In the code you post, shuffledPrizes can be null, so you should put a condition before calling .map()
My self, I would call the suffle function in the parent that store it in is state, to store it directly with shuffling and not calling shuffle function at a wrong rerender.

React native: useState not updating correctly

I'm new to react native and currently struggling with an infinite scroll listview. It's a calendar list that need to change depending on the selected company (given as prop). The thing is: the prop (and also the myCompany state are changed, but in the _loadMoreAsync method both prop.company as well as myCompany do hold their initial value.
import * as React from 'react';
import { FlatList } from 'react-native';
import * as Api from '../api/api';
import InfiniteScrollView from 'react-native-infinite-scroll-view';
function CalenderFlatList(props: { company: any }) {
const [myCompany, setMyCompany] = React.useState(null);
const [data, setData] = React.useState([]);
const [canLoadMore, setCanLoadMore] = React.useState(true);
const [startDate, setStartDate] = React.useState(undefined);
let loading = false;
React.useEffect(() => {
setMyCompany(props.company);
}, [props.company]);
React.useEffect(() => {
console.log('set myCompany to ' + (myCompany ? myCompany.name : 'undefined'));
_loadMoreAsync();
}, [myCompany]);
async function _loadMoreAsync() {
if ( loading )
return;
loading = true;
if ( myCompany == null ) {
console.log('no company selected!');
return;
} else {
console.log('use company: ' + myCompany.name);
}
Api.fetchCalendar(myCompany, startDate).then((result: any) => {
// code is a little more complex here to keep the already fetched entries in the list...
setData(result);
// to above code also calculates the last day +1 for the next call
setStartDate(lastDayPlusOne);
loading = false;
});
}
const renderItem = ({ item }) => {
// code to render the item
}
return (
<FlatList
data={data}
renderScrollComponent={props => <InfiniteScrollView {...props} />}
renderItem={renderItem}
keyExtractor={(item: any) => '' + item.uid }
canLoadMore={canLoadMore}
onLoadMoreAsync={() => _loadMoreAsync() }
/>
);
}
What I don't understand here is why myCompany is not updating at all in _loadMoreAsync while startDate updates correctly and loads exactly the next entries for the calendar.
After the prop company changes, I'd expect the following output:
set myCompany to companyName
use company companyName
But instead i get:
set myCompany to companyName
no company selected!
I tried to reduce the code a bit to strip it down to the most important parts. Any suggestions on this?
Google for useEffect stale closure.
When the function is called from useEffect, it is called from a stale context - this is apparently a javascript feature :) So basically the behavior you are experiencing is expected and you need to find a way to work around it.
One way to go may be to add a (optional) parameter to _loadMoreAsync that you pass from useEffect. If this parameter is undefined (which it will be when called from other places), then use the value from state.
Try
<FlatList
data={data}
renderScrollComponent={props => <InfiniteScrollView {...props} />}
renderItem={renderItem}
keyExtractor={(item: any) => '' + item.uid }
canLoadMore={canLoadMore}
onLoadMoreAsync={() => _loadMoreAsync() }
extraData={myCompany}
/>
If your FlatList depends on a state variable, you need to pass that variable in to the extraData prop to trigger a re-rendering of your list. More info here
After sleeping two nights over the problem I solved it by myself. The cause was an influence of another piece of code that used React.useCallback(). And since "useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed" (https://reactjs.org/docs/hooks-reference.html#usecallback) the code worked with the old (or initial) state of the variables.
After creating the whole page new from scratch I found this is the reason for that behavior.

State getting reset to 0 after render

I am rendering photos from unsplash api. And I am keeping the index of the photos to be used in the lightbox, after the initial render state of imageindex goes back to 0, how can I retain its value?
I will show some code
const ImageList = ({ image, isLoaded }) => {
const [imageIndex, setImageIndex] = useState(0);
const [isOpen, setIsOpen] = useState('false');
const onClickHandler = (e) => {
setIsOpen(true);
setImageIndex(e.target.id);
};
const imgs = image.map((img, index) => (
<img
id={index}
key={img.id}
src={img.urls.small}
onClick={onClickHandler}
if (isOpen === true) {
return (
<Lightbox
onCloseRequest={() => setIsOpen(false)}
mainSrc={image[imageIndex].urls.regular}
onMoveNextRequest={() => setImageIndex((imageIndex + 1) % image.length)}
onMovePrevRequest={() => setImageIndex((imageIndex + image.length - 1) % image.length)}
nextSrc={image[(imageIndex + 1) % image.length].urls.regular}
prevSrc={image[(imageIndex + image.length - 1) % image.length].urls.regular}
/>
after the initial render state, imageIndex goes back to 0.
That makes sense, the initial render would use whatever you set as the default value. You can use something like local storage to help you keep track of the index of the last used item. It's a bit primitive, but until you integrate something like Node/MongoDB for database collections, this will be perfect.
In your component, import useEffect() from React. This hook lets us execute some logic any time the state-index value changes, or anything else you might have in mind.
import React, { useEffect } from "react"
Then inside your component, define two useEffect() blocks.
Getting last used index from localStorage on intitial load:
useEffect(() => {
const lastIndex = localStorage.getItem("index", imageIndex)
setImageIndex(imageIndex)
}, []) //set as an empty array so it will only execute once.
Saving index to localStorage on change:
useEffect(() => {
localStorage.setItem("index", imageIndex)
}, [imageIndex]) //define values to subscribe to here. Will execute anytime value changes.

Resources