Sveltekit how do I set checkboxes checked based on query params? - javascript

I am trying to set checkboxes active based on query parameters using Sveltekit. To achieve this, I tried to get all route query parameters using the 'url.searchParams.entries()' method to get all query parameters from the URL.
Then I am trying to loop through all the params and then loop through all posts and check if the value of the query param is equal to a value in posts. If so, it should set checked to true forEach checkbox that meets the value. Then in my HTML, I bind the checked variable to my checkbox. But the issue is that when I put in 4 in the query params (or select 4 using the checkbox), it checks all the checkboxes but, if I select any of the other checkboxes, it doesn't do a single thing (besides from querying data from the JSON placeholder api)
This is my HTML:
<div class="grid grid-cols-2">
{#each posts as post}
<div class="col-span-1">
<input type="checkbox" bind:checked={checked} value={post.id} bind:group=
{selectedPosts} on:change="{() => updateValue(post.id)}" class="mr-2">
</div>
<div class="col-span-1">
<label for="{post.id}">{post.id}</label>
</div>
{/each}
</div>
This is inside my script tag:
import { filter, results } from '../../stores.js';
import { page } from '$app/stores';
let checked = false
let selectedPosts = []
const posts = [
{
name: 'post',
id:'1'
},
{
name: 'post',
id: '2'
},
{
name: 'post',
id: '3'
},
{
name: 'post',
id: '4'
}
]
$: {
//If the query params would be '?posts=1', the output would be ['posts', '1']
let params = [...$page.url.searchParams.entries()]
params.forEach(param => {
//Returns the first param. In this case '1'
let value = param[1]
posts.forEach(post => {
if(post.id === value){
console.log('true')
checked = true
} else {
checked = false
console.log('false')
}
})
});
}
async function updateValue(value) {
filter.set(value)
}
In my store I set the query params like so:
export const results = derived(filter, (value) => {
if(!value) return {};
const data = fetch(`https://jsonplaceholder.typicode.com/todos/${value}`).then(res => {
return res.json()
})
return data
});
filter.subscribe(value => {
if(!value) return
goto(`/activiteiten/filter?post=${value.toString()}`)
return value
})
And finally I return with a derived store some data from the JSON placeholder API.

You don't need bind:checked={checked} when using bind:group. I think this is the root of your problem. You are setting the same checked variable for every item.
if(post.id === value){
console.log('true')
checked = true
} else {
checked = false
console.log('false')
}
When you later check based on this variable, you get unexpected behavior.

Related

Process multiple request queries

I have an API that serves JSON data. Currently if you do api/weapons for example it gives you all the weapons available, api/weapons/weaponName gives information about that specific weapon. What I want to do is be able to api/weapons?type=sword&rarity=5 for example. I managed to pull of api/weapons?type=sword and api/weapons?rarity=5 on their own but not together.
Here's what I'm currently doing:
let filtered = [];
if (query.type) {
filtered = filtered.concat((await weapons).filter(w => formatName(w.weaponType) === formatName(query.type)));
}
if (query.rarity) {
filtered = filtered.concat((await weapons).filter(w => w.rarity == query.rarity));
}
if (!filtered.length) filtered = [await weapons]
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
formatName is just a function that makes the string all lowercase and trims it and removes all spaces.
If we take api/weapons?type=sword&rarity=5
I think what's happening right now is:
It is getting all the weapons with the type "sword"
It is getting all the weapons with the rarity "5"
It is joining all the results together, so all the weapons with the type sword (regardless of rarity) and al the weapons with the rarity 5 (regardless of type).
I want it to filter weapons with ONLY that rarity AND ONLY that type. So only 5 rarity swords for example. What is the most beneficial way of handling this
I'd suggest retrieving "weapons" once and then running any filters on them without concatenating the results:
let filtered = [ ...(await weapons) ];
if (query.type) {
filtered = filtered.filter(w => w => formatName(w.weaponType) === formatName(query.type));
}
if (query.rarity) {
filtered = filtered.filter(w => w.rarity == query.rarity);
}
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
Your current logic is testing whether one constraint OR another matches, what you actually need to do is to do an AND, which means you must perform the test in a single pass of filter.
I would slightly modify your code so that you compare all constraints that you're sending...you could further modify the logic below to accept a logical operator to test whether the rarity is >= or <= to a certain number for example.
const weapons = [{
type: 'sword',
name: 'swift blade of zek',
rarity: 5
},
{
type: 'mace',
name: 'hammer of kromzek kings',
rarity: 1
},
{
type: 'sword',
name: 'split blade of thunder',
rarity: 2
},
{
type: 'sword',
name: 'blade of carnage',
rarity: 5
},
]
const getWeapons = (query = {}) => {
let filtered = [];
let constraints = [];
// We could build this object dynamically but I just wanted
// to demonstrate it using your current approach
if (query.hasOwnProperty('type')) {
constraints.push({
name: 'type',
value: query.type
})
}
if (query.hasOwnProperty('rarity')) {
constraints.push({
name: 'rarity',
value: query.rarity
})
}
// Compare all of the conditions and only return weapons
// that match all of the conditions passed.
filtered = weapons.filter(w => {
let matches = 0
constraints.forEach(c => {
if (w[c.name] === c.value) {
matches += 1
}
})
// ensures we only return complete matches
return matches === constraints.length
});
return filtered
}
console.log(getWeapons({
type: 'sword',
rarity: 5
}))
Create an object which has the same property keys as the filters you want to use. Assign a function to each property where the evaluation for that specific filter is specified.
const filters = {
type: (weapon, type) => formatName(weapon.weaponType) === formatName(type),
rarity: (weapon, rarity) => weapon.rarity === rarity,
};
Then loop over the weapons with filter. Inside the filter loop, loop over the keys of the query variable with the every method. This method will return true or false based on if every evaluation is true or not.
In the every loop, use the keys of the query to select the filter from the filters list. Pass the weapon and the values of the query object to these filter functions and return result.
By doing this you can use one, two or no filters at all. And any new filters can be added in the filters object.
const filteredWeapons = weapons.filter((weapon) =>
Object.keys(query).every((filterKey) => {
if (!(filterKey in filters)) {
return false;
}
const filter = filters[filterKey]
const value = query[filterKey]
return filter(weapon, value);
})
);
res.status(HttpStatusCodes.ACCEPTED).send(filteredWeapons);

how to update array of objects when input is changed

I have an array of objects
const data= [
{ id:1,name:"apple", city:"ban" },
{ id:2,name:"mango", city:"hyd" }
]
And I have two input fields when the user fills. if !null then I need to add an object to "data" {id:2,name:"apple",city:"nel"} and the id of the 2 in the data object should be 3 and if again the user makes it to null ie deleted the value from the input field need to remove this {id:2,name:"apple",city:"nel"} and make the id from 3 to 2. similarly of the user gives 2 inputs i need to add 2times {id:2,name:"apple",city:"nel"},{id:3,name:"apple",city:"nel"} and make the id of 2 to 4. similarly if the user deletes eaither/or deletes both or single data from the input i need to add or remove the data array accordingly can anyone please help me with this in react with hooks
You can track the form values with a single useState() hook.
You can use both input onChange event handlers, and form onSubmit event handler to build a component that behaves like you want.
The following is an example React component with hooks you can take as a starting point to manage your state. Please take it as a template and tweak it as needed to fulfill your actual requirement.
const storedData = [
{ id: 1, name: 'apple', city: 'ban' },
{ id: 2, name: 'mango', city: 'hyd' },
]
let nextId = 3
const MyFormComponent = () => {
const initialFormData = { name: '', city: '' }
const [formData, setFormData] = useState(initialFormData)
const clearFormData = () => {
setFormData(initialFormData)
}
const handleOnInputChange = (event) => {
const { value, name } = event.target
// merge previous formData with the new value from input
setFormData({
...formData,
[name]: value,
})
}
const handleOnSubmit = (event) => {
// prevent HTML form default handler (navigation, etc.)
event.preventDefault()
const { name, city } = formData
// OPTIONAL: handle form data validations
if (name === '') {
// no "name" provided
alert('Must specify a name!')
return
}
// input1 "name" and input2 "city" are available in formData
// TODO handle the form data and implement your application logic / update storedData / etc.
// TODO a rough example below, please tweak it to match your requirements
const existingEntryByIndex = storedData.findIndex(
({ name }) => formData.name === name
)
if (existingEntryByIndex >= 0 && formData.city === '') {
// name exists and city value is empty => delete this entry
// NOTE: city value can't be null, because it will be always a string. Maybe, you can use "null" string though.
storedData.splice(existingEntryByIndex, 1)
} else {
// name exists and city not empty, or name is new => add a new city
storedData.push({ id: nextId, name, city })
nextId++
}
// --- end of rough example ---
// OPTIONAL: clear the form values ->
clearFormData()
}
return (
<div className={'my-form-container'}>
<form onSubmit={handleOnSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleOnInputChange}
/>
<input
type="text"
name="city"
value={formData.city}
onChange={handleOnInputChange}
/>
</form>
</div>
)
For further reference, you can check the React docs to learn about more techniques and ways to handle form inputs, and events.

Filtering through an array after a GET method

guys can you explain why is it not working?
I am GETTING an array from my backend rails API which gives me the data correctly.
Now i made an empty array where i filter my records based on their ID.
It does it correctly ,but whenever i refresh my page my filterRecords methods is not calling it just gives me an empty array.
Here i am looping through that array and writing out the description in my filteredRecords:
<h1
class="content"
v-for="(record, index) of filteredRecords"
:key="index"
:record="record"
:class="{ 'is-active': index === activeSpan }"
>
<strong>{{ record.description }} </strong>
</h1>
This is my filterRecords() in computed:
computed: {
...mapGetters({
id: "id"
}),
filteredRecords() {
return this.records.filter(record => {
return record.template_id === this.id;
});
}
},
this is how i am getting the data from an API:
created() {
if (!localStorage.signedIn) {
this.$router.replace("/");
} else {
this.$http.secured
.get("/api/v1/records")
.then(response => {
this.records = response.data;
})
.catch(error => this.setError(error, "Something went wrong"));
}
},
So how do i make this work so that when i reload my page it gets the records and after that it filters through it ( calls the filteredRecords computedmethod)
Thank you!
EDIT
hello problem was that i used return record.template_id === this.id;
and it should be just return record.template_id == this.id;
since they are different data types.
Try the below code once
computed: {
...mapGetters({
id: "id"
}),
filteredRecords: function () {
let localId = this.id;
let filtered = this.records.filter(record => {
return record.template_id == localId;
});
console.log(localId);
console.log(filtered);
return filtered;
}
},
If this does not work, can you log 'this.id' and see what is getting logged.
It happens because overwriting values of array are not reactive. So Vue doesn't detect the change and doesn't call your computed property.
Try this:
this.records.splice(0, this.records.length - 1, ...response.data)
More about this you can read here: Reactivity in Depth
.splice will work because Vue wraps this method and do some magic under the hood
Edit 1
for(let i = 0; i < response.data - 1; i++) {
Vue.set(this.records, i, response.data[i]);
}
Edit 2
After discussion via chat we figured out that there is a second problem with types (id of the element returned from backend is a string and localId is a number so .filter will return an empty array). The solution is to use == over ===, but I would suggest to use:
record.template_id.toString() === this.id.toString()
Then we will be sure that both id's are strings

ReactJS: Updating array inside object state doesn't trigger re-render

I have a react hooks function that has a state object apiDATA. In this state I store an object of structure:
{
name : "MainData", description: "MainData description", id: 6, items: [
{key: "key-1", name : "Frontend-Test", description: "Only used for front end testing", values: ["awd","asd","xad","asdf", "awdr"]},
{key: "key-2", name : "name-2", description: "qleqle", values: ["bbb","aaa","sss","ccc"]},
...
]
}
My front end displays the main data form the object as the headers and then I map each item in items. For each of these items I need to display the valuesand make them editable. I attached a picture below.
Now as you can see I have a plus button that I use to add new values. I'm using a modal for that and when I call the function to update state it does it fine and re-renders properly. Now for each of the words in the valuesI have that chip with the delete button on their side. And the delete function for that button is as follows:
const deleteItemFromConfig = (word, item) => {
const index = apiDATA.items.findIndex((x) => x.key === item.key);
let newValues = item.value.filter((keyWord) => keyWord !== word);
item.value = [...newValues];
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
apiDATA.items = [...apiDataItems];
setApiDATA(apiDATA);
}
});
};
Unfortunately this function does not re-render when I update state. And it only re-renders when I update some other state. I know the code is a bit crappy but I tried a few things to make it re-render and I can't get around it. I know it has something to do with React not seeing this as a proper update so it doesn't re-render but I have no idea why.
It is not updating because you are changing the array items inside apiDATA, and React only re-render if the pointer to apiDATA changes. React does not compare all items inside the apiDATA.
You have to create a new apiDATA to make React updates.
Try this:
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
setApiDATA(prevState => {
return {
...prevState,
items: apiDataItems
}
});
}
Using splice isn't a good idea, since it mutates the arrays in place and even if you create a copy via let apiDataItems = [...apiDATA.items];, it's still a shallow copy that has original reference to the nested values.
One of the options is to update your data with map:
const deleteItemFromConfig = (word, item) => {
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
const items = apiDATA.items.map(it => {
if (it.key === item.key) {
return {
...item,
values: item.value.filter((keyWord) => keyWord !== word)
}
}
return item;
})
setApiDATA(apiData => ({...apiData, items});
}
});
}

