recently I'm working on a project showing sport events data
While v-for through the events array (which is around 500~2000 length), I'll call a method in each to show their participants name
<div v-for="event in eventArr" :key="event.id">
<template v-if="mappingObj[event.id]">
<div class="participantName">
{{ getCorrespondParticipants (
mappingInfo[event.id][mappingId],
mappingInfo[event.id][sportId])
}}
</div>
</template>
</div>
what it looks like in template, mappingObj is a object collecting all event data with event id as key, its length is around 500~2000(same as the origin events array)
getCorrespondParticipants (pidArr, sportId) {
this.participantListObj[sportId];
});
// this part was supposed to get corresponded participants data from the object, returning this.participantListObj[sportId][pid in pidArr]
return pArr;
},
The method was trying to get the participants info, tho even if I remove all the lines just left 'this.participantListObj[sportId]' there(so it basically did nth but calling the obj), it still causing massive lag result to slow performance for my page, not to mention there's another method to get the league info
participantListObj () {
let cloneObj = JSON.parse(
JSON.stringify(this.totalParticipantList)
);
for (let sportId in cloneObj) {
cloneObj[sportId] = cloneObj[sportId].reduce(
(pObj, item) =>
Object.assign(pObj, { [item.participant_id]: item }),
{}
);
}
return cloneObj;
},
which would looks like...
participantListObj :{
1: {
123:{
participant_id: 123
name_EN: 'Player',
name_CN: '玩家'
},
...(18000+)
}
}
The obj is participants list object under each sport Id, made from a participants array list which length could be around 18000, in this case the page has only one sport that is soccer. Did I did anything wrong?
Related
The data from workData fills <Card></Card> correctly.
The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)
my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.
why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.
if it's not obvious im super new, i am, any responses would be super appreciated. ty
cards good
after clicking "Read1" bad, should say Test1, test text 1
in App.js: import { Works } from "./Works";
in Works.js: import { workData } from "./data";
also in Work.js:
export const Works = () => {
const [show, setShow] = React.useState(false);
const onClick = () => setShow(true);
return (
<>
<div className="work-container">
<Row xs={1} md={2} lg={4} className="g-4">
{workData.map((data, key) => {
return (
<div key={key}>
<Col>
<Card>
<Card.Img variant="top" src={data.projectImage} />
<Card.Body>
<Card.Title>{data.projectTitle}</Card.Title>
<Card.Text>with {data.projectTeam}</Card.Text>
<Button variant="link" onClick={onClick}>
{data.readMore}
</Button>
</Card.Body>
<Card.Footer>{data.tags}</Card.Footer>
</Card>
</Col>
<Modal
show={show}
onHide={() => setShow(false)}
dialogClassName="modal-95w"
>
<Modal.Header closeButton>
<Modal.Title>{data.projectTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Image src={data.projectImage}></Image>
<p>
{data.modalText}
</p>
<Image src={data.modalImage}></Image>
</Modal.Body>
</Modal>
</div>
);
})}
</Row>
</div>
</>
);
}
in data.js:
export const workData = [
{
projectTitle: "Test1",
modalTitle: "Modal1",
modalText: "test text 1",
modalImage: "image",
readMore: "Read1",
projectImage: "image",
projectTeam: "Test1",
year: "2022",
link1: "link",
link2: "link2",
tags: [
"#tag1 ",
"#tag2 "
]
},
...
The data from workData fills <Card></Card> correctly.
The <Modal></Modal> only fills with the last entry of workData (e.g. Test4, Modal4, test text 4...)
my goal is to generate cards and respective modals (for each card) using the data from the json, in the same file.
why is the modal only being filled by the last properties in the json? how do i get it to populate with the entire array? if possible please explain why this does not work the way it is.
cards good
after clicking "Read1" bad, should say Test1, test text 1
You iterate over workData for Cards and Modals, but you use only one single state for everything. What you need to do, is to also create a state for every Modal. Usually you create an array with unique id as key and boolean value. I assumed projectTitle is unique:
{
Test1: false,
Test2: false,
Test3: false
}
Because you don't know the length of your data, you just iterate over the array, as you have done for Cards und Modals:
const initialShowState = Object.fromEntries(
workData.map((data) => [data.projectTitle, false])
);
const [show, setShow] = React.useState(initialShowState);
Then you need to create a generic callback function, which takes the id of the Card and shows the appropriate Modal. I simplified the logic and created a toggle function:
const toggleShow = (id) =>
setShow((prev) => {
return { ...prev, [id]: !prev[id] };
});
Finally, when you render UI and iterate over workData, you need to apply the callback function to Button onClick and Modal onHide event handlers and set the show property of Modal:
<Button variant="link" onClick={() => toggleShow(data.projectTitle)}>
...
<Modal
show={show[data.projectTitle]}
onHide={() => toggleShow(data.projectTitle)}
dialogClassName="modal-95w"
>
That's it. Here is the working sandbox: https://codesandbox.io/s/hungry-sunset-t865t3
Some general tips:
You don't need the outer Fragment in Works as you only have one upper most element
If you use JSX syntax in your file, your extension should be .jsx and not.js (Works.jsx)
Using index as key in the list is bad practice. Find some unique id in your data
what is ngxs' way of passing object?
I have this following state
#State<CounterStateModel>({
name: 'counter',
defaults: { count: 0, isLoading: true, tests: [{value: 0}] }
})
and the selector
#Selector()
static count(state: CounterStateModel) {
return state.count;
}
#Selector()
static test(state: CounterStateModel) {
return state.tests;
}
I used those two selectors in component.ts
#Select(CounterState.test) test$: Observable<TestModel[]>;
#Select(CounterState.count) count$: Observable<number>;
then in template I show the value by
<div class="count" *ngFor="let test of (test$ | async)">TEST: {{ test?.value }}</div>
<div class="count">COUNT: {{ (count$ | async) }}</div>
then I increase the count or value by subscribing and directly plus 1
incrementT() {
this.test$.subscribe(test => test.map(t => t.value++))
}
incrementC() {
this.count$.subscribe(count => count++)
}
after clicking button
the value in test array element is increased, but count remains the same.
so what is NGSX passing?
see the stackblitz url:
https://stackblitz.com/edit/ng-ngxs-playground-pmekh1?file=src/app/counter/counter.component.ts
To mutate values contained in the state you need dispatch an action to the state, rather than modify the values that you receive in your subscriptions.
NGXS enforces this rule to ensure state is immutable, so if in your stack blitz you set developmentMode: true you will see NGXS errors in the console when you try to increment the 'test' values.
To modify the count or the test items you need to dispatch an action to the state and have the state make the update (thus emitting the new value to your subscribed component). This is outlined here in the NGXS documentation. This is a bit of a different way of thinking if you are new to NGXS (or CQRS patterns).
In my List component I have a method which count the length of the array within certain categories.
methods: {
getLengthofaCategory(cat) {
const LowerCaseSearch = this.search.toLowerCase();
let categoryCount = this.products.filter(
product =>
(product.name.toLowerCase().includes(LowerCaseSearch) ||
product.category.toLowerCase().includes(LowerCaseSearch)) &&
(!this.checked.length || this.checked.includes(product.category)) &&
product.category === cat
);
return categoryCount.length;
}
}
See here my setup in this sandbox.
But I want the values next to the checkboxes (which are coming from my CheckBox component).
How do I get the logic from the method getLengthofaCategory into my CheckBox component?
So I am able to use {{ getLengthofaCategory('tennis') }} in the v-for loop, inside the CheckBox component. And then maybe I can also use category.value instead of hardcoding e.g 'tennis' as the paramater?
In your list.vue, you can use the already created computed function filteredData instead of doing the filter again. This saves some performance because in Vue, computed properties are "cached" once run.
So you can create a new computed function that creates an object with keys per category and value can either be just the amount or an array of products in this category.
I would then pass this computed value to the CheckBox component via a prop, then inside the CheckBox component, you can display the .length or value regarding how many items each category has:
List.vue:
computed: {
//...
amountPerCategory() {
return this.filteredData.reduce((categories, product) => {
if (!(product.category in categories)) {
categories[product.category] = [];
}
categories[product.category].push(product);
return categories;
}, {});
}
}
CheckBox.vue:
<span class="ml-2 text-gray-700 capitalize">{{ category.value }}</span> -
<span
v-if="count[category.value]"
class="ml-2 text-gray-700 capitalize"
>{{ count[category.value].length }}</span>
count: {
type: Object,
default: () => ({})
}
https://codesandbox.io/s/admiring-ellis-4hojl?file=/src/components/CheckBox.vue
I have a multistep form on vue js. I need to dynamically during input send requests, validate on the server and receive a response to display validation errors.
My data:
data() {
return {
form: {...this.candidate}, // we get an object with fields already filled *
errors: new Errors(), // object with validation errors
}
},
Now for each input I have computed property:
characteristicFuturePlans: {
get() {
return this.form.characteristic_future_plans;
},
set(value) {
this.saveField('characteristic_future_plans', value);
}
},
saveField method sends data:
saveField(field, value) {
this.form[field] = value; // keep the data in the object relevant
axios.put(`/api/candidate/${this.candidate.token}`, {[field]: value})
.then(() => { this.errors.clear(field) })
.catch((error) => {
this.errors.record(field, error.response.data.errors[field]);
});
},
Now, with each change of input, a request will be sent. But with this approach, when we quickly type text in a field, sometimes the penultimate request sent comes after the last. It turns out that if you quickly write "Johnny", sometimes a query with the text "Johnn" will come after a query with the text "Johnny" and the wrong value will be saved in the database.
Then I made sure that the data was sent 1 second after the termination of text input. Added timerId: {} to data(){} and then:
saveField(field, value) {
if(this.timerId[field]) {
clearTimeout(this.timerId[field]);
}
this.timerId[field] = setTimeout(this.send, 1000, field, value);
},
send(field, value) {
this.form[field] = value;
axios.put(`/api/candidate/${this.candidate.token}`, {[field]: value})
.then(() => { this.errors.clear(field) })
.catch((error) => {
this.errors.record(field, error.response.data.errors[field]);
});
},
But now, if after filling in the input in less than a second, press the button to go to the next step of the form, the page will simply refresh. (the button to go to the next step will send a request to the server to check if the required fields are filled)
How correctly save data to the database during text input? Can I do this without setTimeout()? If so, how can I make sure that the data of the last request, and not the penultimate, is stored in the database?
I will be glad to any tip.
Updated. Attach some template code.
Part of Step[i].vue component:
<div class="row">
<div class="form-group col-md-6">
<element-form title="Title text" :error="errors.get('characteristic_future_plans')" required isBold>
<input-input v-model="characteristicFuturePlans" :is-error="errors.has('characteristic_future_plans')"
:placeholder="'placeholder text'"/>
</element-form>
</div>
</div>
template of input-input component:
<input :value="value" :type="type" class="form-control" :class="isErrorClass"
:placeholder="placeholder" #input="$emit('input',$event.target.value)">
Components of form steps are called from the Page component. Nearby is the component of the button for moving to the next step.
<component :is="currentStep"
:candidate="candidate"
// many props
:global-errors="globalErrors"/>
<next-step :current-step="step" :token="candidate.token"
#switch-step-event="switchStep" #throw-errors="passErrors"></next-step>
NextStep component sends a request to the server, it is checking whether the required fields are filled in the database. If not, throw out a validation error. If so, go to the next form step.
you could try with watching the input values, and then use _.debounce() from underscore.js (src: https://underscorejs.org/#debounce) to delay the method call that makes a server request:
watch: {
fieldName: _.debounce(function(value) {
if(value === ''){
return;
}
this.saveField(this.fieldName, value);
},
...
I have a Knockout observable array that refuses to update the UI (a jquery Accordion) to which it is bound after a .sort() call, but happily updates the UI after a .reverse() call - I've been banging my head against this problem for days - can't seem to find an answer... help!
Container View Model - implements the observableArray Property :
function DataTextsKOViewModel( jsonTexts ) {
var self = this;
// Texts array
self.texts = ko.observableArray();
// Build from json data array
for (var i = 0; i < jsonTexts.AuthorityTexts.length; i++) {
var jsontext = jsonTexts.AuthorityTexts[i];
// Push Text VModel objects onto the KO observable array list ..
self.texts.push(
new DataTextKOViewModel( jsontext )
);
}
}
Array Object Model - These are the objects which are sorted:
// Single data text view model
function DataTextKOViewModel(jsonText) {
// Other properties omitted for brevity
this.ListOrder = ko.observable(jsonText.ListOrder);
}
Custom Sort Function :
function textListOrderCompare(l, r) {
// Ascending sort
var retval = ((l.ListOrder() == r.ListOrder()) ? 0
: ((l.ListOrder() > r.ListOrder()) ? 1 : -1));
return retval;
}
Binding Markup :
<!-- ko foreach: texts -->
<div class="group" data-bind="attr:{'id': clientId() }">
<h3 data-bind="attr:{'dataKey': responsibilityId() }">
<span data-bind="text: responsibilitySectionHeader"></span>
</h3>
<div>
<!-- section content goes here -->
</div>
</div>
When User Elects to sort:
myDataTextsKOViewModel.rollbackChanges();
dataTextsViewModel.texts.sort(textListOrderCompare);
// UI Never updates - but I've checked console output to ensure that the array
/ /sorts correctly
dataTextsViewModel.texts.sort(textListOrderCompare);
// UI updates with items in reverse order
Any help you can give me would be most appreciated.
EDIT: Should have mentioned - I already call valueHasMutated() on the observable array property after the .sort()! - Andrew
Try to call valueHasMutated function after sorting:
dataTextsViewModel.texts.sort(textListOrderCompare);
dataTextsViewModel.texts.valueHasMutated();
dataTextsViewModel.texts(dataTextsViewModel.texts().sort(textListOrderCompare));
this way you change observable. Other way was changing the array inside and didn't trigger the change.