How Do I Integrate JAMB Past Questions Into a React Native App?
If you're building a JAMB CBT app with React Native, one of the easiest ways to add structured JAMB/UTME past questions is by connecting your app to SdashAPI.
SdashAPI provides a REST API that returns JAMB questions, options, answers, solutions and examination information as JSON.
Your React Native app can fetch that data, store it in state and render the questions inside your own CBT interface.
The basic flow is:
React Native App
↓
Your Backend or API Request
↓
SdashAPI
↓
JAMB Questions in JSON
↓
Display Questions in Your CBT Screen
Here's how to set it up.
Step 1: Create a SdashAPI Account
Before your React Native app can fetch protected JAMB questions, you need a SdashAPI developer account.
After creating your account, go to your dashboard and get your personal Access Token.
Your token is used to authenticate requests.
It will be sent in the HTTP header like this:
AccessToken: YOUR_ACCESS_TOKEN
SdashAPI currently recommends using the HTTP header for authentication rather than exposing the token as a URL parameter in production applications.
Step 2: Test the JAMB API Endpoint
The core V1 question endpoint is:
https://sdashapi.com/api/v1/q
For example, to request JAMB Biology questions:
https://sdashapi.com/api/v1/q?subject=biology&type=utme
To request Biology questions from a particular year:
https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022
And to request multiple questions:
https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022&limit=20
SdashAPI supports filtering questions by subject, examination type and year, and V1 can return multiple questions in one request.
Step 3: Fetch JAMB Questions in React Native
React Native already supports fetch(), so you don't necessarily need an extra HTTP library.
Here is a simple example:
const fetchJambQuestions = async () => {
try {
const response = await fetch(
"https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022&limit=20",
{
headers: {
AccessToken: "YOUR_ACCESS_TOKEN",
},
}
);
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Failed to fetch JAMB questions:", error);
}
};
When the request succeeds, SdashAPI returns JSON that your React Native app can process.
Step 4: Store the Questions in State
Inside a React Native component, you can store the returned questions using useState.
import React, { useEffect, useState } from "react";
import {
View,
Text,
TouchableOpacity,
ActivityIndicator,
} from "react-native";
export default function JambPracticeScreen() {
const [questions, setQuestions] = useState([]);
const [loading, setLoading] = useState(true);
const fetchQuestions = async () => {
try {
const response = await fetch(
"https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022&limit=20",
{
headers: {
AccessToken: "YOUR_ACCESS_TOKEN",
},
}
);
const result = await response.json();
if (result.status === 200) {
setQuestions(
Array.isArray(result.data)
? result.data
: [result.data]
);
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchQuestions();
}, []);
if (loading) {
return <ActivityIndicator size="large" />;
}
return (
<View>
<Text>Questions loaded: {questions.length}</Text>
</View>
);
}
The Array.isArray() check is useful because your code can safely handle either a single question object or an array of questions.
What Does the SdashAPI Response Look Like?
A V1 question response can include fields such as:
{
"status": 200,
"data": {
"id": 4821,
"question": "Which of the following is the chemical formula for table salt?",
"section": null,
"option": {
"a": "NaCl",
"b": "KCl",
"c": "CaCO3",
"d": "NaOH"
},
"answer": "a",
"solution": "NaCl is sodium chloride.",
"image": null,
"examtype": "UTME",
"examyear": "2022"
}
}
The core V1 response provides the information needed for a traditional CBT experience, including the question, options, correct answer, solution, examination type and year.
Step 5: Display One JAMB Question
Now you can display the first question.
const question = questions[0];
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 20 }}>
{question.question}
</Text>
</View>
);
For a full CBT app, you'll normally track the current question index.
const [currentIndex, setCurrentIndex] = useState(0);
const currentQuestion = questions[currentIndex];
Then your screen can display:
<Text>
Question {currentIndex + 1} of {questions.length}
</Text>
<Text>
{currentQuestion.question}
</Text>
Step 6: Display the Answer Options
SdashAPI V1 returns options as an object.
For example:
{
"a": "NaCl",
"b": "KCl",
"c": "CaCO3",
"d": "NaOH"
}
You can convert this object into an array using:
const options = Object.entries(currentQuestion.option);
That produces values like:
[
["a", "NaCl"],
["b", "KCl"],
["c", "CaCO3"],
["d", "NaOH"]
]
Now render them:
{Object.entries(currentQuestion.option).map(
([key, value]) => (
<TouchableOpacity
key={key}
onPress={() => selectAnswer(key)}
style={{
padding: 15,
marginBottom: 10,
borderWidth: 1,
borderRadius: 8,
}}
>
<Text>
{key.toUpperCase()}. {value}
</Text>
</TouchableOpacity>
)
)}
Now you have a basic working JAMB CBT question screen.
Step 7: Save the Student's Selected Answer
You need to remember what the student selects for each question.
One simple approach is to use an object:
const [answers, setAnswers] = useState({});
Then:
const selectAnswer = (option) => {
setAnswers((previousAnswers) => ({
...previousAnswers,
[currentQuestion.id]: option,
}));
};
Your answers object could eventually look like:
{
4821: "a",
4822: "c",
4823: "b"
}
Each question ID maps to the option selected by the student.
Step 8: Add Previous and Next Buttons
A CBT app needs question navigation.
const nextQuestion = () => {
if (currentIndex < questions.length - 1) {
setCurrentIndex(currentIndex + 1);
}
};
const previousQuestion = () => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};
Then render:
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 20,
}}
>
<TouchableOpacity onPress={previousQuestion}>
<Text>Previous</Text>
</TouchableOpacity>
<TouchableOpacity onPress={nextQuestion}>
<Text>Next</Text>
</TouchableOpacity>
</View>
Your CBT navigation is now functional.
Step 9: Mark the JAMB Test
When the student submits, compare their selected answers with the correct answers from the API.
const calculateScore = () => {
let score = 0;
questions.forEach((question) => {
const selectedAnswer = answers[question.id];
if (selectedAnswer === question.answer) {
score++;
}
});
return score;
};
For example, if the student gets 16 out of 20:
Score: 16/20
You could calculate a percentage:
const percentage =
(calculateScore() / questions.length) * 100;
and display:
80%
Step 10: Show Solutions After Submission
Because SdashAPI can provide the correct answer and solution, you can build a correction screen.
For example:
<Text>
Correct Answer:
{currentQuestion.answer.toUpperCase()}
</Text>
<Text>
{currentQuestion.solution}
</Text>
This turns your CBT app into a revision tool instead of simply giving students their final score.
Build a Subject Selection Screen
You probably don't want Biology permanently hardcoded into the API URL.
Instead, create a subject selection screen.
For example:
const subjects = [
"english",
"mathematics",
"biology",
"chemistry",
"physics",
"economics",
"government",
];
Then when a student selects Biology:
setSelectedSubject("biology");
Your API request becomes:
const url =
`https://sdashapi.com/api/v1/q` +
`?subject=${selectedSubject}` +
`&type=utme` +
`&limit=40`;
Now one screen can support several JAMB subjects.
Add Examination-Year Selection
You can do the same thing with the year.
const [selectedYear, setSelectedYear] =
useState("2022");
Then:
const url =
`https://sdashapi.com/api/v1/q` +
`?subject=${selectedSubject}` +
`&type=utme` +
`&year=${selectedYear}` +
`&limit=40`;
The resulting request could be:
/api/v1/q?subject=physics&type=utme&year=2022&limit=40
SdashAPI's current documentation supports these kinds of subject, exam-type and year filters.
Add a CBT Timer
The examination timer is something your React Native application should manage locally.
For example:
const [timeLeft, setTimeLeft] = useState(1800);
That gives you 1,800 seconds, or 30 minutes.
You can decrement it every second using setInterval.
useEffect(() => {
if (timeLeft <= 0) {
submitExam();
return;
}
const timer = setInterval(() => {
setTimeLeft((time) => time - 1);
}, 1000);
return () => clearInterval(timer);
}, [timeLeft]);
SdashAPI supplies the question content while your application controls the exam experience.
What Should SdashAPI Handle?
SdashAPI can handle the educational content layer.
For example:
Questions
Options
Answers
Solutions
Subjects
Years
Question metadata
Your React Native application handles things like:
User accounts
CBT UI
Timers
Navigation
Selected answers
Scores
Subscriptions
Bookmarks
Progress tracking
Analytics
Offline state
That separation makes your application easier to maintain.
Don't Expose Your Access Token in Production
This part is important.
You may see examples like:
headers: {
AccessToken: "YOUR_ACCESS_TOKEN"
}
That is useful for development and testing.
However, hardcoding your private production SdashAPI Access Token directly into a distributed React Native application can expose it to users who inspect the app.
SdashAPI itself recommends keeping private credentials on your backend where possible.
A better production architecture is:
React Native App
↓
Your Backend
↓
SdashAPI
For example:
React Native App
GET https://yourdomain.com/api/questions
↓
Your Node.js/Laravel/PHP Backend
↓
GET https://sdashapi.com/api/v1/q
AccessToken: PRIVATE_TOKEN
↓
SdashAPI
Your Access Token remains on your server.
Example Backend Flow
Your React Native application might request:
fetch(
"https://yourdomain.com/api/jamb/questions?subject=biology&year=2022"
);
Your backend then makes the actual SdashAPI request using your private token.
This also allows your backend to add:
- User authentication
- Subscription checks
- Rate limiting
- Caching
- Usage tracking
- Custom limits
- Anti-abuse controls
before returning the questions to your app.
Can I Use Expo With SdashAPI?
Yes.
If your React Native application is built using Expo, you can still make normal HTTP requests with fetch().
You don't need a special native module just to communicate with SdashAPI.
A basic Expo app can therefore request your questions just like any other React Native application.
Can I Build a Complete JAMB CBT App With SdashAPI?
Yes.
A basic project could contain screens like:
Home
↓
Select Subject
↓
Select Year
↓
CBT Instructions
↓
Questions
↓
Submit Exam
↓
Result
↓
Corrections
SdashAPI supplies the examination content while your React Native app handles the interface and user experience.
What About Advanced JAMB Features?
SdashAPI is not limited to the core V1 question endpoint.
Its current API structure includes several versions aimed at different types of educational applications.
V1: Core Questions
Useful for standard CBT functionality.
You get the essential information required to present and mark examination questions.
V2: Metadata and Taxonomy
Useful for organising questions around additional information such as topics and subtopics.
V3: Detailed Explanations
Useful for revision applications that want to provide more educational feedback.
V4: Related Question Intelligence
Useful for adaptive learning features where your app recommends similar questions after a learner struggles with a particular concept.
This means your first version could simply be a CBT application, while later versions could introduce personalised practice and deeper learning tools.
Can I Test the Integration for Free?
Yes.
SdashAPI provides developers with a Sandbox for testing integrations.
The Sandbox includes 2,000 API credits, which you can use while building and testing your React Native application.
This gives you room to test things like:
- API authentication
- Fetch requests
- JSON parsing
- Subject selection
- Year selection
- CBT navigation
- Answer selection
- Scoring
- Error handling
before moving your application into full production use.
Complete React Native Example
Here is a simplified version bringing the core pieces together:
import React, { useEffect, useState } from "react";
import {
View,
Text,
TouchableOpacity,
ActivityIndicator,
} from "react-native";
export default function JambCBT() {
const [questions, setQuestions] = useState([]);
const [currentIndex, setCurrentIndex] =
useState(0);
const [answers, setAnswers] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
loadQuestions();
}, []);
const loadQuestions = async () => {
try {
const response = await fetch(
"https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022&limit=20",
{
headers: {
AccessToken: "YOUR_ACCESS_TOKEN",
},
}
);
const result = await response.json();
if (result.status === 200) {
const questionData = Array.isArray(result.data)
? result.data
: [result.data];
setQuestions(questionData);
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const selectAnswer = (option) => {
const question = questions[currentIndex];
setAnswers((previous) => ({
...previous,
[question.id]: option,
}));
};
const calculateScore = () => {
let score = 0;
questions.forEach((question) => {
if (answers[question.id] === question.answer) {
score++;
}
});
return score;
};
if (loading) {
return <ActivityIndicator size="large" />;
}
if (!questions.length) {
return <Text>No questions found.</Text>;
}
const currentQuestion =
questions[currentIndex];
return (
<View style={{ padding: 20 }}>
<Text>
Question {currentIndex + 1} of{" "}
{questions.length}
</Text>
<Text
style={{
fontSize: 18,
marginVertical: 20,
}}
>
{currentQuestion.question}
</Text>
{Object.entries(currentQuestion.option).map(
([key, value]) => (
<TouchableOpacity
key={key}
onPress={() => selectAnswer(key)}
style={{
padding: 15,
marginBottom: 10,
borderWidth: 1,
borderRadius: 8,
}}
>
<Text>
{key.toUpperCase()}. {value}
</Text>
</TouchableOpacity>
)
)}
<TouchableOpacity
onPress={() =>
setCurrentIndex((index) =>
Math.min(
index + 1,
questions.length - 1
)
)
}
>
<Text>Next Question</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>
console.log(
"Score:",
calculateScore()
)
}
>
<Text>Submit Test</Text>
</TouchableOpacity>
</View>
);
}
For a real production app, move the private SdashAPI Access Token to your backend rather than shipping it inside this component.
How Do I Integrate JAMB Past Questions Into a React Native App?
In summary:
- Create a SdashAPI developer account.
- Get your Access Token.
- Request JAMB questions from
/api/v1/q. - Use
type=utme. - Add subject, year and limit filters.
- Parse the returned JSON in React Native.
- Store the questions in state.
- Render the question and answer options.
- Record the student's selected answers.
- Add previous and next navigation.
- Calculate the final score.
- Display corrections and solutions.
- Keep your production Access Token on your backend.
A request can be as simple as:
const response = await fetch(
"https://sdashapi.com/api/v1/q?subject=biology&type=utme&year=2022&limit=20",
{
headers: {
AccessToken: "YOUR_ACCESS_TOKEN",
},
}
);
const data = await response.json();
From there, the rest of the CBT experience is entirely yours to build.
SdashAPI handles the structured JAMB examination content while your React Native application handles the interface, user experience and learning features.