5
\$\begingroup\$

A counter gets incremented by a custom value. Each increment value gets stored with its refcount that tracks how many times it occurred.
An ordered list shows all increment values entered so far by number of occurrences. Higher rank means more occurrence.

animation of the "feature"

Before venturing futher, a disclaimer:

  • I can't make prettier work on my VS Code, so I resort to formatting (often poorly) the code myself. #No longer the case
  • The reason behind the variable names is that I need long (to the point of excess) names so I can reason about my code and I find udnerscores soothing for some reason.

That being said: I will welcome every remark, reproach, fist shaking with the same amount (a lot) of gratitude that I will welcome criticism (of any kind) and advice.

The code:

Two components: Parent: CounterInDis.vue and child: CounterDisplay.vue.

CounterInDis.vue:

<script>
import CounterDisplay from './CounterDisplay.vue';
let count = 0;
let increment = 0;
let last_value = 0;
let values_occurrence = {};
let sorted_values = [];
export default {
    components: { CounterDisplay },
    data() {
        return {
            count: count,
            last_value: last_value,
            increment: increment,
            values_occurrence: values_occurrence,
            sorted_values: sorted_values,
        }
    },
    methods: {
        increment_count() {
            let checked = this.is_increment_a_number(this.increment)
            if (checked == 0) { return }
            this.last_value = checked;
            this.count += checked;
            this.update_values_occurrence(checked);
            this.sort_by_occurrence();
        },

        is_increment_a_number(amount) {
            if (typeof amount != 'number') { amount = 0; };
            return amount;
        },

        update_values_occurrence(amount) {
            if (!values_occurrence[amount]) {
                values_occurrence[amount] = 0;
            }
            values_occurrence[amount] += 1
        },

        sort_by_occurrence() {
            const keys_n_values = Object.entries(this.values_occurrence);
            keys_n_values.sort((a, b) => b[1] - a[1]);
            sorted_values = keys_n_values.map((entry) => entry[0]);
        },

    },

    computed: {
        to_the_doms_with_you() {
            return {
                current_count: this.count,
                last_value: this.last_value,
                current_increment: this.increment,
                values_occurrence: this.values_occurrence,
                sorted_values: sorted_values, //this.sorted_values won't make the list display, idk why
            }
        },
    },
}
</script>
<template>
    <div>
        <h3>Counter</h3>
        <h4>Welcome to the new and improved counter!</h4>
        <CounterDisplay :counter_display_values="to_the_doms_with_you" />

        <button v-on:click="increment_count">Increment dat count</button>

        <div>
            <label for="increment">Increment by</label>
            <input type="number" v-model="increment">
        </div>
    </div>
</template>

CounterDisplay.vue.

<script>
export default {
    props: {
        counter_display_values: {
            type: Object,
            required: true,
        },
    }
}
</script>
<template>
    <p>The Counter is at {{ counter_display_values.current_count }}.</p>
    <p v-if="counter_display_values.last_value == 0">Click the button and start counting.</p>
    <p v-else>Last increment's value: {{ counter_display_values.last_value }}</p>

    <div class="fixed_width">
        <p class="half">To each value, its number of occurrences:
        <pre>{{ counter_display_values.values_occurrence }}</pre>
        </p>

        <p>Ranked occurrences:</p>
        <ol class="half">
            <li v-for="value in counter_display_values.sorted_values" v-bind:key="value">
                {{ value }}
            </li>
        </ol>
    </div>
</template>
<style>
.full {
    height: 250px;
}

.half {
    height: 110px;
}
</style>

The implementation:

Two components: Parent: CounterInDis.vue and child: CounterDisplay.vue.

