How to request new tiles in TileLayer (React Leaflet) every 30min? - reactjs

This is what I have:
const RL = require('react-leaflet');
var Map = RL.Map;
var TileLayer = RL.TileLayer;
<Map
zoom={14}
zoomSnap={0}
style={{height: "100%"}}
ref={(map) => { this.map = map; }}
>
<TileLayer url={`https://api.mapbox.com/styles/v1/${process.env.MAPBOX_URL}/tiles/256/{z}/{x}/{y}#2x?fresh=true&access_token=${process.env.MAPBOX_API}`}/>
But I can't figure out how to request new tiles every 30min? I need that to show updated traffic information...

Your goal is to re-render the component that includes Map and TileLayer every 30 mins. For that, you need some sort of state in your component that will have to change every 30 mins. To schedule the updates, setInterval method is the way to go.
Being said that, let's assume your component is called Parent and let's scaffold it:
class Parent extends Component {
state = {
updatesCount: 0
};
intervalId = null;
componentDidMount() {
this.intervalId = setInterval(this.update, 1800000) // every 30 mins
}
update = () => {
// this function will be called every 30 mins, updating the state and causing the component to re-render
this.setState({ updatesCount: this.state.updatesCount++ });
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
<div>
<Map
zoom={14}
zoomSnap={0}
style={{height: "100%"}}
ref={(map) => { this.map = map; }}
>
<TileLayer url={`https://api.mapbox.com/styles/v1/${process.env.MAPBOX_URL}/tiles/256/{z}/{x}/{y}#2x?fresh=true&access_token=${process.env.MAPBOX_API}`}/>
</div>
}
}

Related

React.js/ScrollMagic mount component after DOM modification

I'm trying to use ScrollMagic with React and I'm stuck with the setPin() function which wraps my component within a div. As the component in mounted before the wrapping, everytime I update my states or props, React seems to think that the component doesn't exist and mount it again.
How can I manage to mount my component after the setPin function which does the wrapping?
componentDidMount () {
this.scrollController = new ScrollMagic.Controller()
this.initScroller()
}
initScroller () {
this.scene = new ScrollMagic.Scene({
triggerElement: this.node,
duration: window.innerHeight * 2,
triggerHook: 0,
reverse: true
})
this.scene.setPin(this.node, { pushFollowers: false })
this.scrollController.addScene(this.scene)
}
render () {
return (
<div
ref={node => {
this.node = node
}}
>
<div className={this.getClassNames()}>
<Hemicycle
deputiesCount={this.props.deputiesCount}
swissCount={this.props.swissCount}
/>
</div>
</div>
)
}

How to scroll to bottom when props changed in react-virtualized?

I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;
You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate

React router native animation blinks before animation starts

I'm developing a react native app and using React router native v4, and I'm trying to develop the animation part, as suggested by documentation, first I made sure that everything works without animations or transitions.
I've iterated the implementation and this is as far as I got by now:
my main component renders the following:
// app.js:render
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
my routes.js renders the following (note the location prop passed to Switch to prevent it updating its children before the parent component):
// routes.js render
<ViewTransition pathname={location.pathname}>
<Switch location={location}>
<Route exact path={uri.views.main} component={Dashboard} />
<Route path={uri.views.login} component={Login} />
<Route path={uri.views.register} component={Register} />
</Switch>
</ViewTransition>
and the ViewTransition that handles the animation, by now it just fadesin/out the old and the new views:
// view-transition.js
#withRouter
export default class ViewTransition extends Component {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object.isRequired,
};
state = {
prevChildren: null,
opacity: new Animated.Value(1)
};
componentWillUpdate(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({ prevChildren: this.props.children }, this.animateFadeIn);
}
}
animateFadeIn = () => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 150
}).start(this.animateFadeOut);
};
animateFadeOut = () => {
this.setState({ prevChildren: null }, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 400
}).start();
});
};
render() {
const { children } = this.props;
const { prevChildren, opacity } = this.state;
return (
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
opacity,
position: "absolute"
}}
>
{prevChildren || children}
</Animated.View>
);
}
}
The code above is working, I can see old view fading out and new view fading in, but I have an issue, when it starts fading out, somehow the component remounts again and I can see a blink just before the animation starts, I wish to know what's wrong with my code.
I could fix my code above, it happened that the method componentWillUpdate in the lifecycle of a react component, already had passed the nextProps to the children, and in the meantime my component sets the new state with old children, the Switch is preparing the render of the new ones, that produces an unmount of the oldChildren and the mount of the new children, when finally my component finishes to set the state, the old children already had been unmounted and they have to be mounted again.
The story above is the long story of "I can see a blink when my animation starts", the solution happened to be easy, I don't check stuff in componentWillUpdate anymore but in componentWillReceiveProps, since the new props will pass to the parent component before its children, it gives me enough time to catch the current children, assign them to the state, render before Switch unmounts them and keep them in the View for the fading out, so no blinking anymore.
My final view-transition.js:
// view-transition.js
export default class ViewTransition extends Component {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object.isRequired,
};
state = {
prevChildren: null,
opacity: new Animated.Value(1)
};
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({ prevChildren: this.props.children }, this.animateFadeIn);
}
}
animateFadeIn = () => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 150
}).start(this.animateFadeOut);
};
animateFadeOut = () => {
this.setState({ prevChildren: null }, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 400
}).start();
});
};
render() {
const { children } = this.props;
const { prevChildren, opacity } = this.state;
return (
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
opacity,
position: "absolute"
}}
>
{prevChildren || children}
</Animated.View>
);
}
}
I wrote an AnimatedSwitch component to work in React Native.
There is a brief moment where the states are updating and it would flash, but I fixed that with this code:
// Need to render the previous route for the time between not animating and animating
if (needsAnimation && !animating) {
const tempPreviousRoute = getPreviousRoute(
exact,
previousLocation,
previousChildren,
);
if (tempPreviousRoute) {
const prevRouteComp = renderPreviousRoute(tempPreviousRoute);
return prevRouteComp;
} else {
return null;
}
}
Complete code is here.
https://javascriptrambling.blogspot.com/2020/07/react-router-native-animatedswitch.html
Hmm..i know it's little bit too late, maybe you can try react-router-native-animate-stack
It is subcomponent of react-router-native. But it is animatable and provide stack feature. It is inspired by switch component, if you know switch, you will know how to use this package.
Default behaviour
Customization

