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
}
})
}
}
Related
I'm trying to override a method of an object returned from an async function but get this error;
dataProvider.getList is not a function
i tried to extend the object simply in this way but obviously is not correct
function App() {
const [dataProvider, setDataProvider] = useState(null);
useEffect(() => {
const buildDataProvider = async () => {
//this must to be called with await
const dataProvider = await buildHasuraProvider({
clientOptions: { uri: 'http://localhost:8080/v1/graphql' }
});
//this object should be the same as above but with an overridend method (getList)
const extendeDataProvider = {
...dataProvider,
getList: (resource, params) => {
if (resource === 'apikeys') {
//implementation missing
} else {
// fallback to the default implementation
return dataProvider.getList(resource, params);
}
}
}
setDataProvider(() => extendeDataProvider);
};
buildDataProvider();
}, []);
if (!dataProvider) return <p>Loading...</p>;
return (<Admin dataProvider={dataProvider} />)
}
export default App;
i asume that the issue is with your fallback implementation in this line:
// fallback to the default implementation
return dataProvider.getList(resource, params);
u can also prove that by logging it this way -
console.log(typeof(dataProvider.getList))
I have a function that refreshes the data of my component when the function is called. At this moment it only works for one component at a time. But I want to refresh two components at once. This is my refresh function:
fetchDataByName = name => {
const { retrievedData } = this.state;
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data;
this._isMounted && this.setState({ retrievedData });
});
};
My function is called like this:
refresh("meetingTypes");
As it it passed as props to my component:
return (
<Component
{...retrievedData}
{...componentProps}
refresh={this.fetchDataByName}
/>
);
I tried passing multiple component names as an array like this:
const args = ['meetingTypes', 'exampleMeetingTypes'];
refresh(args);
And then check in my fetchDataByName function if name is an array and loop through the array to fetch the data. But then the function is still executed after each other instead of at the same time. So my question is:
What would be the best way to implement this that it seems like the
function is executed at once instead of first refreshing meetingTypes
and then exampleMeetingTypes?
Should I use async/await or are there better options?
The fetchData function:
fetchData = (fetch, callback) => {
const { componentProps } = this.props;
let { route, params = [] } = fetch;
let fetchData = true;
// if fetcher url contains params and the param can be found
// in the component props, they should be replaced.
_.each(params, param => {
if (componentProps[param]) {
route = route.replace(`:${param}`, componentProps[param]);
} else {
fetchData = false; // don't fetch data for this entry as the params are not given
}
});
if (fetchData) {
axios
.get(route)
.then(({ data }) => {
if (this.isMounted) {
callback(null, data);
}
})
.catch(error => {
if (error.response.status == 403) {
this._isMounted && this.setState({ errorCode: 403 });
setMessage({
text: "Unauthorized",
type: "error"
});
}
if (error.response.status == 401) {
this._isMounted && this.setState({ errorCode: 401 });
window.location.href = "/login";
}
if (error.response.status != 403) {
console.error("Your backend is failing.", error);
}
callback(error, null);
});
} else {
callback(null, null);
}
};
I assume fetchData works asynchronously (ajax or similar). To refresh two aspects of the data in parallel, simply make two calls instead of one:
refresh("meetingTypes");
refresh("exampleMeetingTypes");
The two ajax calls or whatever will run in parallel, each updating the component when it finishes. But: See the "Side Note" below, there's a problem with fetchDataByName.
If you want to avoid updating the component twice, you'll have to update fetchDataByName to either accept multiple names or to return a promise of the result (or similar) rather than updating the component directly, so the caller can do multiple calls and wait for both results before doing the update.
Side note: This aspect of fetchDataByName looks suspect:
fetchDataByName = name => {
const { retrievedData } = this.state; // <=============================
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data; // <=============================
this._isMounted && this.setState({ retrievedData });
});
};
Two problems with that:
It updates an object stored in your state directly, which is something you must never do with React.
It replaces the entire retrievedData object with one that may well be stale.
Instead:
fetchDataByName = name => {
// *** No `retrievedData` here
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
if (this._isMounted) { // ***
this.setState(({retrievedData}) => ( // ***
{ retrievedData: {...retrievedData, [name]: data} } // ***
); // ***
} // ***
});
};
That removes the in-place mutation of the object with spread, and uses an up-to-date version of retrievedData by using the callback version of setState.
I am trying to implement subscriptions with redux-logic middleware.
The idea is following: when data is fetched from server, to call callback for each subscriber passing fetched data as arguments.
// logic/subscriptions.js
const fetchLatestLogic = createLogic({
type: FETCH_LATEST_DATA,
latest: true,
process({getState, action}, dispatch, done) {
const {seriesType, nextUpdateTime} = action.payload;
const callbacks = getState()[seriesType][nextUpdateTime].callbacks
apiFetch(seriesType)
.then(data => {
callbacks.forEach(callback => callback(seriesType, data));
done()
})
}
})
const subscribeLogic = createLogic({
type: SUBSCRIPTIONS_SUBSCRIBE,
cancelType: SUBSCRIPTIONS_REMOVE,
process({getState, action, cancelled$}, dispatch) {
const {seriesType, nextUpdateTime, updateInterval, subscriberId, callback} = action.payload;
const interval = setInterval(() => {
dispatch(fetchLatestData(seriesType, nextUpdateTime))
}, updateInterval);
cancelled$.subscribe(() => {
clearInterval(interval)
})
}
})
// reducers/subscriptions.js
import update from 'immutability-helper';
update.extend('$autoArray', (value, object) => (object ? update(object, value) : update([], value)));
const initialState = {
'SERIESTYPE1': {}
'SERIESTYPE2': {}
}
// state modifications using 'immutable-helpers'
const serieAddSubscriberForTime = (seriesSubscriptions, time, subscriber) =>
update(seriesSubscriptions, {
[time]: {
$autoArray: {
$push: [subscriber]
}
}
});
// state modifications using 'immutable-helpers'
const serieRemoveSubscriberForTime = (seriesSubscriptions, subscriptionTime, subscriber) => {
const subscriptions = seriesSubscriptions[subscriptionTime].filter(s => s.subscriberId !== subscriber.subscriberId);
if (subscriptions.length === 0) {
return update(seriesSubscriptions, { $unset: [subscriptionTime] });
}
return { ...seriesSubscriptions, ...{ [subscriptionTime]: subscriptions }
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case SUBSCRIPTIONS_SUBSCRIBE: {
const { seriesType, nextUpdateTime, subscriber} = action.payload;
const newSubscriptionAdded = serieAddSubscriberForTime(state[seriesType], nextUpdateTime, subscriber);
const oldSubscriptionRemoved = serieRemoveSubscriberForTime(state[seriesType], nextUpdateTime, subscriber);
return update(state, { [seriesType]: { ...oldSubscriptionRemoved, ...newSubscriptionAdded } });
}
default:
return state;
}
}
How would it be possible to cancel running interval for given subscriber only? *(Without dispatching intervalID to reducer and saving it in state?)
Because by just dispatching action
cancelType: SUBSCRIPTIONS_REMOVE
will remove all intervals for all subscriptions with my current implementation.
UPDATE: actually there is much more smooth way to do the cancellation logic.
cancelled$
is an observable, and the RxJS .subscribe() accepts three functions as arguments:
[onNext] (Function): Function to invoke for each element in the observable sequence.
[onError] (Function): Function to invoke upon exceptional termination of the observable sequence.
[onCompleted] (Function): Function to invoke upon graceful termination of the observable sequence.
so the argument of onNext function is an emited value, and since in our case its the SUBSCRIPTIONS_REMOVE action, we can access its payload and do the cancellation depending on that payload:
cancelled$.subscribe(cancellAction => {
if (cancellAction.payload.subscriberId === subscriberId &&
cancellAction.payload.seriesType === seriesType) {
clearTimeout(runningTimeout);
}
})
const fetch = url => dispatch => {
// ...
}
export const fetchQuestions = tag => (dispatch) => {
return dispatch(fetch(tag));
};
What is dispatch in the fetch function ? url is a first and single parameter fetch function. But what is dispatch here ?
This is equivalent to one function returning another. I.e. this
const fetch = url => dispatch => {
// ...
}
is equivalent to
const fetch = function(url) {
return function(dispatch) {
// ...
}
}
Similarly this
export const fetchQuestions = tag => (dispatch) => {
return dispatch(fetch(tag));
};
is equivalent to
export const fetchQuestions = function(tag) {
return function(dispatch) {
return dispatch(fetch(tag));
}
};
dispatch is the first and single parameter of the function returned by the url => ... function. With normal function syntax, it would be
const fetch = function(url) {
return function(dispatch) {...}
}
fetch is a named function expression that takes a url parameter and returns a new function that takes a dispatch parameter.
You could rewrite using traditional function syntax:
const fetch = function (url) {
return function(dispatch) {
// ...
}
}
Its a shorter way of writing a function that returns another function. The arguments url and dispatch are arguments to the curryed function The ES5 equivalent of arrow function syntax would be
function fetch(url) {
return function(dispatch) {
....
}
}
or with Arrow syntax as
const fetch = (url) => {
return (dispatch) => {
// ...
}
}
Similarly you would have fetchQuestion written as
export function fetchQuestions(tag) {
return function(dispatch){
return dispatch(fetch(tag));
}
}
or with Arrow syntax as
export const fetchQuestions = (tag) => {
return (dispatch) => {
return dispatch(fetch(tag));
}
};
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.