Chart won't render upon state change - javascript

I am testing out react-native and I am trying to make a simple lineChart which redraws on props change. I have a parent component HomeScreen which passes an array of integers as props to the LineChart child component. However, the LineChart is never drawn.
I have tried passing in an already initialized array with dummy values. The lineChart child component will then render, but it won't re-render on subsequent state changes.
I have checked the actual values of state and props in react-devtools, and the childcomponent does receive the props and the state is updated. How can I make the chart render the props I pass it?
UPDATE: So I took the advice from the replies here, and made the component functional. The chart now renders, but there is still something wrong with the prop type. I'll investigate further and read up on the react-native-svg documentation. Thanks!
enter code here
import React from 'react';
import { View } from 'react-native';
import { LineChart, Grid } from 'react-native-svg-charts'
const BeatChart = ({ data }) => (
<LineChart
style={{ height: 200 }}
data={data}
svg={{ stroke: 'rgb(0, 255, 255)' }}
contentInset={{ top: 20, bottom: 20 }}
>
<Grid />
</LineChart>
);
export default BeatChart;

You don't need state in this example, so you could just use the functional component.
import React from 'react';
import { View } from 'react-native';
import { LineChart, Grid } from 'react-native-svg-charts'
const BeatChart = ({ data }) => (
<LineChart
style={{ height: 200 }}
data={data}
svg={{ stroke: 'rgb(0, 255, 255)' }}
contentInset={{ top: 20, bottom: 20 }}
>
<Grid />
</LineChart>
);
export default BeatChart;

since the render occurs on every change in props, you can comment this part of the code.
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.data !== prevState.data) {
return { data: nextProps.data };
}
return null;
}
even if you want to implement it, use componentWillmount if needed.
otherwise the code works fine.
so your final code be like:
import React from 'react';
import { View } from 'react-native';
import { LineChart, Grid } from 'react-native-svg-charts'
class BeatChart extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
data: [],//your data
}
};
// static getDerivedStateFromProps(nextProps, prevState) {
// if (nextProps.data != prevState.data) {
// return { this.setState({data: nextProps.data}) };
// }
// return null;
// }
render() {
const arr = this.state.data;
return (
<LineChart
style={{ height: 200 }}
data={arr}
svg={{ stroke: 'rgb(0, 255, 255)' }}
contentInset={{ top: 20, bottom: 20 }}
>
<Grid />
</LineChart>
)
}
}
export default BeatChart;

Related

Get total number of items rendered from FlatList, and then update that number based on item state changes