Using react-virtualized InfiniteLoader

Does react-virtualized's InfiniteLoader require seed data to function? The following is my component:
class Bookmarks extends PureComponent {
constructor(props) {
super(props);
this.loaded = {
cursor: null,
data: []
};
this._isRowLoaded = this._isRowLoaded.bind(this);
this._loadMoreRows = this._loadMoreRows.bind(this);
this._rowRenderer = this._rowRenderer.bind(this);
}
render() {
const size = this.loaded.data.length;
return (
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={size}>
{({onRowsRendered, registerChild}) =>
<AutoSizer>
{({width, height}) =>
<List
ref={registerChild}
height={height}
onRowsRendered={onRowsRendered}
rowCount={size}
rowHeight={30}
rowRenderer={this._rowRenderer}
width={width}
/>}
</AutoSizer>}
</InfiniteLoader>
);
}
_isRowLoaded({index}) {
return !!this.loaded.data[index];
}
_loadMoreRows({startIndex, stopIndex}) {
fetch('/api/bookmarks').then((response) => {
return response.json();
}).then((json) => {
this.loaded = {
cursor: json.cursor,
data: this.loaded.data.push(...json.data),
};
});
}
_rowRenderer({index, key, style}) {
return (
<div key={key} style={style}>{this.loaded.data[index]}</div>
);
}
}
In render(), size is initially zero as there's no data yet, and I assume the component would call _loadMoreRows—apparently not.
This is what I thought to be the flow of logic (which is incorrect):
Component is created
_loadMoreRows is called (1st time)
When the promise returned by _loadMoreRows is resolved, check each loaded rows with _isRowLoaded
Render each row
In render(), size is initially zero as there's no data yet, and I assume the component would call _loadMoreRows
This is the key to the misunderstanding. InfiniteLoader needs to know if there is more data to potentially load. The docs have a couple of simple recipes for working with InfiniteLoader:
If you know the total number of remote records then look at this example.
If you only know if there's at least 1 more record/page to be loaded,
look at this example.

React component does not react to mobx observable data

I am using mobX for my react native project. Please consider this store class:
class Birds {
#observable listOne = [];
#observable fetchingListOne = false;
#observable fetchErrorOne = '';
#action setListOne = () => {
this.fetchingListOne = true;
api.getList()
.then((data) => {
this.listOne.replace(data);
this.fetchingListOne = false;
})
.catch((error) => {
this.fetchingListOne = false;
this.fetchErrorOne = error;
});
};
}
And this the react component:
#inject('BirdStore') #observer
export default class Flat extends React.Component {
componentDidMount() {
this.props.BirdStore.setListOne();
}
_renderHeader = () => {
return <Text style={styles.listHeaderText}>
Hello {this.props.BirdStore.listOne.length} is {this.props.BirdStore.fetchingListOne.toString()}
</Text>;
};
_renderItem = ({item}) => {
return <Text style={styles.item}>{item.name}</Text>
};
_renderFooter = () => {
if (this.props.BirdStore.fetchingListOne) {
return <ActivityIndicator/>
}
else {
return null
}
};
render() {
const dataSource = this.props.BirdStore.listOne.slice();
return (
<View style={styles.container}>
<Text>Fetching: {this.props.BirdStore.fetchingListOne.toString()}</Text>
<FlatList
style={styles.listContainer}
ListHeaderComponent={this._renderHeader}
data={dataSource}
renderItem={this._renderItem}
keyExtractor={(item, i) => item.id}
ListFooterComponent={this._renderFooter}
/>
</View>
)
}
}
From above it looks to me that:
When the Flat component mounts, it call the method of the store setListOne().
setListOne() sets fetchingListOne to true and makes an api call.
On the component side, when the fetchingListOne is true, the ActivityIndicator displays, and in the ListHeaderComponent it should display true.
On the store side, after successful/unsuccessful response, it sets fetchingListOne to false.
Finally on the component side, because fetchingListOne is set to false, ActivityIndicator should not display and in the ListHeaderComponent it should display false.
However, this is not what's happening. Here when the setListOne() method is called, after it sets the fetchingListOne to true, the component does not react to the changes made after api call. And the ActivityIndicator keeps displaying and in ListHeaderComponent its displaying true.
What am I doing wrong here? Could you please help me. Thank you
Update
I have added a Text component before the FlatList. Adding a Text component or console logging inside the component class's render method does makes the FlatList react to the changes. I don't know why this is happening though.
The problem you are running into here most probably, is that although Flat is an observer component, FlatList is not (it's an built-in component after all). In this setup _renderFooter and the others are part are rendered by render of FlatList, but not of FlatList. Hence they are not part of the lifecycle of Flat, but of FlatList and as such are not tracked by Mobx
There are two ways to fix this, both pretty simple:
1) declare _renderItem as observer component:
_renderItem = observer(({item}) =>
<Text style={styles.item}>{item.name}</Text>
);
2) use an inline anonymous Observer component:
_renderItem = ({item}) =>
<Observer>{
() => <Text style={styles.item}>{item.name}</Text>}
</Observer>

Resources