Implementing a previous question logic with react hooks - javascript

I'm making a pretty simple react quiz app in which you go from question to question via a 'next' button. Each time the next button is clicked a useState hook (passed as props to onAnswerUpdate) pushes an object with the question's title and the chosen answer to an array named 'answers':
const nextClickHandler = (e) => {
if(selected === '') {
return setError('Please select one option');
}
onAnswerUpdate(prevState => [...prevState, { q: data.question, a: selected }]);
setSelected('');
if(activeQuestion < numberOfQuestions - 1) {
onSetActiveQuestion(activeQuestion + 1);
}else ...
}
I implemented a basic previous button logic whose only function is to return to the previous question but I struggled to find a way to replace the object in the answers array corresponding to the correct question when I change the answer to an already answered question. I've tried replacing it using the activeQuestion number as an index to the array (like in the code sample below) and splice method but neither of them worked. Any help will be greatly appreciated.
const nextClickHandler = (e) => {
if(selected === '') {
return setError('Please select one option');
}
onAnswerUpdate(prevState => [...prevState, answers[activeQuestion] = { q: data.question, a: selected }]);
setSelected('');
if(activeQuestion < numberOfQuestions - 1) {
onSetActiveQuestion(activeQuestion + 1);
} else ...

Hook with prevState and an array always drive me crazy... I prefer to use an easier approach. If you need to replace an element inside an array that is on state, you could write:
...
let currAnswer = answer;
currAnswer[activeQuestion] = { q: data.question, a: selected };
onAnswerUpdate(currAnswer);
...

Related

React Ag-Grid external filter output is delayed from input

I am trying to use external filter with ag-grid in react and it does not seem to work properly.
When I select the input on the external filter, nothing in the table gets filtered. On the next selection the table gets filtered according to the previous selection. This continues to happen for all subsequent inputs, basically the table gets filtered for the previously selected input and not the current one
I have used the example from the ag-grid website and modified it to work with useState.
They main functions for the external filter are as follows.
const isExternalFilterPresent = () => {
return true;
};
const doesExternalFilterPass = (node) => {
switch (filterVal) {
case "below25":
return node.data.age < 25;
case "between25and50":
return node.data.age >= 25 && node.data.age <= 50;
case "above50":
return node.data.age > 50;
case "dateAfter2008":
return asDate(node.data.date) > new Date(2008, 1, 1);
default:
return true;
}
};
The following useEffect ensures that the filter change is only registered after setState is complete
useEffect(() => {
if (!!gridApi) {
gridApi.onFilterChanged();
}
}, [filterVal, gridApi]);
Below is the codesandbox link for the demo and the complete code
https://codesandbox.io/s/ag-grid-external-filter-j6tru
I do not understand why this is happening? Can anyone help with this?

Javascript for survey

Background
I'm working on a survey in javascript which contains ten "yes" or "no" questions. I'd like to code the "yes" responses to = 1 and the "no" responses to = 0. I'm using the html tag so they can only choose one or the other.
Then I'd like to have the sum of their responses added up and divided by the total number of questions to yield a percentage. Depending on their percentage, I would then like to output some HTML text to a field below it with some helpful tips and advice relevant to their score.
To be clear, though I'm learning javascript now, I don't expect anyone to code the thing for me, but I'd just like to know where I should be looking for answers to make this happen. I'm thinking I need to use the if/else conditions to account for the yes/no responses and then maybe I need another function() to do the rest of the calculations.
I've tried a number of different variations of if/else statements but I'm just confused about how to 1) code the yes/no responses; 2) integrate them into if/else statements.
I'll add an abbreviated snippet of code with two sample questions that I'm trying to get working now.
function calc (form) {
var score1;
var score2;
var Result = form.Result.value;
if (form.Question1.options[form.Question1.selectedIndex].value = "Yes") { score1 = 1;
} else {
score1 = 0;
}
if (form.Question2.options[form.Question2.selectedIndex].value = "Yes") { score2 = 1;
} else {
score2 = 0;
}
Result = (((score1 + score2) / 10) * 100);
if (Result == 80) {
document.getElementById("recommendation").innerHTML = "<h4>Heading here</h4> <p>Advice goes here based on their result</p>"
}
}
To be fair, I believe you need to rethink your approach. Having an if-then-else approach for each question & answer can be quite tedious in maintaining or when you want to change the answers.
If you would create a data structure for your questions, you could use for-loops or saving indexes of the current question to handle an answer. The correct answer would then also be part of your data structure (but that is up to you, it could be a client/server request as well).
You already got some answers why your current approach doesn't work due to missing up assignment with equality operators, but I thought I would give you an alternative solution.
This solution will create a dynamic ui, and handle answers on questions when the next button is clicked. The questionaire here is unidirectional, you can only go forward :)
It is mainly to give you an idea how to approach it differently. I don't imagine you actually using this code as is.
The code has quite some inline comments, and is based on the usage of a generator function (which are not supported by Internet Explorer, but should be fine in any other browser)
Upon completion, the score would be displayed in a message box.
// data structure that takes question / answer / which is correct and the points attributed
const questions = [
{
question: 'What platform are you on?',
answers: ['Stackoverflow', 'codereview'],
correct: 0,
points: 5
},
{
question: 'What is the answer to everything',
answers: [42, 'I don\'t have a clue'],
correct: 0,
points: 1
},
{
question: 'How much is 7*6',
answers: ['I am not good with maths', 42],
correct: 1,
points: 10
}
];
// a simple generator that is used in the questionaire
function *questionsGenerator( questions ) {
yield* questions;
}
// creates a questionaire, with forward only options (due to the use of the generator function)
// all variables are locally scoped, when all questions were answered, the onCompleted callback would be called
// it returns an object with nextQuestion function though it could call nextButton internally, and you just have to call the function once if you would want to change it
const questionaire = ( query, nextButton, target, onCompleted ) => {
let score = 0;
let iterator = questionsGenerator( query );
let question = null;
let selectedAnswer = -1;
nextButton.addEventListener('click', nextQuestion);
function evaluateAnswer() {
if (!question) {
// no question yet
return;
}
if (selectedAnswer < 0) {
return;
}
if (question.correct === selectedAnswer) {
score += question.points;
}
return;
}
function nextQuestion() {
evaluateAnswer();
question = iterator.next();
// this is a bit of a hack to check if we just had the last question or not
if (question.done) {
nextButton.removeEventListener('click', nextQuestion);
onCompleted( score );
return;
}
question = question.value;
drawUI();
}
function drawUI() {
// disable next button
nextButton.setAttribute('disabled', true);
selectedAnswer = -1;
// remove existing items
Array.from( target.childNodes ).forEach( child => target.removeChild( child ) );
// create new questions (if available)
const title = document.createElement('h1');
title.innerHTML = question.question;
target.appendChild( title );
question.answers.map( (answer, i) => {
const el = document.createElement('input');
el.type = 'radio';
el.name = 'answer';
el.value = i;
el.id = 'answer' + i;
el.addEventListener('change', () => {
selectedAnswer = i;
nextButton.removeAttribute('disabled');
} );
const label = document.createElement('label');
label.setAttribute('for', el.id );
label.innerHTML = answer;
const container = document.createElement('div');
container.appendChild(el);
container.appendChild(label);
return container;
} ).forEach( a => target.appendChild( a ) );
}
return {
nextQuestion
}
};
// create a questionaire and start the first question
questionaire(
questions,
document.querySelector('#btnNext'),
document.querySelector('#questionaire'),
score => alert('You scored ' + score )
).nextQuestion();
<div id="questionaire">
</div>
<div class="toolstrip">
<button id="btnNext" type="button">Next</button>
</div>
If you want to check for equality use == or ===. == will check if the values are the same,
=== will also check if the types are the same.
For example:
0 == "0" => true
0 === "0" => false.

