When testing action creators, I want to test whether the correct action creator was called and also whether the right action was returned
action.js
export const AboutUs = {
getAboutUsContentSuccess: 'getAboutUsContentSuccess/AboutUs',
getBuildVersionSuccess: 'getBuildVersionSuccess/AboutUs'
};
export const getAboutUsContentSuccess = (data) => {
return {
type: AboutUs.getAboutUsContentSuccess,
data
}
}
action.test.js
import * as actions from './actions'
describe('actions', () => {
it('should create an action to getAboutUsContent', () => {
const text = 'Finish docs'
const expectedAction = {
type: 'getAboutUsContentSuccess/AboutUs',
text
}
console.log(expectedAction)
console.log(actions.getAboutUsContentSuccess(text))
expect(actions.getAboutUsContentSuccess(text)).toEqual(expectedAction)
})
})
But I am getting the following error
TypeError: Cannot read property 'getAboutUsContentSuccess' of undefinedd
It seems that you have a typo in your imports, the name of your file action.js is singular.
Just change your imports to: import * as actions from './action'
Related
I have written render tests for component. I have used action in useEffect that is calling api by triggering saga.
Tests are giving positive result but I am getting following fetch error in from try catch block of saga(calling api)
FetchError {
message: 'invalid json response body at reason: Unexpected end of JSON input',
type: 'invalid-json'
}
What should i do to remove this warning?
My component's useEffect code
const FileUploadContainer = ({
....// some props
}: Props) => {
useEffect(() => {
// action method that calling api by saga
loadOrgUnitClientFiles({
entity: 'xyzzzzz',
query: 'fooooo'
})
}, [loadOrgUnitClientFiles])
return (
<Page title={title}>
.....
</Page>
)
}
export default connect(
(state: RootState) => {
return {
...//mapStateToProps obj
}
},
{
uploadClientFile,
loadOrgUnitClientFiles
}
)(FileUploadContainer)
And the test file is
import React from 'react'
import { render, screen } from 'test-utils/customRenderer'
import FileUploadContainer from './FileUploadContainer'
import { fromJS } from 'immutable'
import SessionRecord from 'shared/reducers/sessionReducer/SessionRecord'
const initialState = fromJS({
...//mock obj
})
describe('FileUploadContainer', () => {
it('renders correctly', () => {
render(<FileUploadContainer />, {
state: initialState
})
expect(screen.getByText('Upload a file for TealBook')).toBeInTheDocument()
expect(screen.getByText('file1.xlsx')).toBeInTheDocument()
})
})
I am pretty new to redux and here I am trying to create a common dispatch function where I can call the function from multiple components but can't seem to use useDispatch() in my common component getting invalid hook call error.
import { useDispatch, useSelector } from "react-redux";
import { UPDATE_PREVIEW_DATA } from "../../redux/types";
export default function setPreviewData(event, obj, lang) {
const dispatch = useDispatch();
const previewData = useSelector((state) => state.previewData);
const dispatchFunc = () => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data: {
[obj]: {
[lang]: {
...previewData[obj][lang],
[event.target.name]: event.target.value,
},
},
},
});
};
return dispatchFunc;
}
// previewData.js in action folder
import { UPDATE_PREVIEW_DATA } from "../types";
const previewData = (data) => (dispatch) => {
dispatch({
type: UPDATE_PREVIEW_DATA,
data,
});
};
export default previewData;
// previewData.js in reducers folder
import { UPDATE_PREVIEW_DATA } from "../types";
const initialState = {...};
const previewData = (state = initialState, action) => {
switch (action.type) {
case UPDATE_PREVIEW_DATA: {
return action.data;
}
default:
return state;
}
};
export default previewData;
And I am trying to make this work like
// component.jsx
setPreviewData(e, "hightlights", "en");
Hooks are intended to be used in Functional components only. As per the Rules of hooks they can be called from
React function components.
Custom Hooks
Reference -> https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions
now you might think your setPreviewData is a React Function Component, but it's just a normal js function, that's why you are getting the error.
As a result, it doesn't get wrapped in React.createElement, so it thinks the hook call is invalid.
Moreover, you are committing one more mistake here, lets's say if setPreviewData was a Function Component you still call it as if though its a normal function
rootReducer
import { combineReducers } from "redux";
import mods from "./mods.js";
export default combineReducers({
mods
})
reducers/mods.js
import { GET_MODS, GET_SPECIFC_MOD } from "../actions/types"
const initialState = {
mods: [],
currMod: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_MODS:
return {
...state,
mods: action.payload
}
case GET_SPECIFC_MOD:
return {
...state,
currMod: action.payload
}
default:
return state
}
}
actions/mods.js
import axios from 'axios'
import { GET_MODS, GET_SPECIFC_MOD } from './types'
// get the mods
export const getMods = () => dispatch => {
axios.get('http://localhost:8000/api/mods')
.then(res => {
dispatch({
type: GET_MODS,
payload: res.data
})
}).catch(err => console.log(err))
}
// get single mod
export const getSpecificMod = (title) => dispatch => {
axios.get(`http://localhost:8000/api/mods/${title}`)
.then(res => {
dispatch({
type: GET_SPECIFC_MOD,
payload: res.data
})
}).catch(err => console.log(err))
}
components/download.js
import React from 'react'
import { useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
useEffect(() => {
const title = window.location.pathname.split('/')[3]
getSpecificMod(title)
})
return (
<></>
)
}
const mapStateToProp = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProp, getSpecificMod)(Download)
Response from backend
GET http://localhost:8000/api/mods/function(){return!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__&&a.dispatch.apply(a,arguments)}
Basically the user clicks on a mod and gets sent to the download section that is handled by 'download.js' the component ('download.js') renders it and reads the window.location to retrieve the title, with redux I want to get the mod so i made a function that takes the title and sends the request 'getMod(title)' but for some reason it is throwing horrible errors that I dont understand, any help is appreciated!
You are not dispatching the action properly in your component. Right now you are actually just calling the getSpecificMod action creator function from your imports. Your Download component doesn't read anything from props so it is ignoring everything that gets created by the connect HOC.
If you want to keep using connect, you can fix it like this:
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = ({currMod, getSpecificMod}) => {
const title = window.location.pathname.split('/')[3]
useEffect(() => {
getSpecificMod(title)
}, [title])
return (
<></>
)
}
const mapStateToProps = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProps, {getSpecificMod})(Download)
We are now accessing the bound action creator as a prop of the component. mapDispatchToProps is an object which maps the property key to the action.
But it's better to use the useDispatch hook:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
const currentMod = useSelector(state => state.mods.currMod);
const dispatch = useDispatch();
const title = window.location.pathname.split('/')[3]
useEffect(() => {
dispatch(getSpecificMod(title));
}, [title, dispatch]);
return (
<></>
)
}
export default Download;
There might be some confusion on terminology here. Your getSpecificMod function is a function which takes dispatch as an argument but it is not a mapDispatchToProps. It is a thunk action creator.
Make sure that you have redux-thunk middleware installed in order to handle this type of action. Or better yet, use redux-toolkit.
Your useEffect hook needs some sort of dependency so that it knows when to run. If you only want it to run once you can use an empty array [] as your dependencies. If you don't specify the dependencies at all then it will re-run on every render.
Does the pathname change? If so, how do you know when? You might want to add an event listener on the window object. Or consider using something like react-router. But that is a separate question.
I am trying to test my actions in my React / Redux application, and in one of my actions, I am using getAppState() to get the current state of Redux. The action has no parameters, and simply deconstructs state properties from the return of getAppState()
actions/myFile/index.test.js
it('should call myAction successfully', () => {
const expected = [
{
type: MY_TYPE,
payload: {
...mockPayload
}
}
];
store.dispatch(myAction());
expect(store.getActions().toEqual(expected));
});
actions/myFile/index.js
export const myAction = () => (dispatch, getAppState) => {
const { myReducer: { reducerProp } } = getAppState();
const { valOne, valTwo, valThree } = reducerProp;
return myServiceCallPromise({ valOne, valTwo }, valThree)
.then((res) => {
dispatch(anotherAction());
});
}
When trying to test my action with Jest, I'm getting an error stating:
TypeError: Cannot read property 'reducerProp' of undefined
My question is HOW do I mock what the getAppState() func returns and use them in my test? I have googled once, saw the results and figured, why not go to StackOverflow and ask myself lol
(I'm also assuming you are using redux-thunk).
You are going to need to configure your store:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const initialState = {
myReducer: { reducerProp: 'something here' }
};
const store = mockStore(initialState)
// Your code down here...
See here for more info about how to use the store.
Basically what I wanted to do was to stop making axios calls inside of my component. So I thought; “Why not just create an action for that?”
I googled around to find a good “guide” to use Redux and this is what I’m using:
Add a constant to the constants file. Something like const GREAT_COURSE = GREAT_COURSE
Add an action creator to the actions folder. Return an action JavaScript object with a type of the constant you created.
Add a reducer to the reducers folder that handles this action creator.
So I began to create my action creator:
import axios from 'axios'
import { CUSTOMER_FETCH } from './types'
import settings from '../settings'
axios.defaults.baseURL = settings.hostname
export const customers = () => {
return dispatch => {
return axios.get('http://hejhej/customers').then(res => {
dispatch({
type: CUSTOMER_FETCH,
data: res.data
})
})
}
}
And later to add a reducer that handles my action creator:
import { CUSTOMER_FETCH } from '../actions/types'
const initial = []
const customer = action => {
return {
data: action.data
}
}
const customers = (state = initial, action) => {
switch (action.type) {
case CUSTOMER_FETCH:
customers = [...state, customer(action)]
console.log('customers as state', customers)
return customers
default:
return state
}
}
export default customers
And inside of my component I'm importing it:
import { customers } from '../../actions/customersAction'
And later using connect: export default connect(null, { customers })(Events)
And finally I'm using it inside of my component:
customers() {
this.props.customers(this.state.data)
}
So I'm wondering what I'm doing wrong, because I can't see my console.log in my dev tools. Thanks a lot for reading!
Inside of my component atm:
axios.get('http://hejhej/customers').then(res => {
this.setState({
res,
customer: res.data
})
})