MOD

# Flashcard template not working

Have there been any breaking changes to Express.js since this course was released? I don't understand why, but my query strings are not working. I can open up the server but as soon as I try to type the URL with the query string it crashes the app.

http://localhost:3000/cards/1?side=question

_http_outgoing.js:491
throw new Error('Can\'t set headers after they are sent.');
^

Error: Can't set headers after they are sent.
at Array.write (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\finalhandler\index.js:285:9)
at listener (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\on-finished\index.js:169:15)
at onFinish (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\on-finished\index.js:100:5)
at callback (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\ee-first\index.js:55:10)
at IncomingMessage.onevent (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\ee-first\index.js:93:5)
at emitNone (events.js:106:13)
at IncomingMessage.emit (events.js:208:7)


Code as below.

card.pug
extends layout.pug

block content
h1 Flash Cards

section#content
h2= text

if hint
p
i Hint: #{hint}
else
p
i Hint: (No Hint available)

app.js
const express = require('express');

//import body-parser middleware
const bodyParser = require("body-parser");

//Express launch function
const app = express();
//set the view engine to use pug for templating
app.set("view engine", "pug");

//use bodyParser and cookieParser middleware - third party.
app.use(bodyParser.urlencoded({extended: false}));

const indexRouter = require("./routes");
const cardsRouter = require("./routes/cards");

app.use(indexRouter);
app.use('/cards', cardsRouter);

/*MIDDLEWARE*/

/* app.use((req, res, next) => {
console.log("Hello");
next();
});

app.use((req, res, next) => {
console.log(", World");
next();
}); */

//404 Middleware!
app.use((res, req, next) => {
err.status = 404;
next(err);
});

//error handler middleware
app.use((err, req, res, next) => {
// do something
//res.status(err.status)
res.locals.error = err;
res.status(err.status);
res.render('error');
next();
//res.end();

});

//Set up the development server listen method - port number
app.listen(3000, function(){
console.log("server currently running on Heroku");

});

index.js
const express = require("express");
const routes = express.Router();

//list data
const names = [
"Jack Bauer",
"Jill Green",
"John Joe",
"Jonah Whale",
"Jamaal LH",
"Joe Fisher"
]

/*ROUTES*/

//serve the home route
routes.get('/', (req, res) => {

//basic response with the send method
if(name) {
res.render('index', {name, page_title: "Flash Card App"});
} else {
res.redirect('/hello');
}
//res.end();
});

//serve the register route
routes.get('/register', (req, res) => {

//basic response with the send method
res.render('register', {page_title: "Flash Card App: Register of users", names});
//res.end();

});

//serve the 4th route which will be a post route
routes.get('/hello', (req, res) => {

//perform actions based on setting of cookie
if(name) {
res.redirect('/');
} else {
res.render('hello', { page_title: "Flash Card App: Hello Route"});
}

//res.end();

});

routes.post('/hello', (req, res) => {

//basic response with the send method,
res.redirect('/');

//console.log(req.body);
//res.end();

});

routes.post('/goodbye', (req, res) => {

res.redirect('/hello');
});

//export index routes to app.js
module.exports = routes;

cards.js
const express = require("express");
const routes = express.Router();

const { data } = require("../data/flashcardData.json");
const { cards } = data;

//serve the cards route
routes.get('/:id', (req, res) => {
const { side } = req.query;
const { id } = req.params;
const text = cards[id][side];
const { hint } = cards[id];

const templateData = { text, hint };
res.render('card', templateData);
});

//export cards routes to app.js
module.exports = routes;


What is cards[id][side] returning if you log it (if it gets that far)? Probably best off logging cards and req.query.side before declaring anything to make sure everything is right and the problem is in the route block.

I can't see the json data, but is there supposed to be a conditional for if side is 'question' text = cards[1][0] else cards[1][1] or am I see this wrong?

Yea I probably should be logging things a bit more. I’ll look at that.

Changing to req.query.side doesn’t seem to have an effect either.

I don’t think we’re supposed to be making any modifications to the JSON. Or anything that’s coded in the video which is what makes me think the method has changed a bit over the years.

