26
loading...
This website collects cookies to deliver better user experience
/polls/
: Lists all the existing questions/polls/{id}/
: Displays a poll details, including the associated results/polss/{id}/
the user should see the title of the poll and the available choicesyarn create react-app ui --template typescript
npx create-react-app ui --template typescript
useQuery
to communicate with our API, we'll set it ot use Axios's GET function. That way we can use our endpoints URLs, both as query keys and argument for axios. utils
folder like so:// utils/queryFn.ts
import axios from "axios";
// We use the built-in QueryFunction type from `react-query` so we don't have to set it up oursevle
import { QueryFunction } from "react-query";
export const queryFn: QueryFunction = async ({ queryKey }) => {
// In a production setting the host would be remplaced by an environment variable
const { data } = await axios.get(`http://localhost:80/${queryKey[0]}`);
return data;
};
// index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { queryFn } from "./utils/queryFn";
import { QueryClient, QueryClientProvider } from "react-query";
// Configuring the queryclient to use
// our query function
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: queryFn,
},
},
});
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import PollIndex from "routes/Poll";
import Results from "routes/Poll/Results";
import CssBaseline from "@mui/material/CssBaseline";
import "./App.css";
function App() {
return (
<div className="App">
<CssBaseline />
<BrowserRouter>
<Routes>
<Route path="/" element={<div>Poll Index</div<}></Route>
<Route path=":questionId/" element={<div>Poll Form</div<} />
<Route path=":questionId/results/" element={<div>Poll Results</div<} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
yarn start
and both routes should become available!PollIndex
and PollResult
components to replace the placeholders! These components will be responsible for querying the API using react-query
and display the results ! useQuery
! /docs
endpoint. /polls/
/polls/{id}
:export interface Choice {
id: number;
choice_text: string;
votes: number;
}
export interface Question {
id: number;
pub_date: string;
question_text: string;
}
export interface QuestionResults extends Question {
choices: Choice[];
}
routes
folder and then mimick the actual route structure of the app. With the latest version of react-router out, I need to check what the current best practices are though!routes/Poll/index.ts
, with the following Implementation://Poll/index.ts
import React from "react";
// The type we've just defined
import { Question } from "types";
import { useQuery } from "react-query";
// Routing
import { Link} from "react-router-dom";
// Material ui stuff
import { styled } from "@mui/material/styles";
import Card from "@mui/material/Card";
import Typography from "@mui/material/Typography";
import Container from "@mui/material/Container";
import Box from "@mui/material/Box";
import Page from "components/template/Page";
const StyledLink = styled(Link)`
text-decoration: none;
`;
const PollIndex: React.FunctionComponent = () => {
// Syncing our data
const { data: questions, isSuccess } = useQuery<Question[]>("polls/");
// In real life we should handle isError and isLoading
// displaying an error message or a loading animation as required.
// This will do for our tutorial
if (!isSuccess) {
return <div> no questions </div>;
}
return (
<Page title="Index">
<Container maxWidth="sm">
{questions?.map((question) => (
<Box marginY={2}>
<StyledLink to={`${question.id}/results/`}>
<Card key={question.id}>
<Typography color="primary" gutterBottom variant="h3">
{question.question_text}
</Typography>
</Card>
</StyledLink>
</Box>
))}
<Outlet />
</Container>
</Page>
);
};
export default PollIndex;
App.tsx
:// App.tsx
import PollIndex from "routes/Poll";
...
function App() {
return (
...
<Route>
...
<Route path="/" element={<PollIndex />}></Route>
</Routes>
)
}
const { data: questions, isSuccess } = useQuery<Question[]>("polls/");
. As you can see I'am passing the useQuery
hook the expected type of our response. Otherwise data
would be of type unkown
and we don't want that ! Polls/index.tsx
page, let's call it Polls/Details.tsx
. This time, as this page will be accessed at polls/<poll_id>
we'll use the useParam
hook from reat-router-dom
to retrieve the id, and pass it to our API. Like so :// Detail.tsx
import React, { useState } from "react";
// types
import { QuestionResults } from "types";
// routing
import { useParams } from "react-router-dom";
// querying
import { useQuery } from "react-query";
// Material ui stuff
import Card from "@mui/material/Card";
import Page from "components/template/Page";
import Chip from "@mui/material/Chip";
import CardContent from "@mui/material/CardContent";
import CardHeader from "@mui/material/CardHeader";
import CardActionArea from "@mui/material/CardActionArea";
import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
const Details = () => {
const { questionId } = useParams();
// This state variable controls
// displaying the results
const [hasVoted, setHasVoted] = useState(false);
// We can use the id from use param
// directly with the useQuery hook
const questionQuery = useQuery<QuestionResults>(`polls/${questionId}/`);
if (!questionQuery.isSuccess) {
return <div> loading </div>;
}
return (
<Page title={questionQuery.data.question_text}>
<Grid spacing={2} container>
<Grid item xs={12}>
<Typography variant="h2">
{questionQuery.data.question_text}
</Typography>
</Grid>
{questionQuery.data.choices.map((choice) => (
<Grid item xs={12} md={6}>
<Card key={choice.id}>
<CardActionArea onClick={() => setHasVoted(true)}>
<CardHeader title={choice.choice_text}></CardHeader>
<CardContent>
{hasVoted && <Chip label={choice.votes} color="success" />}
</CardContent>
</CardActionArea>
</Card>
</Grid>
))}
</Grid>
</Page>
);
};
export default Details;
map
over the choices of a specific poll to display them. The results display are controlled usinguseState
hook. However if this data was really sensitive, we'd have to restrict access to it on the server as well !App.tsx
and admire the result !// App.tsx
import PollDetails from "routes/Poll/Details";
...
function App() {
return (
...
<Route>
...
<Route path="/" element={<PollIndex />}></Route>
<Route path="/" element={<PollDetails />}></Route>
</Routes>
)
}