The first chunk as an action creator below works as is with thunk, but I want to also apply the 2nd chunk, which is a promise middleware. How do I tweak it so that it can dispatch 2 actions?
export const fetchPokemon = function (pokemonName) {
return function (dispatch) {
dispatch({type: 'REQUESTING'})
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`
return fetch(requestURL)
.then(function (response) {
return response.json()
})
.then(function (data) {
dispatch(receivePokemon(formatPokemonData(data)))
dispatch(fetchPokemonDescription(pokemonName))
})
}
}
middleware
const fetchPromiseMiddleware = store => next => action => {
if (typeof action.then !== 'function') {
return next(action)
}
return Promise.resolve(action).then(function (res) {
if (res.status >= 400) {
throw new Error("Bad response from server")
}
return res.json()
}).then(store.dispatch)
}
I've tried the below but get an error:
store.js:33 Uncaught (in promise) TypeError: (0 ,
_actionCreators.receivePokemon) is not a function
const fetchPromiseMiddleware = store => next => action => {
if (typeof action.then !== 'function') {
return next(action)
}
return Promise.resolve(action).then(function (res) {
if (res.status >= 400) {
throw new Error("Bad response from server")
}
return res.json()
}).then(function (data) {
return store.dispatch(receivePokemon(formatPokemonData(data)))
}).then(function (data) {
return store.dispatch(fetchPokemonDescription(data.name))
})
}
there's not enough code in your question, but it seems when you call receivePokemon(formatPokemonData(data)) in the code you show, receivePokemon isn't a function, now you need to check where is that defined, it probably isn't.
Related
I made a function to get request. It look like this
export const toggleCompleted = (id) => {
axiosMethod.get('api/taks/toggle/' + id)
.then(res => {
console.log(res.data)
return res.data;
}).catch(error => {
return error;
})
return 'Test';
}
I want to get this request and if PHP return true, run dispatch. So I made this code
const markAsCompleted = (id) => {
console.log(toggleCompleted(id));
if (toggleCompleted(id) == 1){
toggleMarkAsCompleted(id);
}
}
toggleCompleted is a my request which is show before
toggleMarkAsCompletedis my dispatch.
If toggleCompleted return 1 I want to run my dispatch. It's simple? Interested is that this code
console.log(toggleCompleted(id));
return Test while my request 1 or 0 (from .then()). Why?
Add return in the toggleCompleted and use async/await to get return data
export const toggleCompleted = (id) => {
return axiosMethod
.get("api/taks/toggle/" + id)
.then((res) => {
console.log(res.data);
return res.data;
})
.catch((error) => {
return error;
});
};
const markAsCompleted = async (id) => {
const res = await toggleCompleted(id);
if (res == 1) {
toggleMarkAsCompleted(id);
}
};
i want to do http request using fetch(). The request is more than one that from same domain (it's just different endpoint).
https://api.banghasan.com/quran/format/json/cari/${keyword}/bahasa/id/mulai/0/limit/100
https://api.banghasan.com/quran/format/json/surat/${chapter}/ayat/${verse}
https://api.banghasan.com/quran/format/json/catatan/${num}
I made my code like this:
Get the first data(number of chapter and verse):
static getData(keyword) {
return fetch(`https://api.banghasan.com/quran/format/json/cari/${keyword}/bahasa/id/mulai/0/limit/100`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.reject(`The "${keyword}" keyword is not found`);
} else {
return Promise.reject(`Something Wrong`)
}
})
.catch(error => {
return Promise.reject(error);
})
}
If getData return `resolve', there are the number of chapter and verses
Then, get the verses:
static async getAyat(surat, ayat) {
return fetch(`https://api.banghasan.com/quran/format/json/surat/${surat}/ayat/${ayat}`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.resolve(rj.ayat.data);
} else {
return Promise.reject('Terjadi kesalahan')
}
})
.catch(error => {
return Promise.reject(error);
})
}
Last, get Notes, if the verse has something to explain
static getNote(num) {
return fetch(`https://api.banghasan.com/quran/format/json/catatan/${num}`)
.then(resolve => {
return resolve.json()
})
.then(rj => {
if (rj.status == 'ok') {
return Promise.resolve(rj.catatan.teks)
} else {
return Promise.reject('Terjadi kesalahan')
}
})
.catch(error => {
return Promise.reject(error);
})
}
The code is works properly. I just wanna know, is there simple way to write it?
Use a function. Ask yourself which parts are the same versus what is different, then take the parts that are different and make them parameters.
In your case, here's what's different:
The arguments to the function
The URL generation
The data you extract from the response
So here's how you can create a function to encapsulate those differences:
const baseUrl = 'https://api.banghasan.com/quran/format/json';
const returnAllData = data => data;
function createFetchMethod(urlBuilder, dataExtractor = returnAllData) {
return async (...params) => {
const url = urlBuilder(...params);
const response = await fetch(`${baseUrl}${url}`);
if (!response.ok) {
throw new Error("Something's wrong");
}
const json = await response.json();
if (json.status !== 'ok') {
throw new Error("Something's wrong");
}
return dataExtractor(json);
}
}
The way you'd use this is to create your methods like this:
const getData = createFetchMethod(
keyword => `/cari/${keyword}/bahasa/id/mulai/0/limit/100`
);
const getAyat = createFetchMethod(
(surat, ayat) => `/surat/${surat}/ayat/${ayat}`,
json => json.ayat.data
);
const getNote = createFetchMethod(
num => `/catatan/${num}`,
json => json.catatan.teks
);
These can now be called as before, only all the error handling is encapsulated. You can further customize by adding more parameters.
Note that one potential problem with your URL building code is if the parameters being injected aren't URL-safe, you need to call encodeURIComponent for them to escape special characters.
I keep getting this error in the console log
Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'ServiceWorkerGlobalScope': Cannot construct a Request with a Request object that has already been used.
I tried changing my service worker but it doesn't work
self.addEventListener('install', (event) => event.waitUntil(preLoad()));
const preLoad = function () {
return caches.open('cc-offline').then((cache) => {
return cache.addAll(['/offline.html', '/index.html']);
});
}
self.addEventListener('fetch', (event) => {
event.respondWith(checkResponse(event.request).catch(function () {
return returnFromCache(event.request)
}));
event.waitUntil(addToCache(event.request));
});
const checkResponse = (request) => {
return new Promise((fulfill, reject) => {
fetch(request).then((response) => {
(response.status !== 404) ? fulfill(response) : reject()
}, reject)
});
};
const addToCache = (request) => {
return caches.open('cc-offline').then((cache) => {
return fetch(request).then((response) => {
return cache.put(request, response);
});
});
};
const returnFromCache = (request) => {
return caches.open('cc-offline').then((cache) => {
return cache.match(request).then((matching) => {
return (!matching || matching.status == 404) ? cache.match('offline.html') : matching
});
});
};
fetch don't allow you to use a request twice, at least at current version :). Using the same request object in both checkResponse and addToCache maybe the case. You can try to clone the request object before calling fetch as mention in here Why does this code fail to execute 'fetch'?
I made a Storage.js file with some Utils function. In most case, I use it in this way:
async someFunction()=>{ let foo = await getOnDevice(someName)}
But the in my case, I have a problem with the syntax
import {AsyncStorage} from 'react-native'
export const saveOnDevice = async (name, data) => {
try {
if (data !== null && data !== undefined) {
await AsyncStorage.setItem(name, JSON.stringify(data));
}
} catch (error) {
console.log(error)
}
};
export const getOnDevice = async (name) => {
try {
const data = await AsyncStorage.getItem(name);
if (data !== null && data !== undefined) {
return data
}
} catch (error) {
console.log(error)
}
};
How can I use it without declaring an async function?
import {saveOnDevice} from '../../utils/Storage'
export function fetchUrlWithRedux(url) {
return (dispatch) => {
dispatch(fetchUrlRequest(url));
return fetchUrl(url, dispatch).then(([response, json]) => {
if (response.status === 200) {
saveOnDevice('url', json.data.host);
dispatch(fetchUrlSuccess(json))
}
else {
dispatch(fetchUrlError())
}
}).catch(() => dispatch(fetchUrlError()))
}
}
What is wrong with my code?
If you don't want to use async/await in your main file then you can use promise, like below.
saveOnDevice('url', json.data.host).then(() => {
dispatch(fetchUrlSuccess(json))
})
I think you need to add async before the anonymous function passed to .then(), and await before the call to saveOnDevice():
import {saveOnDevice} from '../../utils/Storage'
export function fetchUrlWithRedux(url) {
return (dispatch) => {
dispatch(fetchUrlRequest(url));
return fetchUrl(url, dispatch).then(async ([response, json]) => {
if (response.status === 200) {
await saveOnDevice('url', json.data.host);
dispatch(fetchUrlSuccess(json))
}
else {
dispatch(fetchUrlError())
}
}).catch(() => dispatch(fetchUrlError()))
}
}
I want to pass in a boolean value as the 2nd argument to my actionCreator which would determine what my middleware dispatches, but how do I give my middleware access to this 2nd argument?
Do I have to dispatch an array or object instead of a promise?
export const fetchPokemon = function (pokemonName, booleanValue) {
return function (dispatch) {
dispatch({type: 'REQUESTING'})
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`
dispatch(fetch(requestURL))
}
}
Middleware
const fetchPromiseMiddleware = store => next => action => {
if (typeof action.then !== 'function') {
return next(action)
}
...
return response.json()
}).then(function (data) {
if booleanValue {
store.dispatch(receivePokemon(formatPokemonData(data)))
} else {
store.dispatch(fetchPokemonDescription(data.name))
}
})
}
it seems you have answered yourself, the action you dispatch should contain all the relevant data.
The simplest option seem to be to add a property (or properties) to your action, as a Promise is already an object.
export const fetchPokemon = function (pokemonName, booleanValue) {
return function (dispatch) {
dispatch({type: 'REQUESTING'})
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`
dispatch(Object.assign(fetch(requestURL), {
someNameForYourBooleanParameter: booleanValue
})
}
}
and
const fetchPromiseMiddleware = store => next => action => {
if (typeof action.then !== 'function') {
return next(action)
}
...
return response.json()
}).then(function (data) {
if (action.someNameForYourBooleanParameter) {
store.dispatch(receivePokemon(formatPokemonData(data)))
} else {
store.dispatch(fetchPokemonDescription(data.name))
}
})
}
If you want to continue this path, I'd recommend to put these values under a .payload property to prevent any collision with members of the Promise class
I'd take this approach further to avoid the multiple actions being dispatched for the same logical action:
export const fetchPokemon = function (pokemonName, booleanValue) {
return function (dispatch) {
const requestURL = `http://pokeapi.co/api/v2/pokemon/${pokemonName}/`;
dispatch({
type: 'REQUESTING',
promise: fetch(requestURL),
payload: {
someNameForYourBooleanParameter: booleanValue
}
})
}
}