Hello everyone,
I’m trying to write some DRY Stimulus controllers but constantly run into a problem with fetch
— I cannot access this
.
Given this example:
export default class extends Controller {
static targets = [ 'country', 'regions' ]
connect(e) {
this.handle()
}
handle(e) {
const authenticityToken = document.querySelector('meta[name="csrf-token"]').content
const country_id = this.countryTarget.options[this.countryTarget.selectedIndex].value
const url = '/countries/' + country_id
fetch(url, {
credentials: 'include',
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': authenticityToken
}
})
.then(response => response.json())
.then(function(data) {
if (!data.regions) {
return
}
// Populate this.regionsTarget
})
}
}
What would I need to do to access this
? I’m not having luck using await fetch
as it throws a Babel compiler error in webpack.
converting to an arrow function should fix your issue
.then((data) => {
if (!data.regions) {
return
}
// Populate this.regionsTarget
})
What I’m learning is I need to really brush up on my JavaScript skills.
I ended up using both suggestions here and I broke the populating of the regions down into a separate function. I’m not sure if this is the cleanest approach — if this were pure Ruby I would prefer to make the populateRegions
function private
.
Here is my final result (note that the JSON response now has country
and regions
is a child of that object):
export default class extends Controller {
static targets = ['country', 'regions']
connect(e) {
this.handle()
}
handle(e) {
const authenticityToken = document.querySelector('meta[name="csrf-token"]').content
const country_id = this.countryTarget.options[this.countryTarget.selectedIndex].value
const url = '/countries/' + country_id + '.json'
fetch(url, {
credentials: 'include',
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': authenticityToken
}
})
.then(response => response.json())
.then(response => this.populateRegions(response.country))
}
populateRegions(country) {
const selected_value = this.regionsTarget.options[this.regionsTarget.selectedIndex].value
Array.from(this.regionsTarget.options).forEach(function(option) {
option.remove()
})
country.regions.forEach(function(region) {
const option = document.createElement('option')
option.text = region.name
option.value = region.id
if (option.value == selected_value) {
option.selected = true
}
this.regionsTarget.add(option)
}.bind(this))
}
}
tleish
December 17, 2021, 3:27pm
6
You can accomplish something similar as:
export default class extends Controller {
static targets = ['country', 'regions']
connect(e) {
this.handle()
}
handle(e) {
const authenticityToken = document.querySelector('meta[name="csrf-token"]').content
const country_id = this.countryTarget.options[this.countryTarget.selectedIndex].value
const url = '/countries/' + country_id + '.json'
fetch(url, {
credentials: 'include',
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': authenticityToken
}
})
.then(response => response.json())
.then(response => populateRegions(response.country, this.regionsTarget))
}
}
function populateRegions(country, target) {
const selected_value = target.options[target.selectedIndex].value
Array.from(target.options).forEach((option) => option.remove())
country.regions.forEach((region) => {
const option = document.createElement('option')
option.text = region.name
option.value = region.id
option.selected = (region.id == selected_value)
target.add(option)
})
}
That actually makes a lot of sense. Yep, a JavaScript brush-up is in order. Thanks!
I am by no means an expert (just started with Stimulus yesterday!) but here’s what worked for me:
static targets = ['output']
requestText() {
const ajax = new XMLHttpRequest()
ajax.onload = this.applyText(this)
ajax.open('GET', '/something/', true)
ajax.send()
}
// Where `instance` is the instance of the Controller class,
// and `response` is what is returned by your GET request.
applyText(instance) {
return function(response) {
instance.outputTarget.innerHTML = response.target.responseText
}
}
I’m using XMLHttpRequest
directly, but it should be similar with fetch
.
Basically, use a higher-order function and pass this
into it.