How to add/remove elements from array based on array contents

I've been struggling with this piece for a few days and I can't seem to find what's wrong. I have an array with a few objects:
myMainPostObj.categories = [Object, Object]
This is for add/removing categories to a post. Imagine I'm editing an existing post which is already associated with a couple of categories (as per code above).
I also have an array which has all categories in the db (including the ones which are associated with the post). On my js controller I have the following code:
$scope.addCategory = function (cat) {
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.slice(i, 1);
} else if (cat._id !== $scope.post.category_ids[i]) {
$scope.post.category_ids.push(cat._id);
}
}
}
The above function is called every time the user click on a category. The idea is for the above function to loop through the categories within the post (associated with the post) and compares it with the category passed as argument. If it matches the function should remove the category. If it doesn't it should add.
In theory this seems straight forward enough, but for whatever reason if I tick on category that is not associated with the post, it adds two (not one as expected) category to the array. The same happens when I try to remove as well.
This is part of a Angular controller and the whole code can be found here
The error in your code is that for each iteration of the loop you either remove or add a category. This isn't right... You should remove if the current id matches but add only if there was no match at all. Something like this:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
I guess the problem could be that you're altering the category_ids array while you're iterating over it with the for loop. You might be better off trying something like this:
$scope.addCategory = function (cat) {
var catIndex = $scope.post.category_ids.indexOf(cat._id);
if (catIndex > -1)
$scope.post.category_ids.splice(catIndex, 1);
else
$scope.post.category_ids.push(cat._id);
}
Note that indexOf doesn't seem to be supported in IE7-8.
Let's simplify this a bit:
const CATEGORIES = [1, 2, 3];
let addCategory = (postCategories, categoryId) => {
CATEGORIES.forEach((cId, index) => {
if (postCategories[index] === cId) console.log("Removing", categoryId);
else console.log("Adding", categoryId);
});
return postCategories;
};
Please ignore the fact that we actually are not mutating the array being passed in.
A is either equal or not equal to B - there is no third option (FILE_NOT_FOUND aside). So you are looping over all of your categories and every time you don't find the category ID in the array at the current index you add it to the postCategories array.
The proper solution to your problem is just to use a Set (or if you need more than bleeding edge ES6 support, an object with no prototype):
// Nicer, ES6-or-pollyfill option
var postCategories = new Set();
postCategories.add(categoryId);
// Or, in the no-prototype option:
var postCategories = Object.create(null);
postCategories[categoryId] = true;
// Then serialization needs an extra step if you need an array:
parentObject.postCategories = Object.keys(parentObject.postCategories);

