React-native: 0.47.1
How would I maintain the aspect ratio for height when the width is 100%? Basically I want to auto scale the height while maintaining the aspect ratio. The images will be displayed in a FlatList (not necessarily, if something else worls I'm happy to change the FlatList). Images are displayed from an array of data which is set in the state:
export default class ImageTest extends Component {
constructor(props) {
super(props)
this.state = {
data : [
{id: 1, name: "image1", url: "http://urlOfImage.com/1.jpg"}
{id: 2, name: "image2", url: "http://urlOfImage.com/2.jpg"}
{id: 3, name: "image3", url: "http://urlOfImage.com/3.jpg"}
{id: 4, name: "image4", url: "http://urlOfImage.com/4.jpg"}
{id: 5, name: "image5", url: "http://urlOfImage.com/5.jpg"}
];
}
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem = {({item})=>
<Text style={styles.item}>{item.name}</Text>
<Image
source={uri: item.url}
style={styles.myImageStyle} />
}
/>
</View>
)
}
}
const styles = StyleSheet.create(
myImageStyle: {
width: Dimensions.get('window').width,
height: /* WHAT DO I PUT HERE ?? */
}
)
So the images may vary in height, original height may be 700px, some maybe 1200px, or even a square image. How would I define the height for each image since they're coming from an array?
Many thanks
You can take a dimensions of the image from URI. You can loop through your array and take the dimensions for each image using static method getSize() included in Image component. Here is a documentation for this method
Example:
const preloadedImgData = []
this.state.data.forEach((img) => {
Image.getSize(img.url, (w, h) => {
img.dimensions = {w, h}
preloadedImgData.push(img)
})
})
I'm not sure if above code is 100% correct but you got the idea :)
EDIT:
Also you can use this module which should do everything for you: react-native-fit-image
- I'm not owner of this module
Related
I am trying to adapt the example Display HTML clusters with custom properties for react-map-gl.
I got basic clusters without custom styling working (adapted from Create and style clusters):
<ReactMapGL ref={mapRef}>
<Source id="poi-modal-geojson" type="geojson" data={pointsToGeoJSONFeatureCollection(points)}
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
>
<Layer {...{
id: 'clusters',
type: 'circle',
source: 'poi-modal-geojson',
filter: ['has', 'point_count'],
paint: {
'circle-color': [
'step',
['get', 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
],
'circle-radius': [
'step',
['get', 'point_count'],
20,
100,
30,
750,
40
]
}
}} />
<Layer {...{
id: 'unclustered-point',
type: 'circle',
source: 'poi-modal-geojson',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 4,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
}} />
</Source>
</ReactMapGL>
Here, pointsToGeoJSONFeatureCollection(points: any[]): GeoJSON.FeatureCollection<GeoJSON.Geometry> is a function returning a GeoJSON (adapted from here).
However, I need more complex styling of markers and I am trying to adapt Display HTML clusters with custom properties without success so far. I mainly tried to adapt updateMarkers() and to call it inside useEffect():
const mapRef: React.Ref<MapRef> = React.createRef();
const markers: any = {};
let markersOnScreen: any = {};
useEffect(() => {
const map = mapRef.current.getMap();
function updateMarkers() {
const newMarkers: any = {};
const features = map.querySourceFeatures('poi-modal-geojson');
// for every cluster on the screen, create an HTML marker for it (if we didn't yet),
// and add it to the map if it's not there already
for (const feature of features) {
const coords = feature.geometry.coordinates;
const props = feature.properties;
if (!props.cluster) continue;
const id = props.cluster_id;
let marker = markers[id];
if (!marker) {
let markerProps = {
key: 'marker' + id,
longitude: coords[0],
latitude: coords[1],
className: 'mapboxgl-marker-start'
}
const el = React.createElement(Marker, markerProps, null),
marker = markers[id] = el;
}
newMarkers[id] = marker;
if (!markersOnScreen[id]) {
// TODO re-add
// marker.addTo(map);
}
}
// for every marker we've added previously, remove those that are no longer visible
for (const id in markersOnScreen) {
if (!newMarkers[id]) delete markersOnScreen[id];
}
markersOnScreen = newMarkers;
}
// after the GeoJSON data is loaded, update markers on the screen on every frame
map.on('render', () => {
if (!map.isSourceLoaded('poi-modal-geojson')) return;
updateMarkers();
});
}, [points]);
Unfortunately, the Marker created using React.createElement() isn't displayed I am not sure what is the right approach to create Marker elements in updateMarkers() or if my approach is completely wrong.
There is a great article on marker clustering which uses the supercluster and use-supercluster libraries and it makes clustering really easy not only for map box but for other map libraries as well, you can find it here.
You just have to convert your points into GeoJSON Feature objects in order to pass them to the useSupercluster hook and for the calculations to work. It will return an array of points and clusters depending on your current viewport, and you can map through it and display the elements accordingly based on the element.properties.cluster flag.
The properties property of the GeoJSON Feature object can be custom so you can pass whatever you need to display the markers later on when you get the final cluster array.
I would like to implement a dynamic carousel view of screens (similar to Swiper). I was thinking of creating a View with conditional rendering that would display a screen by selectedId corresponding to each screen, but that would cause a re-render on every screen, unless I have a state for each one of them.
Is there any better solution to make a carousel view of screens without using navigation and keep the data on the screen saved?
I have used react-native-progress-steps for this solution. I have modified the config file a little and wrote a dummy data template for it.
Dummy data:
const reviewData = [
{
id: "1",
serviceID: 0,
},
{
id: "2",
serviceID: 1,
},
{
id: "3",
serviceID: 2,
},
];
render (disregard colors):
<View style={styles.container}>
<ProgressSteps
activeStepIconColor={defaultColors.white}
completedProgressBarColor={settings.colors.primary}
progressBarColor={defaultColors.light}
activeStepIconBorderColor={settings.colors.primary}
completedStepIconColor={settings.colors.primary}
disabledStepIconColor={defaultColors.light}
activeStepNumColor={defaultColors.dark}
disabledStepNumColor={defaultColors.mediumLight}
>
{reviewData.map((item, index) => (
<ProgressStep
nextBtnStyle={styles.nextButtonStyle}
previousBtnStyle={
index === 0
? styles.disabledButton
: styles.prevButtonStyle
}
nextBtnTextStyle={styles.nextButtonTextStyle}
previousBtnTextStyle={styles.prevButtonTextStyle}
previousBtnText={`Назад`}
nextBtnText={`Дальше`}
finishBtnText={`Завершить`}
previousBtnDisabled={index === 0 ? true : false}
>
<View style={{ alignItems: "left" }}>
<AppText>
{item.serviceID},{index}
</AppText>
</View>
</ProgressStep>
))}
</ProgressSteps>
</View>
I am satisfied with the solution, but if you have any other solutions, please let me know :)
I'm writing a sticky header above a FlatList, I have what I want, look at the picture below, but there are two problems about it. The effect I want is:
At first, the picture's height is 170
As user scrolls up, the picture goes up as well
But when user has scrolled 100, the picture stops going up and stays there, (meaning that the bottom 70 of the picture is showing)
Meanwhile, the opacity reduces from 1 to 0.5 minimum when user scrolls
The code is below the GIF
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
const FULL_HEADER_HEIGHT = 170;
const DEFAULT_HEADER_HEIGHT = 70;
const HEADER_DIFF = FULL_HEADER_HEIGHT - DEFAULT_HEADER_HEIGHT;
class TestScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
};
this.scrollY = new Animated.Value(0); // How many pixels scrolled
this.headerOpacity = this.scrollY.interpolate({
inputRange: [0, HEADER_DIFF, HEADER_DIFF + 1],
outputRange: [1, 0.5, 0.5]
});
this.headerPositionY = this.scrollY.interpolate({
inputRange: [0, HEADER_DIFF, HEADER_DIFF + 1],
outputRange: [0, -HEADER_DIFF, -HEADER_DIFF]
});
}
render() {
return (
<View style={{ ... }}>
<Animated.View
style={{
transform: [{ translateY: this.headerPositionY }],
opacity: this.headerOpacity
}}
>
<Image
source={...}
style={{ width: '100%', height: FULL_HEADER_HEIGHT }}
resizeMode='cover'
/>
</Animated.View>
<AnimatedFlatList
data={this.state.data}
keyExtractor={(item) => item}
renderItem={({ item }) => (...)}
onScroll={Animated.event([{ nativeEvent: contentOffset: { y: this.scrollY } }], { useNativeDriver: true })}
/>
</View>
);
}
}
export default TestScreen;
Problem 1
The animation is not smooth
Problem 2
When the image goes up, its container seems to have stayed still, when elements of FlatList disappear, they don't disappear at the bottom of the picture, they disappear at the bottom of the original picture, which is at height 170
I think I have a similar problem with react-native: 0.55.4 on an iPhone 6 (real device). I tried to add a listener to the animated value and here is what it looks like:
18:24:57.157 Object {value: -39.919418166496385}
18:24:57.175 Object {value: -39.76205524921881}
18:24:57.200 Object {value: -39.42250543249108}
18:24:57.235 Object {value: -38.74063147167402}
18:24:57.626 Object {value: -20.959873329014293}
18:24:57.720 Object {value: -15.215298200036738}
18:24:57.742 Object {value: -13.990965698770527}
...
I know that the console adds a bias in the experiment, but we can still see some big time differences between different tick times, here ~400ms (which you can definitively see happening on the screen):
18:24:57.235 Object {value: -38.74063147167402}
18:24:57.626 Object {value: -20.959873329014293}
Compared to what looks normal ~20ms (60FPS should result in ~17ms between each tick):
18:24:57.157 Object {value: -39.919418166496385}
18:24:57.175 Object {value: -39.76205524921881}
Strangely when I do the animation in the other way, everything is just fine. What's also stranger is that I use the same animation (literally the same code) to animate different objects and some do just fine (both increasing and decreasing).
Just for the sake of comparison, here is the decrementing part:
18:24:55.541 Object {value: -1.124358489855093}
18:24:55.566 Object {value: -1.804371440300843}
18:24:55.592 Object {value: -3.0104530710073063}
18:24:55.607 Object {value: -3.896255056788916}
18:24:55.630 Object {value: -5.484551217549029}
18:24:55.661 Object {value: -7.919857696074535}
18:24:55.691 Object {value: -10.712999608975895}
18:24:55.707 Object {value: -12.43977121139206}
18:24:55.725 Object {value: -14.376069524734937}
18:24:55.743 Object {value: -16.422698462753544}
18:24:55.763 Object {value: -18.833926735827166}
18:24:55.796 Object {value: -23.027717951856513}
18:24:55.832 Object {value: -27.17280894556397}
18:24:55.865 Object {value: -30.581203230966732}
18:24:55.893 Object {value: -33.16734858413125}
18:24:55.908 Object {value: -34.426713502779336}
18:24:55.928 Object {value: -35.95476927982921}
18:24:55.957 Object {value: -37.822663476915075}
18:24:55.992 Object {value: -39.40622189728781}
18:24:56.007 Object {value: -39.80367046402294}
18:24:56.030 Object {value: -40}
The maximum duration between two frames looks to be around ~40ms.
Solution
I think I found what was causing this, I had a huge component that was updating while the animation was taking place. I think this caused JS to have more work to do and I guess that gives less opportunities for the animation events to be scheduled.. I'm not sure how it works internally but there probably is a single event loop (and a single execution thread) and while another routine (here the update method from my heavy component) is running, the animation step is not executed.
You have two solutions:
Check for updates that take place in parallel of your animation and prevent them from happening by defining shouldComponentUpdate(nextProps, nextState) for any component that you think is causing this JS slowdown to happen.
Use the native driver as suggested by this article: Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time
I am having a problem with the edge colors when rendering a network visualisation through a react app.
I have a MindMapComponent which contains a network.
I receive the data for the network through the props for the component:
class MindMapComponent extends React.Component {
//React component to display a single submission Item.
//Displays the text and author of a Perspective Item
constructor(props) {
super(props);
this.state = {map: props.mindMap, node_options: props.node_options, edge_options: props.edge_options}
}
I then go ahead and create the network in my componentDidMount function:
componentDidMount(){
var edge_dataset = new vis.DataSet(JSON.parse(this.state.map.conceptmap_edges));
var nodes_dataset = new vis.DataSet(JSON.parse(this.state.map.conceptmap_nodes));
var data = {
nodes:nodes_dataset ,
edges: edge_dataset
};
var edge_options =JSON.parse(this.state.edge_options);
edge_options.color = {inherit:false};
var options = {
edges: edge_options,
nodes: JSON.parse(this.state.node_options),
physics: false,
locale: 'en',
interaction: {
navigationButtons: true,
keyboard: true,
hover: true
}
};
var network = new vis.Network(this.refs.network, data,options)
console.log(network);
}
And finally render the whole thing in my render function
render() {
const liStyle = {
borderStyle: 'outset',
backgroundColor: 'lightgrey',
marginBottom: '10px',
listStyleType: 'none'
};
const metaStyle = {
paddingLeft: '15px'
}
const networkStyle = {
height:'250px',
borderRight:'dashed 2px'
}
var dateString = new Date(this.state.map.date_added);
dateString = dateString.getDate() + "/" + (dateString.getMonth() +1) + "/" + dateString.getFullYear();
return <li key={this.state.map.id} style={liStyle}>
<div className = 'row'>
<div className = 'col-lg-8' ref = "network" style = {networkStyle}></div>
<div className = 'col-lg-4' style = {metaStyle}><br/><p>Submitted on: {dateString}</p></div>
</div>
</li>
}
The final network should look like this (rendered in normal html+js app).
However in my react app the colors of the edges do not display
I took a look inside of the network data structure (through the console.log(network) at the end of component did mount).
The body.data.edges part of the structure contains the correct color value. However the body.edge.[id].options.color part is empty
I believe this is the source of my problem but am not sure whether my analysis is correct or how to go about fixing it.
I think I have a solution (I ran into the same problem as you, but then in Angular2).
Try setting the color as an Object (see http://visjs.org/docs/network/edges.html) and specify the inherit property to false. E.g.(using typescript)
const myEdge: Edge = {
id: 'myEdge',
from: 'NODE1',
to: 'NODE2',
arrows: 'to',
color: {color: '#ff0000', inherit: false};
dashes: true
}
You might also want to set the highlight and hover colors
Been trying to get into react and was looking at react-grid-layout when I came across a bit of a roadblock. I've pasted in the example from here essentially as is, but for some reason, when I drag an element it's not sticking. The error I'm getting in the console is:
Uncaught TypeError: this.props.onLayoutChange is not a function
I'm sure it's a simple thing that I'm missing, but this is my first React project and I would appreciate some guidance.
My code is included below:
'use strict';
var React = require('react');
var _ = require('lodash');
var ResponsiveReactGridLayout = require('react-grid-layout').Responsive;
/**
* This layout demonstrates how to use a grid with a dynamic number of elements.
*/
var AddRemoveLayout = React.createClass({
getDefaultProps() {
return {
className: "layout",
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
rowHeight: 100
};
},
getInitialState() {
return {
items: [0, 1, 2, 3, 4].map(function(i, key, list) {
return {i: i, x: i * 2, y: 0, w: 2, h: 2, add: i === list.length - 1};
}),
newCounter: 0
};
},
createElement(el) {
var removeStyle = {
position: 'absolute',
right: '2px',
top: 0,
cursor: 'pointer'
};
var i = el.add ? '+' : el.i;
return (
<div key={i} _grid={el}>
{el.add ?
<span className="add text" onClick={this.onAddItem} title="You can add an item by clicking here, too.">Add +</span>
: <span className="text">{i}</span>}
<span className="remove" style={removeStyle} onClick={this.onRemoveItem.bind(this, i)}>x</span>
</div>
);
},
onAddItem() {
console.log('adding', 'n' + this.state.newCounter);
this.setState({
// Add a new item. It must have a unique key!
items: this.state.items.concat({
i: 'n' + this.state.newCounter,
x: this.state.items.length * 2 % (this.state.cols || 12),
y: Infinity, // puts it at the bottom
w: 2,
h: 2
}),
// Increment the counter to ensure key is always unique.
newCounter: this.state.newCounter + 1
});
},
// We're using the cols coming back from this to calculate where to add new items.
onBreakpointChange(breakpoint, cols) {
this.setState({
breakpoint: breakpoint,
cols: cols
});
},
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
onRemoveItem(i) {
console.log('removing', i);
this.setState({items: _.reject(this.state.items, {i: i})});
},
render() {
return (
<div>
<button onClick={this.onAddItem}>Add Item</button>
<ResponsiveReactGridLayout onLayoutChange={this.onLayoutChange} onBreakpointChange={this.onBreakpointChange}
{...this.props}>
{_.map(this.state.items, this.createElement)}
</ResponsiveReactGridLayout>
</div>
);
}
});
module.exports = AddRemoveLayout;
React.render(<AddRemoveLayout/>, document.getElementById('app'))
The error you are receiving is an error about a missing prop. In a react component you basically have 2 places to keep your data, in its parent and in your component itself. Your parent often has props while declaring it because those are properties you pass to the child (like an attribute in an HTML tag). Then we have the state which is data inside a component itself.
The error you are receiving is saying that we didn't get a required prop from our parent (You can also see that inside the onLayoutChange(layout) function a call is being made to the this.props.onLayoutChange(layout) method).
So basically we are missing a few props. In the example from GitHub there is a root file called test-hook.jsx (https://github.com/STRML/react-grid-layout/blob/master/test/test-hook.jsx). This root node has as a child ( the code you are trying to render directly ) in which it is passing the required function as a property.
You can either use the test-hook.jsx or you can write your own root node which has a state with the layout and the required function which updates that state (see the github example on how to do that).
So after some searching, I figured out that the example was specifying the onLayoutChange function as a placeholder. If I wanted a custom funcion, I needed to define that.
Simply removing this function altogether and using the default fixed the issue.
Remove this:
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
#Dirk-Jan explained it well. But the proper solution IMHO is to remove the prop call:
onLayoutChange(layout) {
// this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
So the meaningful part is still there. In the examples the test-hook.jsx parent has to get hold of the layout so it can display it outside of the layout container for demonstration purposes. In a real-world application we don't need that.