I cannot figure out why this is not working:
-spec.js
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
},
store = fakeStore(state),
container = <HomePageContainer store={store} />;
const homePageContainer = shallow(container),
homePage = homePageContainer.find(HomePage);
expect(homePage.props.company.name).to.equal(state.company.name)
});
const fakeStore = state => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
HomePageContainer.js
import React from 'react';
import { connect } from 'react-redux'
import HomePage from '../../client/components/HomePage';
export const mapStateToProps = state => {
company: state.company
}
export { HomePage }
export default connect(mapStateToProps)(HomePage);
HomePage.js
import React, { Component, PropTypes } from 'react';
export default class HomePage extends Component {
render(){
return (
<div className='homepage'>
{/*{this.props.company.name}*/}
</div>
)
}
}
I'm getting this type error because props.company is undefined so for some reason it's not persisting state to :
TypeError: Cannot read property 'name' of undefined
That error is relating to the assert when it's trying to read expect(homePage.props.company.name)
I notice that when putting a breakpoint inside mapStateToProps, that it's not picking up the state object from the store still for some reason:
I know you can pass just the store via props...and if there's nothing in the context, connect() will be able to find it via the store prop. For example in another test suite, this passes just fine:
it('shallow render container and dive into child', () => {
const container = shallow(<ExampleContainer store={fakeStore({})}/>);
expect(container.find(Example).dive().text()).to.equal('Andy');
});
Problem
At this line you pass the store as a prop into <HomePageContainer>:
// ...
container = <HomePageContainer store={store} />;
// ...
But connect() needs the store via context.
And you must wrap the object you want to return in mapStateToProps in parenthese.
Solution
You can use shallow() to make the store available as a context property.
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
};
const store = fakeStore(state);
const homePageContainer = shallow(
<HomePageContainer />,
// Make the store available via context
{ context: { store } }
);
const homePage = homePageContainer.find(HomePage);
expect(homePage.props().company.name).to.equal(state.company.name)
});
Updated mapStateToProps:
export const mapStateToProps = state => ({
company: state.company
});
Found out the problem was really my helper.
This was passing a object with property "state" in it. Not what I want. That would have meant that mapStateToProps would have had to reference the props with state.state.somePropName
const fakeStore = (state) => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
changed it to this, and now mapStateToProps is working fine, it's able to access it with the props as the root level of the object literal, the object I passed in from my test:
const fakeStore = (state) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});
Related
I have below component in react. I put in short only
export interface EditCertificateProps {
id:string;
}
export function EditCertificate(props: any) {
injectStyle();
const {id} = props.match.params;
const history = useHistory();
}
When I am doing jest testing it is throwing error.
const id = '123';
describe('EditCertificate', () => {
const params = {
id: '123',
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate id={id} />);
expect(baseElement).toBeTruthy();
});
});
it is throwing error
from another component this page gets called like below.
<SecureRoute path="/:id/edit" component={EditCertificate} />
I changed my testcase like below still error.
describe('EditCertificate', () => {
const props = {
match: {
params: 123,
},
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate {...props.match.params} />);
expect(baseElement).toBeTruthy();
});
});
what wrong I am doing?
The EditCertificate component is expecting a match prop with a params property.
export function EditCertificate(props: any) {
injectStyle();
const {id} = props.match.params;
const history = useHistory();
...
}
This match prop needs to be provided in the unit test. You are creating a props object so you can just spread that into EditCertificate. Spread the entire props object, not props.match.params, the latter only spreads the individual params.
describe('EditCertificate', () => {
const props = {
match: {
params: {
id: 123, // <-- props.match.params.id
},
},
};
it('should render successfully', () => {
const { baseElement } = render(<EditCertificate {...props} />);
expect(baseElement).toBeTruthy();
});
});
The next issue will be a missing routing context for the useHistory hook. You can provide a wrapper for the render util, or simply wrap EditCertificate directly.
const RouterWrapper = ({ children }) => (
<MemoryRouter>{children}</MemoryRouter> // *
);
...
const { baseElement } = render(
<EditCertificate {...props} />,
{
wrapper: RouterWrapper
},
);
or
const { baseElement } = render(
<MemoryRouter>
<EditCertificate {...props} />
</MemoryRouter>
);
* MemoryRouter used for unit testing since there is no DOM
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..
What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);
I'm trying to get to grips with Redux + React - I have hooked up the relevant bits of Redux with connect() for a small todo app but I cannot for the life of me get the component to update and show the reflected store changes. The store state does update however the component will not. Here are the relevant bits in my code:
actionTypes.js
export const ADD_TODO = "ADD_TODO";
export const DELETE_TODO = "DELETE_TODO";
export const CLEAR_TODO = "CLEAR_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
reducers.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from '../actions/actionTypes';
const todoApp = (state, action) => {
let updatedState;
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
case COMPLETE_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items[action.index].completed = true;
return updatedState;
case DELETE_TODO:
const items = [].concat(state.todo.items);
items.splice(action.index, 1);
return Object.assign({}, state, {
todo: {
items: items
}
});
case CLEAR_TODO:
return Object.assign({}, state, {
todo: {
items: []
}
});
default:
return state;
}
};
export default todoApp;
actions.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from './actionTypes.js';
export const addTodoCreator = (text) => {
return {
type: ADD_TODO,
text: text,
completed: false
}
};
export const completeTodo = (index) => {
return {
type: COMPLETE_TODO,
index: index
}
};
export const deleteTodo = (index) => {
return {
type: DELETE_TODO,
index: index
}
};
export const clearTodo = (index) => {
return {
type: CLEAR_TODO,
index: index
}
};
AddTodoContainer.js
import { connect } from 'react-redux';
import TodoList from '../components/TodoList';
const mapStateToProps = (state, ownProps) => {
return {
todo: state.todo
}
};
export default connect(mapStateToProps)(TodoList);
TodoListContainer.js
import { connect } from 'react-redux';
import {addTodoCreator} from '../actions/actions';
import AddTodo from '../components/AddTodo';
const mapStateToProps = (state) => {
console.log(state);
return {
todo: state.todo
}
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => {
const action = addTodoCreator(text);
dispatch(action);
},
}
};
export default connect(mapStateToProps, mapDispatchToProps)(AddTodo);
AddTodo.js
import React from 'react'
const handler = (addTodo) => {
const text = document.getElementById('textInput').value;
addTodo(text);
};
const AddTodo = ({addTodo}) => {
return (
<div>
<input id="textInput" type="text" className="textInput" />
<button onClick={(handler).bind(null, addTodo)}>Add</button>
</div>
)
}
export default AddTodo
TodoList.js
import React from 'react';
import AddTodoContainer from '../containers/AddTodoContainer';
class TodoList extends React.Component {
render () {
console.log(this.props);
return (
<div>
<ul>
{this.props.todo.items.map((item) => {
return <li>
{item.text}
</li>
})}
</ul>
<AddTodoContainer/>
</div>
)
}
}
export default TodoList;
I've tried all of the suggestions under Troubleshooting and as far as I can tell I am not mutating state. The reducer is firing and I can log out the states. The code is stored here under react-fulltodo http://gogs.dev.dylanscott.me/dylanrhysscott/learn-redux
Thanks
Dylan
You're passing todo to your component and while the todo object gets updated the actual todo object in redux state is the same exact object as it was before. So react does not see the object as changed. For example:
const a = { foo: 'bar' };
const b = a;
b.foo = 'I made a change';
console.log(a==b);
// logs true because a and b are the same object
// This is exactly what's happening in React.
// It sees the object as the same, so it does not update.
You need to clone the todo object so that react sees it as a changed/new object.
In your reducer:
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
// Shallow clone updatedState.todo
updatedState.todo = Object.assign({}, updatedState.todo);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
Meanwhile, if you passed state.todo.items to your component you would not have to clone todo but you would have to clone items. So in the future, if you have a component that directly mapStateToProps with state.todo.items, it will have the same problem because you are not cloning the items array in ADD_TODO like you are in the DELETE_TODO reducer.
If I had a component, which was loaded into a page, accepted a few props, made a couple of API calls and rendered a list, would they share the same redux store?
Say for example...
<Trending data-limit=5 data-offset=0 />
<div>Something here</div>
<Trending data-limit=5 data-offset-5 />
I have something similar to this and they seem to override each other.
If you mean React State, then no.
If you mean Redux Store State, by mapStateToProps or other way and your react component are connected to the same end point in the storeState then Yes
ex : let's say you have mapStateToPros linking the props of the component to this end point of the store State.App.User.Info.email
If email changes all component mapped to this end point will update
In the other hand if you're calling each component with it's own data, then each component lives in it's own space like the example you gave in your question
I put together an example to show how to use the same component with two different Redux containers that could be used to populate the store differently. I am actually confused now because the two reducers overwrite the same state, despite being separated by combineReducers.
Example:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from 'react-redux';
import { createStore, combineReducers } from 'redux';
const ParentComponent = React.createClass({
propTypes: {
fetchData: React.PropTypes.func.isRequired,
data: React.PropTypes.string
},
componentDidMount: function () {
setTimeout(() => {
this.props.fetchData();
}, 2000);
},
render: function () {
return (
<div>{this.props.data}</div>
);
}
});
const ParentComponentContainer = React.createClass({
render: function () {
return (<ParentComponent {...this.props} />);
}
});
const mapStateToPropsFoo = (state) => {
if (state.exampleReducerFoo && state.exampleReducerFoo.data) {
return {
data: state.exampleReducerFoo.data
}
}
return {};
};
const mapStateToPropsBar = (state) => {
if (state.exampleReducerBar && state.exampleReducerBar.data) {
return {
data: state.exampleReducerBar.data
}
}
return {};
};
const mapDispatchToPropsFoo = (dispatch) => {
return {
fetchData: () => {
dispatch({
type: 'RECEIVE_DATA',
data: 'foo'
});
}
}
};
const mapDispatchToPropsBar = (dispatch) => {
return {
fetchData: () => {
dispatch({
type: 'RECEIVE_DATA',
data: 'bar'
});
}
}
};
const reducers = combineReducers({
exampleReducerFoo: (state = {}, action) => {
switch (action.type) {
case 'RECEIVE_DATA':
return Object.assign({}, state, {
data: action.data
});
default:
return state;
}
},
exampleReducerBar: (state = {}, action) => {
switch (action.type) {
case 'RECEIVE_DATA':
return Object.assign({}, state, {
data: action.data
});
default:
return state;
}
}
});
const store = createStore(reducers);
const ConnectedParentComponentContainerFoo = connect(mapStateToPropsFoo, mapDispatchToPropsFoo)(ParentComponentContainer);
const ConnectedParentComponentContainerBar = connect(mapStateToPropsBar, mapDispatchToPropsBar)(ParentComponentContainer);
ReactDOM.render(<Provider store={store}><div><ConnectedParentComponentContainerFoo data="aaa"/>something<ConnectedParentComponentContainerBar data="bbb"/></div></Provider>, document.getElementById('ReactApp'));
When the state gets to the mapStateToProps functions it's shape is:
{
exampleReducerBar: {
data: 'bar'
},
exampleReducerFoo: {
data: 'bar'
}
}
I expected the reducers to be writing to their own space in the state (reducerBar's data should be 'bar' and reducerFoo's data should be 'foo'), but apparently even though the reducers shape the state when using combineReducers, the state is shared between reducers. I am confused.