I created a donut chart components (DonutChart) that takes an array of values representing percentages. The values are derived from data gathered from a firestore db. When running this:
Warning: React has detected a change in the order of Hooks called by DonutChart. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
Previous render Next render
-
Previous render Next render
------------------------------------------------------
1. useMemo useMemo
2. useEffect useMemo
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
along with:
TypeError: undefined is not an object (evaluating 'prevDeps.length')
I am assuming the SKIA library uses 'useMemo' for it's animation computations.
Here is some of the code and then I'll explain what I have tried:
DonutChart
const DonutChart = ({ dataValues }: DonutChartProps) => {
const sortedValues = dataValues.sort((n1, n2) => n1 - n2);
const total = sortedValues.reduce((sum, n) => sum + n, 0);
const createRollingTotal = () => {
var rollingTotal = 0;
return sortedValues.map((val, index) => {
rollingTotal = index !== 0 ? rollingTotal + sortedValues[index - 1] : rollingTotal;
return index !== 0 ? total - rollingTotal : total;
});
};
const progress = createRollingTotal();
const progressValues = progress.map(useValue);
React.useEffect(() => {
progress.map((val, index) => animateChart(val, progressValues[index]));
return () => {};
}, []);
const animateChart = (value: number, state: SkiaMutableValue) => {
state.current = 0;
runTiming(state, value, {
duration: 1250,
easing: Easing.inOut(Easing.cubic),
});
};
return (
<Canvas style={{ height: OUTER_RADIUS + 200, width: OUTER_RADIUS + 200 }} mode="continuous">
<DonutChartSkia progress={progressValues} />
</Canvas>
);
};
export default DonutChart;
DonutChartSkia
const fromCircle = (cx: number, cy: number, r: number) =>
rrect(rect(cx - r, cy - r, 2 * r, 2 * r), r, r);
interface ProgressBarProps {
progress: SkiaValue<number>[];
}
interface DonutChartProps {
dataValues: number[];
}
const BLUR = 1;
const SHADOW_WIDTH = 1;
const INNER_RADIUS = 35;
const OUTER_RADIUS = 55;
const PATH_RADIUS = (INNER_RADIUS + OUTER_RADIUS) / 2;
const DonutChartSkia = ({ progress }: ProgressBarProps) => {
const font = useFont(require('../../assets/fonts/SF-Pro-Text-Semibold.otf'), 18);
const text = useComputedValue(() => `${Math.round(progress[0]?.current * 100)}%`, [progress[0]]);
if (font === null) {
return null;
}
const textWidth = font.getTextWidth('00°C');
const path = Skia.Path.Make();
path.addCircle(OUTER_RADIUS, OUTER_RADIUS, PATH_RADIUS);
const c = vec(PATH_RADIUS + 12, PATH_RADIUS + 12);
const renderPath = (val: SkiaValue<number>, index: number) => {
const color = CHART_COLORS[index];
return (
<Group key={index}>
<RadialGradient
colors={[color.secondary, color.primary]}
c={vec(OUTER_RADIUS, OUTER_RADIUS)}
r={70}
/>
<Path
path={path}
style="stroke"
strokeWidth={OUTER_RADIUS - INNER_RADIUS}
end={val}
strokeCap="round"
antiAlias={true}
/>
</Group>
);
};
return (
<Group transform={translate({ x: 70, y: 70 })}>
<Group>
<LinearGradient
start={vec(22, 22)}
end={vec(200, 200)}
colors={[primaryColor, secondaryColor]}
/>
<Box box={fromCircle(OUTER_RADIUS, OUTER_RADIUS, OUTER_RADIUS)}>
<BoxShadow dx={SHADOW_WIDTH} dy={SHADOW_WIDTH} blur={BLUR} color={shadowColor} />
<BoxShadow dx={-SHADOW_WIDTH} dy={-SHADOW_WIDTH} blur={BLUR} color={glareColor} />
</Box>
</Group>
<Group>
<RadialGradient colors={[shadowColor, primaryColor]} c={vec(128, 128)} r={128} />
<Box box={fromCircle(OUTER_RADIUS, OUTER_RADIUS, INNER_RADIUS)}>
<BoxShadow dx={SHADOW_WIDTH} dy={SHADOW_WIDTH} blur={BLUR} color={shadowColor} />
<BoxShadow dx={-SHADOW_WIDTH} dy={-SHADOW_WIDTH} blur={BLUR} color={glareColor} />
</Box>
</Group>
<Text
x={c.x - textWidth / 2}
y={c.y + font.getSize() / Math.PI}
font={font}
text={text}
color={textColor}
/>
<Group>{progress.map((val, index) => renderPath(val, index))}</Group>
</Group>
);
};
I have tried removing the 'useEffect' and instead use 'useMemo'.
Changed how I handle the API call, but still need to use 'useEffect' for that.
Added (and removed) umount functions to the 'useEffect' hook.
I expect the DonutChart to rerender with each data change since it uses values from a redux store that is updated from firestore. I keep running into this error, and I can only assume it is caused by the SKIA library using 'useMemo'. Still, I am not sure and only having less than a year of self-taught React-native dev experience I am not sure if it is just me or some issue I should log on GitHub. I know this is all over the place but I am hoping someone might notice something I might be doing wrong and I might also learn from that.
Related
I am trying to pause and play video using scrollView but i am unable to do that I wrote a code but it have some condition issue, can anybody please tell me what is wrong in my code, I want to play the video when it is visible and pause all other videos in the list than when we scroll the video show on screen it start playing automatically and the previous video pause or stops automatically.
handleVideoLayout = (e:any) => {
const {height} = Dimensions.get("window");
console.log(e.nativeEvent.layout.y,e.nativeEvent.layout.height);
this.position.start = -(e.nativeEvent.layout.y - height + 100);
this.position.end = e.nativeEvent.layout.y + e.nativeEvent.layout.height + 100;
}
handleScroll = (e:any) => {
const scrollPosition = e.nativeEvent.contentOffset.y;
const paused = this.state.paused;
const {start,end} = this.position;
console.log("========================================");
console.log(start,end,scrollPosition);
console.log("========================================");
if(scrollPosition > start && scrollPosition < end && paused){
this.setState({paused:false});
} else if((scrollPosition > end || scrollPosition < start) && !paused){
this.setState({paused:true});
}
My render function:
<ScrollView
onScroll={(event) => {
this.handleScroll(event)
}}
onLayout={this.handleVideoLayout}
>
{data.map((item,index)=> {
return (
<VideoComponent/>
)
})}
</ScrollView>
I don’t have idea about doing this using scroll view but you have an option in flatlist. In that, you will get the visible items on the screen. and then based on your requirements you can apply conditions.
see my below boilerplate.
const onViewableItemsChanged = ({viewableItems, changed}) => {
//here 'viewableItems' is an item that is currently visible on screen.
};
const viewabilityConfigCallbackPairs = useRef([{onViewableItemsChanged}]);
<FlatList
data={data}
renderItem={renderPost}
showsVerticalScrollIndicator={false}
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
/>
find the current index of your list and also the total index's of your list and then put like this
`
const [paused, setPaused] = useState(false);
<Video
videoRef={videoRef}
source={{uri: userData?.Video?.path}}
poster={VideoToImageUrlCoverter(userData?.Video?.path)}
posterResizeMode={'cover'}
pauseOnPress={paused}
shouldPlay={currentIndex === index}
repeat={true}
autoplay
resizeMode="cover"
paused={
currentIndex == index
? paused
: !paused && currentIndex == index
? false
: true
}
/>
`
I have a D3 topoJSON implementation in React Native that displays US counties' paths and state paths to form a map. I want to essentially "zoom" in or scale up to show only one state with the counties. The bordering paths of the states should be around the focused state. How can I do this?
const COUNTIES = feature(
counties,
counties.objects.counties
).features;
const [countyList, setCountyList] = useState([]);
const mapExtent = useMemo(() => {
return dimensions.width > dimensions.height / 2
? dimensions.height / 2 - 90
: dimensions.width - 90;
}, [dimensions]);
const statePaths = useMemo(() => {
const projection = d3.geoMercator().fitSize([mapExtent, mapExtent], {
type: "FeatureCollection",
features: COUNTIES,
});
const geoPath = d3.geoPath().projection(projection);
const svgPaths = COUNTIES.map(geoPath);
return svgPaths;
}, [dimensions]);
useEffect(() => {
setCountyList(
statePaths.map((path, i) => {
const curCounty = COUNTIES[i].properties.NAME;
const isCountyNameInData = data.some(
(county) => county.name === curCounty + " County"
);
const curCountyData = isCountyNameInData
? data.find((county) => county.name === curCounty + " County")["data"]
: null;
return (
<Path
key={COUNTIES[i].properties.NAME}
d={path}
stroke={"#535252"}
strokeOpacity={0.3}
strokeWidth={0.6}
fill={colorScale(curCountyData)}
/>
);
})
);
}, [data]);
I have an application that queries the stock exchange in real time, but when I open the application the first time it does not update any displayed value, as if it were hardcoded, but when I save the file (ctrl + S) it starts to update normally , Can anyone help me with this?
description code below:
this function is listening the data on firebase;
ticketRT is my state that is not updating at first time
Object.keys(subscriptionStocks).forEach((stock) => {
if(!subscriptionStocks[stock].listener) {
firebase.database().ref(`${endPointStocks}/${stock}`).on('value', async data => {
let stock = data.val();
if (stock) {
stockArray[stock.ticket] = stock;
setTicketRT(stockArray);
await updateListStocks(stock);
} else {
console.log(`[listenerStock]: Não foi possível cadastrar o ativo "${stock}"`);
}
});
subscriptionStocks[stock].listener = true;
this function updates the stock list
let tmpStock = {};
console.log('LENGTH 11111', stockArray.length);
if (stockArray.length === 0) {
console.log('LENGTH 22222 ≥≥≥ 2', stockArray.length);
tmpStock[stock.ticket] = stock;
stockArray.push(tmpStock);
setTicketRT(stockArray);
updateWalletBalance();
} else {
const foundStockInArray = stockArray.some(
el => {
let inStock = el[Object.keys(el)[0]];
return inStock.ticket == stock.ticket;
}
);
if (foundStockInArray) {
updateStock(stock);
} else {
tmpStock[stock.ticket] = stock;
stockArray.push(tmpStock);
setTicketRT(stockArray);
updateWalletBalance();
}
}
this is my FlatList that is not updating
<FlatList
style={styles.actionList}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
data={ticketRT}
extraData={(item, index) => {
if(ticketRT !== undefined) {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return stock.ticket;
}
}}
keyExtractor={(item, index ) => {
if(ticketRT !== undefined) {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return stock.ticket;
}
}}
renderItem={({ index }) => {
let stocks = ticketRT[index];
let stock = stocks[Object.keys(stocks)[0]];
return <Ativos data={stock} />
}}
contentInset={{ bottom: 10, top: -5 }}
/>
ALL CODE IS FOUNDED HERE: https://gist.github.com/paulorod07/4e74976345a6a68b0e32755cf5ae1c46
onPressFavourites(item) {
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
if(item.favourites === 0) {
realm.write(() => {
item.favourites = 1;
item.favouritesColour = '#91b54d';
});
} else {
realm.write(() => {
item.favourites = 0;
item.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
I am trying to update an object in the Realm when a button is clicked however I get the error
"Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction."
This code was working a couple days ago but is now throwing the above error.
I am still learning React Native and Realm but from my understanding and following examples and the Realm docs, I am using the correct code so it should work.
EDIT
Seems the realm and write transactions were fine.
We were able to find a roundabout way to fix it however now the updates don't display until the app is refreshed.
It seems the error Possible Unhandled Promise Rejection (id: 0):
Error: Cannot modify managed objects outside of a write transaction. was produced when using the argument item to define the chosen Realm Object. However, if we create a variable related to the item, it doesn't produce that error. Any ideas why this would happen?
onPressFavourites(item) {
//console.log(realm);
let realm = Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
for(var i=0; i<trees.length;i++){
if(trees[i].commonName == item.commonName){
var chosen = trees[i];
break;
}
}
console.log(chosen);
if(item.favourites === 0) {
realm.write(() => {
//item.favourites = 1;
//item.favouritesColour = '#91b54d';
chosen.favourites = 1;
chosen.favouritesColour = '#91b54d';
});
}
else {
realm.write(() => {
chosen.favourites = 0;
chosen.favouritesColour = 'transparent';
});
}
});
alert(item.favourites)// Update a property value
}
EDIT
render() {
var treeList = this.state.trees;
console.log(treeList);
const {navigate} = this.props.navigation;
//var tree = treeList[0];
return(
<Container>
<Header searchBar style={styles.searchBar}>
<Item style={styles.searchBarInner}>
<Icon name="ios-search" />
<Input placeholder="Search" />
</Item>
</Header>
<List dataArray={treeList}
renderRow={(item) =>
<ListItem style={styles.ListItem} button={true}>
<ListButton item={item}
onSelect={(item) => this.saveTreeInfo(item)}
onSelectFavourites={(item) => this.onPressFavourites(item)
}
/>
</ListItem>
}
>
</List>
</Container>
);
}
Above is where item is being passed to the onPressFavourites function. item is being generated from an array of Realm Objects treelist and displayed in a list.
The treelist array comes from the variable this.state.trees which is displayed below.
filterContent(){
Realm.open({
path: RNFS.DocumentDirectoryPath +'/trees.realm',
schema: [sightingSchema, treeSchema],
schemaVersion: 7,
}).then(realm => {
let trees = realm.objects('TreeInfo');
let length = trees.length;
let treeRealm = trees.sorted('commonName');
console.log(this.state.leafEdges);
if (this.state.leafEdges === 'smooth') {
var smoothLeaf = treeRealm.filtered('leafEdges CONTAINS "Smooth"');
this.setState({trees:smoothLeaf});
}
if (this.state.leafEdges === 'toothed') {
var toothedLeaf = treeRealm.filtered('leafEdges CONTAINS "Toothed"');
this.setState({trees:toothedLeaf});
}
if (this.state.leafEdges === 'notsure') {
this.setState({trees:treeRealm});
}
else if (this.state.leafEdges === 'null') {
this.setState({trees:treeRealm});
}
});
}
Answer in English:
The trees has something about realm, so you cannot change it. Just copy a new Array like this:
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}
You should improve the syntax
let order = {...bla, bla,bla}
realm.write(() => {
realm.create("order", order);
});
这个trees 里面关联了realm里的一些东西,所以不能改变,要重新复制一个数组,像这样
let trees = realm.objects('TreeInfo');
var arr=[];
for (let i = 0;trees && i < trees .length; i++) {
var item=trees[i];
var item2={
id: item.id,
....
}
arr.push(item2);
}
I have to show options menu using conditional check on data coming from the server. Here is my code:
function renderMoreOptionsOnNavbar(groupInfo){
console.log('hey check this out', groupInfo)
if (groupInfo.user_joined) {
var formData = new FormData();
formData.append('user_id', global.currentUser.USER_ID);
formData.append('slug', groupInfo.id);
var adminsIdsArray = []
ajaxPost({
url: 'community',
basePath: 'lumen',
params: formData
}).then(res => {
if(!res.err_code) {
console.log('This is what you need', res)
const communityAdminsArray = res.community_admins
adminsIdsArray = communityAdminsArray.map(function(user) {
return user.user_id
})
console.log('AdminsArray', adminsIdsArray.includes(global.currentUser.USER_ID))
}
})
const menuOptions = adminsIdsArray.includes(global.currentUser.USER_ID) ? ['Switch off notifications', 'Edit group information', 'Quit group'] : ['Switch off notifications', 'Quit group']
const menuHeight = menuOptions.length*40
console.log('options', menuOptions)
return(
<ModalDropdown options={menuOptions} style={styles.dropDownView} dropdownStyle = {styles.dropdownStyle, {height: menuHeight, width: 175}} adjustFrame = {adjustDropDownFrame}
renderSeparator = {renderDropDownSeparator} dropdownTextStyle = {styles.dropDownOptionText}
onSelect = {onSelectingDropDownOption.bind(this, groupInfo)}
showsVerticalScrollIndicator = {false}
>
<View style={styles.dropDownButtonView}>
<Image
source={require('../../Assets/vertical_dots_menu.png')}
resizeMode = {Image.resizeMode.center}
style = {styles.dropDownBaseButton}
/>
</View>
</ModalDropdown>
)
}
else {
return null
}
}
The options in menu loading before data. Can some one tell me, how to pause rendering until data loads.
You should follow this pattern to achieve what you want:
state = {requiredData: null}
fetchDataFunction().then((resp)=>{
...
this.setState({requiredData: resp}) // I assume the response of fetch is what you need
})
render() {
if (this.state.requiredData === null) {
return null // you can use a spinner instead null
} else {
return (
... // your main view
)
}
}