I'm very new to React Native (and React/Javascript in general to be honest) and am very stuck. I have a chore tracking app that renders a list of chores using Flatlist, that can then be swiped/checked off using React Native Gesture Handler Swipeable.
I want a tag above the chore list that shows the total number of chores completed (the placeholder currently says "?? Chores complete"). I know this entails finding 1) How many of my ChoreListItems are rendered from the Flatlist component and then 2) How many of those items have a state of "isComplete". I have several nested/reusable components since there will be several versions of this screen (Laundry, kitchen, bathroom, etc.) and I know that's making it even more confusing for me. I feel like there's probably an obvious answer here, but I'm lost on how to even begin unpacking this.
Here is the ChoreListItem (what is rendered from Flatlist and able to be swiped):
import React, { useState, useRef} from 'react';
import { Animated, Image, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import appStyles from '../config/appStyles';
import colors from '../config/colors';
import BodyText from '../config/BodyText';
function ChoreListItem({title}) {
const [isComplete, setIsComplete] = useState(false);
const swipeableRef = useRef();
//Track interaction occurs on left swipe
const LeftActions = (progress, dragX) => {
const scale = dragX.interpolate({
inputRange: [0, 100],
outputRange: [0,1],
extrapolate: 'clamp',
});
if (isComplete == false) {
return (
<View style ={[appStyles.card, styles.leftActions]}>
<Animated.Text style={[styles.swipeText, {transform: [{scale}], fontSize:16, }]}>Swipe to track</Animated.Text>
<Image source={require('../assets/control_arrow_right.png')} style={{alignItems:'center',tintColor:colors.baseWhite,}}/>
</View>
);
}
};
//Untrack button renders on right swipe
const RightActions = (progress, dragX) => {
const scale = dragX.interpolate({
inputRange: [-100,0],
outputRange: [1,0],
extrapolate: 'clamp',
});
if (isComplete === true) {
return (
<TouchableWithoutFeedback onPress={closeSwipeable}>
<View style ={[appStyles.card, styles.rightActions]}>
<Animated.Text style={[styles.swipeText,{transform: [{scale}], fontSize:16, }]}>Tap to untrack</Animated.Text>
</View>
</TouchableWithoutFeedback>
);
}
};
//Closes swiped action and changes state
const closeSwipeable = () => {
if (isComplete===false) {
setIsComplete (true);
console.log(title + ' tracked');
} else {
setIsComplete(false);
console.log(title + ' untracked');
}
}
return (
<Swipeable
ref={swipeableRef}
state={isComplete}
renderLeftActions={LeftActions}
leftThreshold={20}
rightThreshold={10}
overshootRight={false}
renderRightActions={RightActions}
onSwipeableLeftOpen={closeSwipeable}
>
<View style={[appStyles.card, styles.choreListItem]}>
<BodyText style={{textDecorationLine: isComplete ? "line-through" : "none"}}>{title}</BodyText>
<Image style={[styles.checkmark, {display: isComplete ? "flex" : "none"}]} source={require('../assets/checkmark_large.png')}/>
</View>
</Swipeable>
);
}
export default ChoreListItem;
const styles = StyleSheet.create({
checkmark: {
width:16,
height:16,
},
choreListItem: {
paddingLeft:16,
paddingRight:16,
paddingTop:20,
paddingBottom:20,
marginBottom:16,
flex:1,
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between'
},
swipeText: {
color: colors.baseWhite,
},
leftActions: {
paddingLeft:16,
paddingRight:16,
paddingTop:20,
paddingBottom:20,
marginBottom: 16,
backgroundColor: colors.primaryBlue,
flex: 1,
shadowColor: 'transparent',
alignItems:'center',
flexDirection:'row'
},
rightActions: {
paddingLeft:16,
paddingRight:16,
paddingTop:20,
paddingBottom:20,
marginBottom: 16,
backgroundColor: colors.primaryPurple,
shadowColor: 'transparent',
alignItems:'flex-end',
flexDirection:'row',
},
});
Here is the ChoreList (includes the Flatlist component):
import React from 'react';
import {FlatList, StyleSheet, Text, View} from 'react-native';
import appStyles from '../config/appStyles';
import colors from '../config/colors';
import ChoreListItem from '../components/ChoreListItem';
import SectionTitleBar from '../components/SectionTitleBar';
function ChoreList({getCategory}) {
return (
<View style={appStyles.containerPadding}>
{/* <SectionTitleBar title="Today"/> */}
<View>
<FlatList
data = {getCategory}
renderItem={({item}) =>
<ChoreListItem title={item.title} />
}
keyExtractor={(item) => item.title.toString()}
/>
</View>
</View>
);
}
export default ChoreList;
const styles = StyleSheet.create({
choreListItem: {
padding:16,
marginBottom:16,
},
});
Here is the component/screen with all of the props:
import React from 'react';
import { Text, View } from 'react-native';
import choreCats from '../config/choreCats';
import choreItems from '../config/choreItems';
import colors from '../config/colors';
import ChoreList from '../components/ChoreList';
import PageHeader_TitleIllo from '../components/pageHeaders/PageHeader_TitleIllo';
import Screen from '../components/Screen';
function LaundryChoresScreen() {
const choreCategory = choreCats[4];
return (
<Screen>
<PageHeader_TitleIllo
category={choreCategory.category}
image={choreCategory.image}
bgColor={choreCategory.bgColor}
/>
<Text style={{padding:8, backgroundColor:colors.blueHueMedium, alignSelf:'flex-start', marginTop: 8, marginBottom: 8}}>?? Chores complete</Text>
<View style={{backgroundColor:colors.baseFog,}}>
<ChoreList getCategory={choreItems.filter(({category})=>category=== "laundry")}/>
</View>
</Screen>
);
}
export default LaundryChoresScreen;
There are a couple of different ways to accomplish this, but probably the best is for you to lift the state of your completed chores up to the level where it's shared with this new functionality. You would do that by tracking the complete/incomplete quality in the highest place where it needs to be shared. Currently, that's your LaundryChoresScreen component.
You would then need to pass the function that changes your array of chores down to the props of the ChoreListItem to invoke whenever they need to change their chore's status. This process is called prop drilling. A lot of people are frustrated with the tedium of prop drilling, and will opt to create a global state to manage things from anywhere in the application (using Contexts or Redux), but those are probably overkill for this case.

react how to pass props to inline css style in components

I need to draw some colored squares and use props to control the color of these squares.
The current code is not working
import React from "react";
class SketchExample extends React.Component {
constructor(props) {
super(props);
}
render() {
const { color } = this.props;
return <div style={{ color: this.props.color, width: "36px", height: "36px" }} />; } }
export default SketchExample;
And the app.js file
import React from "react";
import ReactDOM from "react-dom";
import SketchExample from "./SketchExample";
function App() {
return (
<div className="App">
<SketchExample color={{ r: "201", g: "112", b: "19", a: "1" }} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Which part went wrong? Thanks.
Passing color will make the text inside the div of that color.
What you need it backgroundColor to make "colored squares".
Also, you can't pass an object to a styles, it need to be a string.
return (
<div
style={{ backgroundColor: `rgba(${Object.values(this.props.color).join(", ")})`, width: "36px", height: "36px" }}
/>
);
From a quick glance of your code I can see you pass a color prop to SketchExample which is an object with props such as r and g and etc. Yet inside SketchExample the divs style.color is the object, not the actual color. Try something like this:
render() {
const { color } = this.props;
return <div style={{
color: `rgba(${color.r},${color.g},${color.b},${color.a})`,
width: "36px",
height: "36px"
}} />
}

React Native 'undefined is not an object' when trying to .map() over GraphQL data

Running into a strange issue here with data from a GraphQL query in a React Native app. I have a query getting me all properties (physical properties/homes) with a couple fields including 2 nested/relational fields from graph.cool. I only have 5 nodes in there so size shouldn't be an issue at all.
When I log this.props.allProperties to the console I see
'undefined'
'undefined'
and then my array of Objects.
But when I try to use this.props.allProperties.map() to map over each item and render an element I get the 'TypeError: undefined is not an object (evaluating 'this.props.allProperties.map') error.
It's weird because it seems my data is getting to me but maybe not in time for the render? I can't figure it out.
Here's my code...
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { Container, Content } from 'native-base';
import MapView, { Marker } from 'react-native-maps';
import { Transition } from 'react-navigation-fluid-transitions';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import navStyles from '../../styles/navStyles';
import FooterTabs from '../../layout/FooterTabs';
class KoviMap extends Component {
render() {
console.log(this.props.allProperties)
return (
<Container style={styles.container}>
<MapView
style={styles.map}
initialRegion={{
latitude: 35.2253342,
longitude: -80.8392494,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}}
>
{this.props.allProperties.map(property => (
<Marker
coordinate={`${property.coordinates.lat},${property.coordinates.long}`}
title={property.propertyName}
/>
))}
</MapView>
<Content></Content>
<Transition shared="footerTabs">
<FooterTabs
activeBtn='map'
navigation={this.props.navigation}
/>
</Transition>
</Container>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
map: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0
}
})
const getMarkers = gql`
query getMarkers {
allProperties {
id
propertyName
units
address {
street
city
state
zip
}
coordinates {
lat
long
}
}
}
`;
export default graphql(getMarkers, {
props: ({data}) => ({...data})
})(KoviMap);

React Native changing background color according to data that comes through http request from server

I am new at React Native.
I'm working on a code that will change the background color of a box (card) according to the data that I will get from API. I want to check first if the title is like 'Taylor' make background red , if it is 'Fearless' make it green and so on.
Here is the API that I got information from :
http://rallycoding.herokuapp.com/api/music_albums
This is the code divided into several files.
First of them index.js
// Import a library to help to create a component
import React from 'react';
import { Text, AppRegistry, View } from 'react-native';
import Header from './src/components/header.js';
import AlbumList from './src/components/AlbumList.js'
// create a component
const App = () => (
<View>
<Header headerText={'Smart Parking'}/>
<AlbumList />
</View>
);
//render it to the device
AppRegistry.registerComponent('albums2', () => App);
second is AlbumList.js
import React, { Component } from 'react';
import { View } from 'react-native';
import axios from 'axios';
import AlbumDetail from './AlbumDetail.js'
class AlbumList extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }) );
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
export default AlbumList;
3rd is AlbumDetail.js
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
4th is card which I need to change background of it
import React from 'react';
import { View } from 'react-native';
const Card = (props) => {
return (
<View style={styles.containerStyle}>
{props.children}
</View>
);
};
const styles = {
containerStyle:{
borderWidth: 1,
borderRadius: 2,
backgroundColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: {width: 0, height:2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
last one is header
// Import libraries for making components
import React from 'react';
import { Text, View } from 'react-native';
// make a components
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return(
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>
</View>
)
};
const styles ={
viewStyle:{
backgroundColor:'orange',
justifyContent: 'center',
alignItems: 'center',
height: 60,
},
textStyle: {
fontSize: 20
}
};
// make the component to the other part of the app
export default Header;
Basically you need to pass the title of the album as prop to the Card from the AlbumDetails component and then on each Card calculate the color to use and pass it in the style like this:
// AlbumDetails.js component
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card title={props.album.title}>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
// Card.js component
import React from "react";
import { View } from "react-native";
function calculateColor(title) {
let bgColor;
switch (title) {
case "Taylor":
bgColor = "red";
break;
case "Fearless":
bgColor = "green";
break;
default:
bgColor = "orange";
break;
}
return bgColor;
}
const Card = props => {
const { title } = props;
const backgroundColor = calculateColor(title);
return (
<View style={[styles.containerStyle, { backgroundColor: backgroundColor }]}>
{props.children}
</View>
);
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
backgroundColor: "#ddd",
borderBottomWidth: 0,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
Something like this should work:
Change the AlbumDetail to conditionally render the Card.
const AlbumDetail = props => {
if (props.album.title === 'Taylor') {
return (
<Card style={{ backgroundColor: 'red' }}>
<Text>{props.album.title}</Text>
</Card>
);
} else {
return (
<Card style={{ backgroundColor: 'green' }}>
<Text>{props.album.title}</Text>
</Card>
);
}
};
Override the default style of the card using the passed style prop.
const Card = props => {
return (
<View style={[styles.containerStyle, props.style]}>{props.children}</View>
);
};
accepted answer is great just its a bad habit to do things in render.
i also not sure since every title has a color why wouldnt the server send this in the object props in first place ? :)
class AlbumList extends Component {
state = { albums: [] };
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response=> Array.isArray(response.data) ? response.data : []) // alittle validation would not hurt :) !
.then(data => this.setState({ albums: data }) );
}
selectHeaderColorForAlbum( album ){
let headerColor = 'red';
// switch(album.title){ ..you logic }
return headerColor;
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} color={this.selectHeaderColorForAlbum(album)} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
const Card = (props) => {
return (
<View style={[styles.containerStyle,{color:props.headerColor}]}>
{props.children}
</View>
);
};
this is easier your logic will render only once.
also notice that react >16.0 depricated componentWillMount, so use DidMount

React-Native FlatList not re-rendering with custom renderItem

I have a FlatList that works as expected when using a plain old <Text> tag, but when using a custom Component inside renderItem, the FlatList will not re-render when changing this.state.dayOfYear. On app load, when I set this.state.dayOfYear, it loads properly. But when I change state again, it will not change the FlatList.
FlatList Code
<FlatList
style={{flex: 1}}
extraData={this.state}
data={reading_data[this.state.dayOfYear]}
renderItem={({item}) => <DayRow content={item}/>} //does not work
// renderItem={({item}) => <Text>{item.ref}</Text>} //Works perfectly
keyExtractor={(item, index) => index}
/>
Custom renderItem (DayView.js)
import {StyleSheet, Text, View} from 'react-native'
import React, {Component} from 'react';
export default class DayRow extends React.Component {
constructor(props) {
super(props)
console.log(props)
this.state = {
content: props.content,
}
}
render() {
return (
<View style={styles.row}>
<Text style={styles.text}>{this.state.content.ref}</Text>
<View style={{height: 2, backgroundColor:'#abb0ab'}}/>
</View>
);
}
}
const styles = StyleSheet.create({
row: {
backgroundColor: '#fff'
},
text: {
fontSize: 16,
padding: 10,
fontWeight: 'bold',
color: '#000',
},
});
module.exports = DayRow;
I'm pretty sure that your DayRow items are being constructed before props.content is being set, you need to grab the props when the component is mounting. Try adding this:
componentWillMount() {
const { content } = this.props;
this.setState({content: content});
}
EDIT
I missed the part about "re-rendering"...
Basically you need a block of code that updates your components state when its props change, react components have another function similar to componentWillMount called componentWillReceiveProps, try:
componentWillReceiveProps(nextProps) {
const { content } = nextProps;
this.setState({content: content});
}
I had the same issue but resolved using extraData = {this.state}
Complete code is here
<FlatList
style={styles.listView}
data={this.state.readingArray}
extraData={this.state}
renderItem=
{({item})=>

Categories

Resources