ProfilePic.js
Due to useeffect on each component rendering API call happen.
How to solve this issue?
useEffect(() => {
dispatch(getImages())
}, [dispatch])
The API call is happening in ProfilePicSlice.js file below:
or how to add if condition in if condition, refer below code
ProfilePicSlice.js
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit'
import { IMAGE_API, ACCESS_KEY } from "../../app/utils/constant";
export const getImages = createAsyncThunk('imageList/images',
(_, { getState }) => {
const store = getState();
const topic = store.users[0].topic;
const otherTopic = store.users[0].otherTopic;
if(store === null){
console.log('api call')
}else{
console.log('no api call')
}
if(topic === 'Other'){
return fetch(`${IMAGE_API}${otherTopic}${ACCESS_KEY}`)
.then((res) => res.json());
}else{
return fetch(`${IMAGE_API}${topic}${ACCESS_KEY}`)
.then((res) => res.json());
}
})
const ProfilePicSlice = createSlice({
name: 'imageList',
initialState: {
images: [],
selectedImage: {},
loading: false,
},
extraReducers: (builder) => {
builder.addCase(getImages.fulfilled, (state, action) => {
state.loading = false;
state.images.push(action.payload);
console.log(action.payload)
})
},
reducers: {
selectedImage: (state, action) => {
state.selectedImage = action.payload;
},
removeImage: (state, action) => {
const itemId = action.payload;
state.images = state.images.filter((item) => item.id !== itemId);
}
},
});
export const { selectedImage, removeImage } = ProfilePicSlice.actions
export default ProfilePicSlice.reducer
Using an EventBus on my Laravel-Vuejs project. I'm emitting an items-updated event from ItemCreate component after the successful Item creation. On the same page I'm using ItemList component which shows a list of created Items
Here is the codes:
app.js file
require('./bootstrap');
window.Vue = require('vue');
window.EventBus = new Vue();
Vue.component('item-list',
require('./components/entities/item/ItemList'));
Vue.component('item-create',
require('./components/entities/item/ItemCreate'));
const app = new Vue({
el: '#app'
});
ItemCreate.vue Component
export default {
data: function () {
return {
itemName: ''
}
},
methods: {sendItemData: function () {
axios.post('/dashboard/item', {
name: this.itemName
})
.then(response => {
if (response.status === 201) {
toastr.success('Item created successfully!', {timeout: 2000});
EventBus.$emit('items-updated');
}
})
.catch(error => {
toastr.error(error, 'Ooops! Something went wrong!');
})
}
}
}
ItemList.vue Component
export default {
data: function () {
return {
items: [],
}
},
methods: {
getItems: function () {
axios.get('/dashboard/items')
.then(response => {
this.items = response.data;
})
.catch(error => {
toastr.error(error, 'Ooops! Something went wrong!');
})
}
},
mounted() {
this.getItems();
EventBus.$on('items-updated', function () {
this.getItems();
});
}
}
It was a general JS mistake. Working code:
on ItemList.vue Component
export default {
data: function () {
return {
items: [],
}
},
methods: {
getItems: function () {
axios.get('/dashboard/items')
.then(response => {
this.items = response.data;
})
.catch(error => {
toastr.error(error, 'Ooops! Something went wrong!');
})
}
},
mounted() {
this.getItems();
let vm = this;
EventBus.$on('items-updated', function () {
vm.getItems();
});
}
}
I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)
In the code below I want to get the form values from Edit form and sent using fetch on handleClick.
class GenerateButton extends Component {
handleClick = () => {
const { push, record, showNotification, values } = this.props;
const updatedRecord = { ...record, is_approved: true };
fetch(`api/reports/${record.id}`, { method: 'GET', body: updatedRecord })
.then((response) => {
return response.blob();
}).then(function(blob) {
console.log(blob);
})
.catch((e) => {
showNotification('Error: report generation failed.', 'warning')
});
}
render() {
return <RaisedButton label="Generate" onClick={this.handleClick} />;
}
}
I have an array of ids, and I created a mutation that allow me to delete an item using only 1 id. Is there any way to call this mutation multiple times using Relay.Store.commitUpdate or this.props.relay.commitUpdate ?
I think you can wrap each Relay.Store.commitUpdate in Promise:
commitMutationPromise = (Mutation, data) =>
new Promise((resolve, reject) => {
Relay.Store.commitUpdate(new Mutation(data), {
onSuccess: (transaction) => {
resolve(transaction);
},
onFailure: (transaction) => {
reject(transaction);
},
});
}
And commit your mutations as array of promises and catch result with Promise.all(but keep in mind its fail-fast behaviour).
It could be something like this:
handleDelete = (deleteIds) => {
const deletePromisesArray = [];
deleteIds.forEach(id => {
deletePromisesArray.push(
this.commitMutationPromise(DeleteMutation, { id })
);
});
Promise.all(deletePromisesArray).then(values => {
this.onSuccessDelete(result);
}, error => {
this.onFailDelete(error);
});
}