When building a Rork app that needs to compare monthly income vs. expenses side by side, react-native-chart-kit's BarChart seems like the obvious choice. But once you actually try it, you run into a frustrating wall:
// This is what you want — but it won't work
const data = {
labels: ["Jan", "Feb", "Mar"],
datasets: [
{ data: [30000, 45000, 38000], color: () => "#4CAF50" }, // income
{ data: [22000, 31000, 29000], color: () => "#F44336" }, // expenses
],
};Pass this to BarChart and only the first dataset renders. The second one is silently ignored. That's by design — react-native-chart-kit's BarChart internally only reads data.datasets[0], unlike LineChart which supports multiple datasets stacked on the same chart.
This is one of the library's most-searched limitations, and the GitHub Issues for it have stayed open for years. Here are three practical approaches to get grouped bar charts working in your Rork app.
Why chart-kit's BarChart Only Renders One Dataset
Looking at the source code, BarChart.tsx only iterates over data.datasets[0].data. The LineChart component handles multiple datasets differently, which makes them behave inconsistently despite being in the same library.
As of 2026, the core BarChart implementation still hasn't addressed this. Your options are to work around it, or switch libraries.
Approach 1: Color-Coded Simulation (Fastest Fix)
The simplest workaround is to interleave all data points into a single array and use the colors property to distinguish them visually. Not true grouped bars, but it works well for 3–4 months of comparisons.
import { BarChart } from "react-native-chart-kit";
import { Dimensions } from "react-native";
const screenWidth = Dimensions.get("window").width;
// Interleave income and expense values
const data = {
labels: ["Jan↑", "Jan↓", "Feb↑", "Feb↓", "Mar↑", "Mar↓"],
datasets: [
{
data: [30000, 22000, 45000, 31000, 38000, 29000],
// Even indices = income (green), odd = expenses (red)
colors: [
() => "#4CAF50",
() => "#F44336",
() => "#4CAF50",
() => "#F44336",
() => "#4CAF50",
() => "#F44336",
],
},
],
};
export default function IncomeExpenseChart() {
return (
<BarChart
data={data}
width={screenWidth - 32}
height={220}
yAxisLabel="$"
yAxisSuffix=""
withCustomBarColorFromData={true} // Required for colors array to work
flatColor={true} // Required alongside the above
chartConfig={{
backgroundColor: "#ffffff",
backgroundGradientFrom: "#ffffff",
backgroundGradientTo: "#ffffff",
decimalPlaces: 0,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
}}
style={{ borderRadius: 8 }}
/>
);
}Key gotcha: Both withCustomBarColorFromData={true} and flatColor={true} must be set together. Either one alone won't apply the colors array — this is a common source of confusion.
Limitation: Labels get crowded quickly. Works fine for 2–3 month comparisons, but breaks down at 12 months.
Approach 2: Overlapping BarCharts with Absolute Positioning
Stack two BarChart instances using position: "absolute" to simulate grouped bars. More visual flexibility, but fiddly to get right.
import { View } from "react-native";
import { BarChart } from "react-native-chart-kit";
import { Dimensions } from "react-native";
const screenWidth = Dimensions.get("window").width;
const chartWidth = screenWidth - 32;
const baseConfig = {
backgroundColor: "transparent",
backgroundGradientFrom: "transparent",
backgroundGradientTo: "transparent",
decimalPlaces: 0,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
};
const incomeData = {
labels: ["Jan", "Feb", "Mar"],
datasets: [{ data: [30000, 45000, 38000] }],
};
const expenseData = {
labels: [" ", " ", " "], // Blank labels to avoid double axis
datasets: [{ data: [22000, 31000, 29000] }],
};
export default function OverlayBarChart() {
return (
<View style={{ height: 220, position: "relative" }}>
{/* Background: income with visible axes */}
<BarChart
data={incomeData}
width={chartWidth}
height={220}
yAxisLabel="$"
yAxisSuffix=""
chartConfig={{
...baseConfig,
color: () => "#4CAF50",
backgroundColor: "#ffffff",
backgroundGradientFrom: "#ffffff",
backgroundGradientTo: "#ffffff",
}}
style={{ position: "absolute", left: 0, top: 0 }}
/>
{/* Foreground: expenses with transparent background */}
<BarChart
data={expenseData}
width={chartWidth}
height={220}
yAxisLabel=""
yAxisSuffix=""
chartConfig={{
...baseConfig,
color: () => "#F44336",
}}
style={{ position: "absolute", left: 0, top: 0 }}
/>
</View>
);
}Honestly, aligning the Y-axes between the two charts takes more trial-and-error than it looks. If you find yourself spending more than an hour on this, the next approach is worth considering.
Approach 3: Switch to Victory Native (Recommended for Grouped Bars)
The more you work around react-native-chart-kit's BarChart limitations, the more you realize that Victory Native solves this natively. VictoryGroup handles grouped bars with a clean API, and the result looks polished without any hacks.
import {
VictoryBar,
VictoryChart,
VictoryGroup,
VictoryAxis,
VictoryTheme,
} from "victory-native";
const incomeData = [
{ x: "Jan", y: 30000 },
{ x: "Feb", y: 45000 },
{ x: "Mar", y: 38000 },
];
const expenseData = [
{ x: "Jan", y: 22000 },
{ x: "Feb", y: 31000 },
{ x: "Mar", y: 29000 },
];
export default function GroupedBarChartVictory() {
return (
<VictoryChart
theme={VictoryTheme.material}
domainPadding={{ x: 30 }}
height={300}
>
<VictoryAxis
tickValues={["Jan", "Feb", "Mar"]}
style={{ tickLabels: { fontSize: 12, fill: "#333" } }}
/>
<VictoryAxis
dependentAxis
tickFormat={(t) => `$${(t / 1000).toFixed(0)}k`}
style={{ tickLabels: { fontSize: 11, fill: "#333" } }}
/>
<VictoryGroup
offset={15}
colorScale={["#4CAF50", "#F44336"]}
>
<VictoryBar data={incomeData} />
<VictoryBar data={expenseData} />
</VictoryGroup>
</VictoryChart>
);
}Install Victory Native with:
npx expo install victory-native react-native-svgFor a full dashboard implementation with Victory Native including line charts, pie charts, and touch interactions, see Building Interactive Charts in Rork Apps with Victory Native.
Which Approach to Choose
If you already have react-native-chart-kit throughout your project and just need grouped bars in one or two places, Approach 1 (color-coded interleaving) is the fastest path to a working implementation.
If you're planning to add more chart types over time — or you're building a data-heavy app like a budget tracker, fitness dashboard, or analytics screen — this is a good moment to migrate to Victory Native. Running two chart libraries in parallel adds bundle size and maintenance overhead that adds up over time.
The choice depends on where your project is headed. For a quick prototype or MVP, Approach 1 is fine. For anything production-grade with ongoing chart requirements, Victory Native gives you a more stable foundation.
Start with Approach 1 to get something on screen quickly, then evaluate whether Victory Native makes sense for your full implementation. That tends to be a better use of development time than polishing a workaround that may need replacing anyway.
Common chart-kit BarChart Pitfalls to Avoid
While working through these approaches, a few other chart-kit gotchas come up regularly in Rork projects.
The segments prop affects Y-axis grid lines, not data grouping
Some developers find the segments prop and assume it controls how data is grouped horizontally. It doesn't — segments only controls how many horizontal grid lines appear on the Y-axis. Setting segments={2} when you have two datasets won't group them.
fromZero can distort visual comparisons
The fromZero={true} prop forces the Y-axis to start at zero, which is usually the right call for bar charts (starting mid-range makes differences look exaggerated). Without it, chart-kit may start the Y-axis at a value that makes two similar bars look dramatically different.
<BarChart
data={data}
fromZero={true} // Always include this for bar charts
// ...other props
/>showValuesOnTopOfBars and long numbers
If you enable showValuesOnTopOfBars={true} with currency values in the tens of thousands, the labels will overflow the chart bounds. Either abbreviate the values in your data (e.g., 30 instead of 30000 and adjust the Y-axis suffix), or use yAxisLabel and yAxisSuffix to format the axis instead.
// Instead of raw values, normalize to thousands
const data = {
labels: ["Jan", "Feb", "Mar"],
datasets: [{
data: [30, 45, 38], // values in thousands
}],
};
<BarChart
data={data}
yAxisLabel="$"
yAxisSuffix="k"
showValuesOnTopOfBars={true}
// Now labels show "$30k" without overflow
/>Adding a Legend Manually
Neither the color-coded simulation nor the overlay approach gives you an automatic legend. For any chart that needs one, building a simple custom legend is straightforward:
import { View, Text, StyleSheet } from "react-native";
function ChartLegend() {
return (
<View style={styles.legendContainer}>
<View style={styles.legendItem}>
<View style={[styles.dot, { backgroundColor: "#4CAF50" }]} />
<Text style={styles.legendLabel}>Income</Text>
</View>
<View style={styles.legendItem}>
<View style={[styles.dot, { backgroundColor: "#F44336" }]} />
<Text style={styles.legendLabel}>Expenses</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
legendContainer: {
flexDirection: "row",
justifyContent: "center",
gap: 20,
marginTop: 8,
},
legendItem: {
flexDirection: "row",
alignItems: "center",
gap: 6,
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
},
legendLabel: {
fontSize: 13,
color: "#555",
},
});Place this directly below your BarChart component. It's a simple addition but makes the chart much more readable for users who haven't seen the data before.
When to Stick with chart-kit vs. When to Move On
react-native-chart-kit is a solid choice when you need simple, quickly-styled charts and aren't planning to go deep on data visualization. Its API is approachable, the default styles look clean, and for basic line charts or single-dataset bar charts, it covers most use cases without much configuration.
The grouped bar chart limitation is a real friction point, but it's also a useful signal: if your app's data requirements are getting complex enough that you need multiple datasets side by side, you're likely also reaching the point where you'd want interaction handling, custom tooltips, or animated transitions — features that Victory Native handles more gracefully.
There's no shame in starting with react-native-chart-kit for an MVP and migrating later. Just be aware that the migration path exists, and don't spend more than a day trying to force chart-kit to do something it wasn't built for.
If you're already on Victory Native, the grouped bar example in Approach 3 is production-ready as-is. Adjust the offset value to fine-tune bar spacing, and use VictoryTooltip with labelComponent if you want tap-to-reveal values on each bar.