JavaScript/jQuery equivalent of LINQ Any()

Is there an equivalent of IEnumerable.Any(Predicate<T>) in JavaScript or jQuery?
I am validating a list of items, and want to break early if error is detected. I could do it using $.each, but I need to use an external flag to see if the item was actually found:
var found = false;
$.each(array, function(i) {
if (notValid(array[i])) {
found = true;
}
return !found;
});
What would be a better way? I don't like using plain for with JavaScript arrays because it iterates over all of its members, not just values.
These days you could actually use Array.prototype.some (specced in ES5) to get the same effect:
array.some(function(item) {
return notValid(item);
});
You could use variant of jQuery is function which accepts a predicate:
$(array).is(function(index) {
return notValid(this);
});
Xion's answer is correct. To expand upon his answer:
jQuery's .is(function) has the same behavior as .NET's IEnumerable.Any(Predicate<T>).
From http://docs.jquery.com/is:
Checks the current selection against an expression and returns true, if at least one element of the selection fits the given expression.
You should use an ordinary for loop (not for ... in), which will only loop through array elements.
You might use array.filter (IE 9+ see link below for more detail)
[].filter(function(){ return true|false ;}).length > 0;
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I would suggest that you try the JavaScript for in loop. However, be aware that the syntax is quite different than what you get with a .net IEnumerable. Here is a small illustrative code sample.
var names = ['Alice','Bob','Charlie','David'];
for (x in names)
{
var name = names[x];
alert('Hello, ' + name);
}
var cards = { HoleCard: 'Ace of Spades', VisibleCard='Five of Hearts' };
for (x in cards)
{
var position = x;
var card = card[x];
alert('I have a card: ' + position + ': ' + card);
}
I suggest you to use the $.grep() method. It's very close to IEnumerable.Any(Predicate<T>):
$.grep(array, function(n, i) {
return (n == 5);
});
Here a working sample to you: http://jsfiddle.net/ErickPetru/BYjcu/.
2021 Update
This answer was posted more than 10 years ago, so it's important to highlight that:
When it was published, it was a solution that made total sense, since there was nothing native to JavaScript to solve this problem with a single function call at that time;
The original question has the jQuery tag, so a jQuery-based answer is not only expected, it's a must. Down voting because of that doesn't makes sense at all.
JavaScript world evolved a lot since then, so if you aren't stuck with jQuery, please use a more updated solution! This one is here for historical purposes, and to be kept as reference for old needs that maybe someone still find useful when working with legacy code.
Necromancing.
If you cannot use array.some, you can create your own function in TypeScript:
interface selectorCallback_t<TSource>
{
(item: TSource): boolean;
}
function Any<TSource>(source: TSource[], predicate: selectorCallback_t<TSource> )
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (let i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
} // End Function Any
Which transpiles down to
function Any(source, predicate)
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (var i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
}
Usage:
var names = ['Alice','Bob','Charlie','David'];
Any(names, x => x === 'Alice');

