I'm creating a Reddit client on Redux and I have 2 store dispatches firing in the app:
// App()
const dispatch = useDispatch();
useEffect(() => {
const stateMatch = window.location.href.match(/state=([^&]*)/);
const codeMatch = window.location.href.match(/code=([^&]*)/);
if ((stateMatch && codeMatch) || localStorage.getItem("access_token")) {
dispatch(fetchUser());
dispatch(fetchSubs());
}
});
...
However, I want fetchUser() to run and finish before fetchSubs() can begin, as the former currently seems to ruin API calls for the latter while it's running. How can I solve this?
Since you are using createAsyncThunk you can do something like this:
dispatch(fetchUser())
.unwrap()
.then((user) => {
// do anything you want with user, or don't, also dispatch actions
dispatch(fetchSubs());
})
.catch((e) => {
// error in case of rejection inside createAsyncThunk second argument
console.log(e);
});
Explanation
Let's say const thunk = fetchUser()
so basically dispatch(fetchUser()) is the same as dispatch(thunk).
Redux's dispatch function returns whatever its argument (the action) returns.
So in this case, dispatch(thunk) returns whatever thunk returns.
thunk, based on how createAsyncThunk works, returns a promise that either resolves to fulfilled action, or the rejected action. (those actions that you receive in extra reducers).
This is how you can access those actions:
dispatch(thunk).then(fullfilledAction=>...).catch(rejectedAction=>...`
RTK library also provides a method called unwrap. Instead of those action objects I explained above, it lets you use the returned value from the 2nd argument of createAsyncThunk.
export const fetchUser = createAsyncThunk("user/fetchUser", async () => {
const user = await Reddit.getUser().then(val => {
return val;
});
return user; // unwrap lets you use this instead of action objects.
})
Try this with pure react and redux hooks
...
const state = useStore(yourStore) //use your method to read state
const dispatch = useDispatch();
const checkValue = () => {
const stateMatch = window.location.href.match(/state=([^&]*)/);
const codeMatch = window.location.href.match(/code=([^&]*)/);
if ((stateMatch && codeMatch) || localStorage.getItem("access_token")) {
return true;
}
return false;
}
useEffect(() => {
if(checkValue())
dispatch(fetchUser());
}
}, []);
useEffect(() => {
if(checkValue() && state.authState)
dispatch(fetchSubs());
}
}, [state.authState]);
...
Related
so I'm trying to be a clever-a$$ and return a promise from a hook (so I can await the value instead of waiting for the hook to give me the value after its resolved and the hook reruns). I'm attempting something like this, and everything is working until the resolve part. The .then doesnt ever seem to run, which tells me that the resolve I set isn't firing correctly. Here's the code:
function App() {
const { valPromise } = useSomeHook();
const [state, setState] = React.useState();
React.useEffect(() => {
valPromise.then(r => {
setState(r);
});
}, []);
if (!state) return 'not resolved yet';
return 'resolved: ' + state;
}
function useSomeHook() {
const [state, setState] = React.useState();
const resolve = React.useRef();
const valPromise = React.useRef(new Promise((res) => {
resolve.current = res;
}));
React.useEffect(() => {
getValTimeout({ setState });
}, []);
React.useEffect(() => {
if (!state) return;
resolve.current(state);
}, [state]);
return { valPromise: valPromise.current, state };
}
function getValTimeout({ setState }) {
setTimeout(() => {
setState('the val');
}, 1000);
}
and a working jsfiddle: https://jsfiddle.net/8a4oxse5/
I tried something similar (re-assigning the 'resolve' part of the promise constructor) with plain functions, and it seems to work:
let resolve;
function initPromise() {
return new Promise((res) => {
resolve = res;
});
}
function actionWithTimeout() {
setTimeout(() => {
resolve('the val');
}, 2000);
}
const promise = initPromise();
actionWithTimeout();
promise.then(console.log);
jsfiddle: https://jsfiddle.net/pa1xL025/
which makes me think something is happening with the useRef or with rendering.
** update **
so it looks like the useRefs are working fine. its the final call to 'res' (or resolve) that doesn't seem to fulfill the promise (promise stays pending). not sure if a reference (the one being returned from the hook) is breaking between renders or something
If you use this code the problem is gone:
const valPromise = React.useRef();
if (!valPromise.current) {
valPromise.current = new Promise((res) => {
resolve.current = res;
})
}
Normally you shouldn't write to ref during render but this case is ok.
Explanation
When you had this initially:
const valPromise = React.useRef(new Promise((res) => {
resolve.current = res;
}));
the promise here is actually recreated on each render and only the result from first render is used.
From the docs:
const playerRef = useRef(new VideoPlayer());
Although the result of new VideoPlayer() is only used for the initial
render, you’re still calling this function on every render. This can
be wasteful if it’s creating expensive objects.
So in your case that meant the resolve.current would be updated on each render.
But the valPromise remains the initial one.
Also since the expression passed to useRef runs during rendering one shouldn't do there anything that you would not do during rendering, including side effects - which writing to resolve.current was.
As Giorgi said, the useRef runs every render, but all results after the first run are discarded, which can cause the issue I was having above. So for those interested, I made the promise implementation into a standalone hook to abstract away the complexity:
this hook has been published to NPM if interested: https://www.npmjs.com/package/usepromisevalue
export function usePromiseValue() {
const resolve = React.useRef();
const promise = React.useRef(new Promise(_resolve => {
// - useRef is actually called on every render, but the
// subsequent result is discarded
// - however, this can cause the `resolve.current` to be overwritten
// which will make the initial promise unresolvable
// - this condition takes care of ensuring we always resolve the
// first promise
if (!resolve.current) resolve.current = _resolve;
}));
return {
promise: promise.current,
resolve: resolve.current,
};
}
(*** NOTE: see the bottom of this answer for the same hook but with the ability to update the promise/resolve combo so you can resolve multiple promises instead of just 1)
keep in mind, this will only resolve the promise 1 time. if you want the promise to react to state changes and be able to fire off another async operation and resolve a new promise, you'll need a more involved implementation that will return a new promise + resolve combo.
Usage; taking the code from my question above, but tweaking:
function useSomeHook() {
const [state, setState] = React.useState();
const { promise, resolve } = usePromiseValue();
React.useEffect(() => {
getValTimeout({ setState });
}, []);
React.useEffect(() => {
if (!state) return;
resolve(state);
}, [state]);
return {
valPromise: promise,
state,
};
}
*** UPDATE ***
here's the updated promise hook that will handle giving you a new promise/resolve combo when dependecies change:
function usePromiseValue({
deps = [],
promiseUpdateTimeout = 200,
} = {}) {
const [count, setCount] = React.useState(0);
const [mountRender, setMountRender] = React.useState(true);
const resolve = React.useRef();
const promise = React.useRef(new Promise(_resolve => {
// - useRef is actually called on every render, but the
// subsequent result is discarded
// - however, this can cause the `resolve.current` to be overwritten
// which will make the initial promise unresolvable
// - this condition takes care of ensuring we always resolve the
// first promise
if (!resolve.current) resolve.current = _resolve;
}));
React.useEffect(() => {
setMountRender(false);
}, []);
React.useEffect(() => {
// - dont run this hook on mount, otherwise
// the promise will update and not be resolveable
if (mountRender) return;
setTimeout(() => {
setCount(count + 1);
}, promiseUpdateTimeout);
// - dont update the promise/resolve combo right away,
// otherwise the current promise will not resolve
// - instead, wait a short period (100-300 ms after state updates)
// to give the current promise time to resolve before updating
}, [...deps]);
React.useEffect(() => {
// - dont run this hook on mount, otherwise
// the promise will update and not be resolveable
if (mountRender) return;
promise.current = new Promise(r => resolve.current = r);
}, [count]);
return {
promise: promise.current,
resolve: resolve.current,
};
}
https://jsfiddle.net/ahmadabdul3/atgcxhod/5/
I'm new to react, here I have two same codes, one is with classes that work, and another is converted from that same class into hooks.
in hooks version, my 'then' is giving an error
Property 'then' does not exist on type '(dispatch: any) =>
Promise'.ts(2339)
have I made some mistake with conversion?
why it is not giving the same error in class while both are the same?
also console.log("Fetched model", realGraph.model); should give an object but it is giving undefined(in-class version it works), but if I put this console outside of loadGraph function then it gives an object, why it's not giving an object inside loadGraph function?
any ideas and suggestions?
class:
import { getGraph, getFloorplan, changeActiveCamera } from '../redux/actions';
const mapStateToProps = (state) => {
return {
currentSite: state.selection.currentSite,
currentCamera: state.selection.currentCamera,
};
};
function mapDispatchToProps(dispatch) {
return {
getGraph: (site) => dispatch(getGraph(site)),
getFloorplan: (site) => dispatch(getFloorplan(site)),
changeActiveCamera: (site, id) => dispatch(changeActiveCamera(site, id)),
};
}
loadGraph() {
if (this.props.currentSite) {
this.props.getFloorplan(this.props.currentSite.identif).then(() => {
console.log('Fetched floorplan');
this.props.getGraph(this.props.currentSite.identif).then(() => {
console.log('Fetched model', this.props.realGraph.model);
// new camera-related node & link status
if (this.props.currentCamera) {
this.props.changeActiveCamera(
this.props.currentSite.identif,
this.props.currentCamera.identif
);
}
});
});
}
}
converted from class to hooks:
Hooks:
const dispatch = useDispatch();
const realGraph = useSelector((state) => state.graphArticles.graph);
const currentSite = useSelector((state) => state.selection.currentSite);
const currentCamera = useSelector((state) => state.selection.currentCamera);
const dispatchGetFloorplan = (site) => dispatch(getFloorplan(site));
const dispatchGetGraph = (site) => dispatch(getGraph(site));
const dispatchChangeActiveCamera = (site, id) =>
dispatch(changeActiveCamera(site, id));
const loadGraph = () => {
if (currentSite) {
dispatchGetFloorplan(currentSite.identif).then(() => {
console.log('Fetched floorplan');
dispatchGetGraph(currentSite.identif).then(() => {
console.log('Fetched model', realGraph.model);
// new camera-related node & link status
if (currentCamera) {
dispatchChangeActiveCamera(
currentSite.identif,
currentCamera.identif
);
}
});
});
}
};
my action related to those:
export function getGraph(site) {
return getData(`api/graph/${site}`, GET_GRAPHS);
}
export function getFloorplan(site) {
return getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
}
On first glance, there are several things I would change in the code you provided.
First, don't use any wrapper factories over your dispatch functions. Use dispatch(action()) directly where you need it component. You aren't gaining anything by creating wrapper functions.
Second, it would be advisable to use some sort of middleware, like Redux Thunk, to handle async Redux actions (like fetching something from the API).
The actions you provided are just "dumb" functions, which are not returning promises so you can't expect it to be "then"-able in your target component.
I also advise the async/await syntax since it is much more readable.
Third, you need to leverage the Hooks reactive API with the useEffect hook.
So first try to define getFloorPlan and getGraph as async actions using the redux-thunk syntax.
export const getGraphAsync = (site) => async (dispatch) => {
try {
const data = await getData(`api/graph/${site}`, GET_GRAPHS);
dispatch(saveGraphData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
export const getFloorplanAsync = (site) => async (dispatch) => {
try {
const data = await getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
dispatch(saveImageData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
I am making an assumption that you correctly configured your store.js to use the thunk middleware.
And then refactor the rest of the component (following some best practices):
const someHookComponent = () => {
// ...
const dispatch = useDispatch();
const currentSite = useSelector((state) =>
state.selection.currentSite);
const currentCamera = useSelector((state) =>
state.selection.currentCamera);
const loadGraph = async () => {
if (currentSite) {
await dispatch(getFloorPlanAsync(currentSite.identif));
console.log('Fetched floorplan');
await dispatch(getGraphAsync(currentSite.identif));
console.log('Fetched model', realGraph.model); /* where is
realGraph coming from? */
/* Why is it important that these 2 dispatches follow one
another when there is no data being passed from one to the
other, or being used later in the component... */
});
});
}
};
useEffect(() => {
// new camera-related node & link status
if (currentCamera) {
dispatch(changeActiveCamera(
currentSite.identif,
currentCamera.identif
));
}
}, [currentSite?.identif, currentCamera?.identif]) /* null chaining is optional here */
// ...
}
I am guessing that loadGraph gets called by some onClick event somewhere down the line like this:
onClick={loadGraph}
If it is called inside useEffect, define the deps (variables used inside loadGraph):
useDeepCompareEffect(() => {
// ... some logic
loadGraph()
}, [currentSite, realGraph])
If you put your currentSite and currentCamera objects directly into the useEffect list of deps then you need to do a deep comparison "by hand".
In that case it's best to create a custom hook like useDeepCompareEffect which will do the heavy lifting of running deep comparisons of reference types under the hood (with the help of some library like lodash for example).
If you want to use or console.log the latest value of realGraph (reference type), you need to use the useEffect hook with a deep comparison again (or just extract the target primitive directly into the deps list and use vanilla useEffect) :
useDeepCompareEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph]) // reference type
// or
useEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph.someProperty]) // primitive
I've being trying populate my redux store with data that comes from my mongo-db realm database.
Whenever I run the function below it will execute fine but the problem is data will be delayed and ends up not reaching my redux store.
My thunk function:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
return await Realm.open(config).then(async (projectRealm) => {
let syncItems = await projectRealm.objects("Item");
await syncItems.addListener((x, changes) => {
x.map(async (b) => {
console.log(b);
return b;
});
});
});
} catch (error) {
console.log(error);
throw error;
}
}
);
and my redux reducer:
extraReducers: (builder) => {
builder.addCase(addItemsTest.fulfilled, (state, { payload }: any) => {
try {
console.log("from add Items");
console.log(payload);
state.push(payload);
} catch (error) {
console.log(error)
}
});
}
Expected Results:
My redux store should have these data once addItemsTest return something:
[{
itemCode: 1,
itemDescription: 'Soccer Ball',
itemPrice: '35',
partition: 'partitionValue',
},
{
itemCode: 2,
itemDescription: 'Base Ball',
itemPrice: '60',
partition: 'partitionValue',
}
]
Actual Results:
Mixed Syntaxes
You are combining await/async and Promise.then() syntax in a very confusing way. It is not an error to mix the two syntaxes, but I do not recommend it. Stick to just await/async
Void Return
Your action actually does not return any value right now because your inner then function doesn't return anything. The only return is inside of the then is in the x.map callback. await syncItems is the returned value for the mapper, not for your function.
Right now, here's what your thunk does:
open a connection
get items from realm
add a listener to those items which logs the changes
returns a Promise which resolves to void
Solution
I believe what you want is this:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
try {
const projectRealm = await Realm.open(config);
const syncItems = await projectRealm.objects("Item");
console.log(syncItems);
return syncItems;
} catch (error) {
console.log(error);
throw error;
}
}
);
Without the logging, it can be simplified to:
export const addItemsTest = createAsyncThunk(
"addItems",
async (config: any) => {
const projectRealm = await Realm.open(config);
return await projectRealm.objects("Item");
}
);
You don't need to catch errors because the createAsyncThunk will handle errors by dispatching an error action.
Edit: Listening To Changes
It seems that your intention is to sync your redux store with changes in your Realm collection. So you want to add a listener to the collection that calls dispatch with some action to process the changes.
Here I am assuming that this action takes an array with all of the items in your collection. Something like this:
const processItems = createAction("processItems", (items: Item[]) => ({
payload: items
}));
Replacing the entire array in your state is the easiest approach. It will lead to some unnecessary re-renders when you replace item objects with identical versions, but that's not a big deal.
Alternatively, you could pass specific properties of the changes such as insertions and handle them in your reducer on a case-by-case basis.
In order to add a listener that dispatches processItems, we need access to two variables: the realm config and the redux dispatch. You can do this in your component or by calling an "init" action. I don't think there's really much difference. You could do something in your reducer in response to the "init" action if you wanted.
Here's a function to add the listener. The Realm.Results object is "array-like" but not exactly an array so we use [...x] to cast it to an array.
FYI this function may throw errors. This is good if using in createAsyncThunk, but in a component we would want to catch those errors.
const loadCollection = async (config: Realm.Configuration, dispatch: Dispatch): Promise<void> => {
const projectRealm = await Realm.open(config);
const collection = await projectRealm.objects<Item>("Item");
collection.addListener((x, changes) => {
dispatch(processItems([...x]));
});
}
Adding the listener through an intermediate addListener action creator:
export const addListener = createAsyncThunk(
"init",
async (config: Realm.Configuration, { dispatch }) => {
return await loadCollection(config, dispatch);
}
);
// is config a prop or an imported global variable?
const InitComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
dispatch(addListener(config));
}, [config, dispatch]);
/* ... */
}
Adding the listener directly:
const EffectComponent = ({config}: {config: Realm.Configuration}) => {
const dispatch = useDispatch();
useEffect( () => {
// async action in a useEffect need to be defined and then called
const addListener = async () => {
try {
loadCollection(config, dispatch);
} catch (e) {
console.error(e);
}
}
addListener();
}, [config, dispatch]);
/* ... */
}
I have the common warning displaying upon loading of my web app but never again...
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
EDIT****
It is caused by this chunk of code. I have narrowed it down to one function. It blows up when I try to setMoisture state. I am not sure why.
function getData (){
Axios.get("http://localhost:3001/api/get-value").then((response) => {
const recievedData = response.data;
const dataValue = recievedData.map((val) => {
return [val.value]
})
if (loading === true){
setLoading(false);
}
return parseInt(dataValue);
}).then((resp)=>setMoisture(resp))
}
React.useEffect(() => {
if (moisture === "initialState"){
getData();
}
}, []);
Posting the answer here (based from the comments) for completeness.
Basically, use local variables and cleanup function towards the end of useEffect(). Using this as reference:
Similar situation here
You should declare the function inside the useEffect or add it as a dependency. one way to do it's just moving your function inside the hook.
// I assumed that your useState hooks looks something similar.
const [moisture, setMoisture] = React.useState('initialState')
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
function getData() {
Axios.get("http://localhost:3001/api/get-value").then((response) => {
const recievedData = response.data;
const dataValue = recievedData.map((val) => {
return [val.value]
})
if(loading === true) {
setLoading(false);
}
return parseInt(dataValue);
}).then((resp => setMoisture(resp)))
}
if (moisture === "initialState"){
getData();
}
}, [])
You also probably want to first set your data to the state and then change your loading state to false, this is gonna prevent some bugs. This is another way to do it and manage the loading state and the promises
React.useEffect(() => {
function getData() {
setLoading(true)
Axios.get("http://localhost:3001/api/get-value")
.then((response) => {
const dataValue = response.data.map((val) => {
return [val.value]
})
// This is going to pass 0 when can't parse the data
setMoisture(parseInt(dataValue) || 0)
setLoading(false)
})
}
getData()
}, [])
So I have this Redux action creator that is using redux thunk middleware:
accountDetailsActions.js:
export function updateProduct(product) {
return (dispatch, getState) => {
const { accountDetails } = getState();
dispatch({
type: types.UPDATE_PRODUCT,
stateOfResidence: accountDetails.stateOfResidence,
product,
});
};
}
How do I test it? I'm using the chai package for testing. I have found some resources online, but am unsure of how to proceed. Here is my test so far:
accountDetailsReducer.test.js:
describe('types.UPDATE_PRODUCT', () => {
it('should update product when passed a product object', () => {
//arrange
const initialState = {
product: {}
};
const product = {
id: 1,
accountTypeId: 1,
officeRangeId: 1,
additionalInfo: "",
enabled: true
};
const action = actions.updateProduct(product);
const store = mockStore({courses: []}, action);
store.dispatch(action);
//this is as far as I've gotten - how can I populate my newState variable in order to test the `product` field after running the thunk?
//act
const newState = accountDetailsReducer(initialState, action);
//assert
expect(newState.product).to.be.an('object');
expect(newState.product).to.equal(product);
});
});
My thunk doesn't do any asynchronous actions. Any advice?
How to Unit Test Redux Thunks
The whole point of a thunk action creator is to dispatch asynchronous actions in the future. When using redux-thunk a good approach is to model the async flow of beginning and end resulting in success or an error with three actions.
Although this example uses Mocha and Chai for testing you could quite as easily use any assertion library or testing framework.
Modelling the async process with multiple actions managed by our main thunk action creator
Let us assume for the sake of this example that you want to perform an asynchronous operation that updates a product and want to know three crucial things.
When the async operation begins
When the async operation finishes
Whether the async operation succeeded or failed
Okay so time to model our redux actions based on these stages of the operation's lifecycle. Remember the same applies to all async operations so this would commonly be applied to http requests to fetch data from an api.
We can write our actions like so.
accountDetailsActions.js:
export function updateProductStarted (product) {
return {
type: 'UPDATE_PRODUCT_STARTED',
product,
stateOfResidence
}
}
export function updateProductSuccessful (product, stateOfResidence, timeTaken) {
return {
type: 'PRODUCT_UPDATE_SUCCESSFUL',
product,
stateOfResidence
timeTaken
}
}
export function updateProductFailure (product, err) {
return {
product,
stateOfResidence,
err
}
}
// our thunk action creator which dispatches the actions above asynchronously
export function updateProduct(product) {
return dispatch => {
const { accountDetails } = getState()
const stateOfResidence = accountDetails.stateOfResidence
// dispatch action as the async process has begun
dispatch(updateProductStarted(product, stateOfResidence))
return updateUser()
.then(timeTaken => {
dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken))
// Yay! dispatch action because it worked
}
})
.catch(error => {
// if our updateUser function ever rejected - currently never does -
// oh no! dispatch action because of error
dispatch(updateProductFailure(product, error))
})
}
}
Note the busy looking action at the bottom. That is our thunk action creator. Since it returns a function it is a special action that is intercepted by redux-thunk middleware. That thunk action creator can dispatch the other action creators at a point in the future. Pretty smart.
Now we have written the actions to model an asynchronous process which is a user update. Let's say that this process is a function call that returns a promise as would be the most common approach today for dealing with async processes.
Define logic for the actual async operation that we are modelling with redux actions
For this example we will just create a generic function that returns a promise. Replace this with the actual function that updates users or does the async logic. Ensure that the function returns a promise.
We will use the function defined below in order to create a working self-contained example. To get a working example just throw this function in your actions file so it is in the scope of your thunk action creator.
// This is only an example to create asynchronism and record time taken
function updateUser(){
return new Promise( // Returns a promise will be fulfilled after a random interval
function(resolve, reject) {
window.setTimeout(
function() {
// We fulfill the promise with the time taken to fulfill
resolve(thisPromiseCount);
}, Math.random() * 2000 + 1000);
}
)
})
Our test file
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import chai from 'chai' // You can use any testing library
let expect = chai.expect;
import { updateProduct } from './accountDetailsActions.js'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Test thunk action creator', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({})
const expectedActions = [
'updateProductStarted',
'updateProductSuccessful'
]
return store.dispatch(fetchSomething())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).to.eql(expectedActions)
})
})
it('expected actions should be dispatched on failed request', () => {
const store = mockStore({})
const expectedActions = [
'updateProductStarted',
'updateProductFailure'
]
return store.dispatch(fetchSomething())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).to.eql(expectedActions)
})
})
})
Have a look at Recipe: Writing Tests from the official documentation. Also, what are you testing, the action creator or the reducer?
Action Creator Test Example
describe('types.UPDATE_PRODUCT', () => {
it('should update product when passed a product object', () => {
const store = mockStore({courses: []});
const expectedActions = [
/ * your expected actions */
];
return store.dispatch(actions.updateProduct(product))
.then(() => {
expect(store.getActions()).to.eql(expectedActions);
});
});
});
Reducer Test Example
Your reducer should be a pure function, so you can test it in isolation outside of the store environment.
const yourReducer = require('../reducers/your-reducer');
describe('reducer test', () => {
it('should do things', () => {
const initialState = {
product: {}
};
const action = {
type: types.UPDATE_PRODUCT,
stateOfResidence: // whatever values you want to test with,
product: {
id: 1,
accountTypeId: 1,
officeRangeId: 1,
additionalInfo: "",
enabled: true
}
}
const nextState = yourReducer(initialState, action);
expect(nextState).to.be.eql({ /* ... */ });
});
});
export const someAsyncAction = (param) => (dispatch, getState) => {
const { mock } = getState();
dispatch({
type: 'SOME_TYPE',
mock: mock + param,
})
}
it('should test someAsyncAction', () => {
const param = ' something';
const dispatch = jest.fn().mockImplementation();
const getState = () => ({
mock: 'mock value',
});
const expectedAction = {
type: 'SOME_TYPE',
mock: 'mock value something'
};
const callback = someAsyncAction(param);
expect(typeof callback).toBe('function');
callback.call(this, dispatch, getState);
expect(dispatch.mock.calls[0]).toEqual([expectedAction])
});