VueJS components prop execution order

I'm using vuejs and vue-apollo in my project.
I have to components : GroupList and GroupForm
The thing is that I first arrive on the list, and then when I click on one of the groups, I arrive in the form view of that group.
To do that, I have a button that opens the form view by giving the id of that group :
openGroupFormView (group, index) {
this.$router.push({ name: 'GroupFormView', params: { groupId: group.id } })
}
In the past, I used to pass the group object as paramater and it worked but I changed my mind and now I want to pass the id so that when the component GroupForm is opened, the graphql query to retrieve this group based on the id is executed. I want to do that in order to avoid apollo-cache inconsistencies in case where someone edited the same group in the meantime.
So in my GroupForm component I have :
My prop :
props: {
groupId: Number
}
My component query :
apollo: {
group: {
query: GROUP_QUERY,
variables () {
return {
id: this.groupId
}
},
..
}
The associated query beneath that :
const GROUP_QUERY = gql`
query ($id: ID) {
group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
My problem is that in data () of my component, I used to use the group that I passed through the props as an object. Now, I still want to do things on the group but the apollo part is executed after the data () part so I have an undefined problem. How could I ensure that this problem doesn't occur ? I mean that apollo query is run first.
My data() block :
data () {
return {
form: {
name: this.group === undefined ? '' : this.group.name
}
}
},
Thanks in advance for your help !
EDIT : A part of my template
<b-form>
<b-form-group label="Name">
<b-form-input
type="text"
v-model="form.name"
required
:disabled="!editing"/>
</b-form-group>
<div v-if="editing" class="mb-3">
<b-button #click="cancelEdit()">Cancel</b-button>
<b-button #click="group === undefined ? createGroup() : updateGroup(group)" variant="success">Save</b-button>
</div>
</b-form>
I don't use Apollo but it looks like you should be able to initialize name to an empty string and then use the result hook to set it when the query completes.
Untested:
apollo: {
group: {
...
result({ data, loading, networkStatus }) {
this.form.name = data.name // something like this
}
}
}

Categories

Resources