jquery/javascript: arrays

I am a begginer with Javascript/jQuery and I hope someone can help me with the following:
I have a simple form (7 questions; 3 radio buttons/answers per question - except for question 5 with 8 possible choices ) and based on the selected answers, when user clicks on 'view-advice' I want to display relevant advices (combination of 38 possible advices) below the form.
I have given "a", "b", "c",... values to radio buttons and I am collecting them in an array.
The part where the script alerts the array works ok.
I can't figure out the part where I display the advices depending on the values in the array.
I'd appreciate your help! Thanks!
Here is the code:
var laArray = new Array();
$('.button-show-advice').click(function(){
$(":radio:checked").each(function(i){
laArray[i] = $(this).val();
if (laArray == ["a","d","g","j","m","u"]) {
$("#advice-container, #advice1, #advice2").show(); // something is wrong here :(
};
})
alert(laArray) // testing to see if it works
})
Rather than test for equality, I think the better means is to check whether or not each of your values are in the array using the jQuery inArray function.
Granted, this is just the beginning of code. You could probably write a function to shore this up, like so.
function radioSelected(val) {
return ($.inArray(val, laArray) != -1);
}
and adapt it to your existing script.
You cannot compare arrays this way you should probably
either compare each element of the 2 arrays
function compare_array(array1,array2) {
var i;
for(i=0;i=array1.length;i++) {
if(array1[i]==array2[i]) {
return false;
}
}
return true;
}
or serialize the array in a comparable form ( comma separated string for example )
function compare_array(array1,array2) {
return array1.join(",")==array2.join(",");
}
Would be nice to see the HTML code. But I guess you want to do something like this:
var laArray = [];
var compareValues = function(arr1, arr2) {
$(arr1).each(function(index, el) {
if(el !== arr2[index]) {
return false;
}
});
return true;
};
$('.button-show-advice').click(function(){
$(":radio:checked").each(function(i){
laArray.push($(this).val());
});
if(compareValues(laArray,["a","d","g","j","m","u"])) {
$("#advice-container, #advice1, #advice2").show();
}
});
EDIT: updated the code, forgot the }); ...

Categories