Are you are getting the right return from req.query.side? Have you used a zero or one instead of question? I'm guessing it's formatted like [['question text', 'answer text], ['question text', 'answer text'], ['question text', 'answer text']] in the data file, no?

Right... I'm getting "question" logged out from req.query.side which I think is what we're after.

The log for cards[id][side] is logging undefined so that seems to be where the problem lies. I changed the line back to const { side } = req.query; removes that undefined log.

Truth is I'm utterly lost.

I have at least now verified my confused rambling about the code being out of date is nonsense by downloading Andrew's code. Now to look in and seek out the bug!

You're getting the data object from the .json file, I assume. Is cards being set? Is there a match in there for cards[1].question (assuming the json is an array), for example the key is definately 'question?

Yes, I don't think there's any question of the data being correctly hooked up. My console logs come up trumps for the side and question data. Everything just seems to fall apart when I try to input route parameters and query strings in the URL .

Truly I think all that part is actually done correctly. Where I think it's falling apart is there is one get route "register" that doesn't actually appear in the course. I have an array called "data" in the index route as well as it's own file called data.js but that file is redundant and isn't actually being used.

The idea of me doing this is so I can expand it when the course is done and to prove to myself i can practice all of this. But it's like one problem substituting for another at the moment. :)

I'm pretty sure the the cards[id][question] return is undefined because that statically comes out to cards[1]['question']. That's not going to return a valid value which is trying to be used in the pug file as text which is gonna cause a runtime error. If you write that line as

const text = cards[id][question] ? 'This is a placeholder for a .json file' : 'No text returned';


The text in the card.pug should be no text returned.

That's not what I get though when I log that output. So confusing!

I'd link you guys to a Repo for this so you could have a proper look but it's linked to a Repo I'm doing for #100DaysOfCode so I'm reluctant to do that.

This is frustrating though because if it wasn't for this, I'd have a functional App! 🤣

Statically set the text then.

const text = 'Static Text';


If that doesn't render then it has to be something with the data because everything else in the route makes sense. I still don't know why cards[id] is an array of arrays and an array of objects.

Should const templateData = { text, hint }; be const templateData = { text: text, hint: hint };, meaning to assign the key values to the appropriate variable?

Hi guys.

I want to thank you both for sticking around the thread with me.

Let's see if we can nip this in the bud. I've created a separate repository with my latest changes. I've finished following the course up to the last section, so it doesn't serve static files yet beyond Pug and JS.

Hopefully the answer is within. :)

https://github.com/jg-digital-media/express_site

Don't forget to npm install obviously.

Yeah try

const { question } = cards[id];

const template = { text: question, hint };


You were referencing a 2d array before. I'm guessing the hint value is correct when you logged it? If not you could try to use JSON.parse(cards) because with the cards are not going through the request so the body parser middleware wouldn't do that automatically. Hopefully that works, I'm on mobile and will try to use the code when I i get home if it's still a problem. Seeing the json file makes me think this will work though, and this is for a hard coded query string with a question. When requesting the answer it would have to be different.

So i ran it and this worked for just getting the id, text, and hint rendered dynamically with no errors.

routes.get("/:id", (req, res) => {
const { side } = req.query;
const { id } = req.params;

let text = "";
let hint;

if (side === "question") {
text = cards[id].question;
hint = cards[id].hint;
} else {
}

const templateData = {
id,
hint,
text
};

res.render("cards", templateData);
});


but the middleware has to change to

//error handler middleware
app.use((err, req, res, next) => {
res.locals.error = err;
const status = err.status || 500;
res.status(status);
res.render("error");
});


I kinda gutted that route but now you can add the the display values in the dataTemplate. I typed in answer as the query and that also worked. It was a weird middleware glitch that couldn't inject the id. Weird..

Nice catch. I'm at work so haven't been able to look at the repo.

I have a very horrible feeling I've led you both down a wild goose chase for nothing.

If you look at the layout.pug file you'll find an attempt to link a stylesheet directly into the pug template. There is a stylesheet there in the project but it shouldn't be there as that's not the way you serve those assets. Anyway I removed that line so the header is not trying to send that anymore.

The app is now working flawlessly. I've just spent the last 2 hours trying to work out why the question/answer and hint text wasn't showing up after Blake's edit. Now I know why.

I'm so sorry!

layout.pug
<!DOCTYPE html>
html(lang="en")
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title= page_title

// Stylesheets
body

if name
h2 Welcome, #{name}

form(action="/goodbye" method="post")
button.submit Goodbye!
p
a(href='/cards') Begin

block content

include includes/footer.pug
`

That's funny I was wondering why I was getting 0 and style.css when I tried to log the id. Was confusing. Either way it worked!

Believe me so was I. It was staring me in the face and screaming from the console! Live and learn, as they say!