CounterInDis.vue has a "number" input field bound to the variable increment. And a button that fires the function increment_count.
increment_count passes increment to the function is_increment_a_number that loosely checks if increment is a number, and either returns 0 if typeof increment != 'number' or returns increment.
if is_increment_a_number returned a 0, increment_amount exits.
otherwise:
it updates the variable last_value with increment (regardless if it's increment === last_value or not).
then, it adds increment to the variable count (the count).
then, it passes increment to the function update_values_occurrence.
update_value_occurrence creates a new entry in the values_occurrence dict if increment isn't already there with the refcount of 0, then it increments the refcount by 1.
then, it (increment_count) fires the function sort_by_occurrence.
sort_by_occurrence get values_occurrence's entries, sort them (which refcount is greater) then map the sorted keys to the array sorted_values.
by now, computed detected changes in the variables it's watching (basically, all of them) and pass them down as props the child component.
something i don't understand: inside the object returned by computed:
sorted_values: this.sorted_values won't display the array in the child component.
sorted_values: sorted_values shows the array inside the child component. why?

inside CounterDisplay.vue: the properties of the object get displayed.

  • a v-for that iterates over* the sorted_values array and uses each value as its own key -> My reasoning is that this ensures the keys will remain unique since no value can have a duplicate.

Is my reasoning correct? Is there a better way to generate the list items and move them around based on their rank?
Thank you (:

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Doing indentation manually is fine, but please do it properly. Your earlier indentation and whitespace was inconsistent and made the code a bit harder to read. There are plenty of JavaScript linting/formatting tools out there. If you're using npm, there's also some npm tools that can help you with this. If you're not using npm, well... then you just have some more to learn :) \$\endgroup\$ Sep 30, 2023 at 22:39

1 Answer 1

4
\$\begingroup\$

You don't need these:

let count = 0;
let increment = 0;
let last_value = 0;
let values_occurrence = {};
let sorted_values = [];

Instead you should assign those values within the data() of your component. This is actually the reason why this.sorted_values doesn't work, because this line:

sorted_values = keys_n_values.map((entry) => entry[0]);

is reassigning the outer sorted_values but not this.sorted_values. As the outer sorted_values changes reference completely, the outer sorted_values is now separated from this.sorted_values. If you would have had more than one component of CounterInDis this would have caused a bug that both of them are using the same sorted_values data. Always put your component-specific values inside the component and don't use data from outside!


sorted_values could actually be a computed property, and as such there's no need for the sort_by_occourence at all -- all that code can actually just be the computed property:

computed: {
    ...
    sorted_values() {
        let keys_n_values = Object.entries(this.values_occurrence);
        keys_n_values.sort((a, b) => b[1] - a[1]);
        return keys_n_values.map((entry) => entry[0]);
    }
}

(I wouldn't use const keys_n_values because you are mutating the array with sort)


this.counter can also be a computed property because it is just the sum of all values keys in the array multiplied by their value.


a v-for that iterates of the sorted_values array and uses each value as its own key -> My reasoning is that this ensures the keys will remain unique since no value can have a duplicate.

That reasoning is correct, yes. Just a tip: v-bind:key is the same as :key. Prefer to be consistent, in some cases - e.g. :counter_display_values you are just using the colon.

\$\endgroup\$
3
  • \$\begingroup\$ but of course! this.sorted_values is shorthand for this.$data.sorted_values if the outside sorted_values change its reference to a different array, then the one inside data will never get updated with how the code i wrote works. also: keep data inside data(), pretty self evident in hindsight. i managed to make prettier work in vscode so i will update the code's formatting (without changing the code as per CR's rules) and will keep in mind all of your recommendations. thank you for your time and effort ( : top notch answer. \$\endgroup\$
    – dhiaagr
    Sep 30, 2023 at 19:09
  • \$\begingroup\$ @dhiaagr You're welcome. Just FYI: Even formatting edits in your question can invalidate answers, but this time I changed that formatting part to become a comment instead. Try to fix formatting before you post questions on Code Review as it's a very easy thing to post an answer just highlighting the formatting aspects (and it's fine to do that according to this site standards - which I agree with). And great first question btw, hope to see similar interesting questions from you in the future! I don't often review code here these days, but I couldn't resist this one :) \$\endgroup\$ Sep 30, 2023 at 22:42
  • \$\begingroup\$ Thank you so much, that means a lot ( : I will keep all your valid points in mind. Hope to get more of your answers soon, too ( : \$\endgroup\$
    – dhiaagr
    Oct 1, 2023 at 0:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.