I found this code from redux documentation
// test-utils.jsx
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
// Import your own reducer
import userReducer from '../userSlice'
function render(
ui,
{
preloadedState,
store = configureStore({ reducer: { user: userReducer }, preloadedState }),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
However, I am working on with a TypeScript project and I am trying to add types to this code snippet however I cannot find the correct types to use. I also cannot find anything from their documentation.
Has anyone tried this?
There is a TypeScript example on React testing library's official site.
import React from 'react';
import { render as rtlRender } from '#testing-library/react';
import { configureStore } from '#reduxjs/toolkit';
import { Provider } from 'react-redux';
const userReducer = (state = { name: '' }) => {
return state;
};
function render(
ui,
{ preloadedState, store = configureStore({ reducer: { user: userReducer }, preloadedState }), ...renderOptions }
) {
const Wrapper: React.FC = ({ children }) => {
return <Provider store={store}>{children}</Provider>;
};
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
Related
this is my testing-library-utils.js:
import React from "react";
import { render as rtlRender } from "#testing-library/react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import reducer from "../reducers/rootReducer";
import { createMemoryHistory } from "history";
import { Router } from "react-router-dom";
const middleware = [thunk];
const history = createMemoryHistory();
function render(
ui,
{
initialState,
store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return (
<Provider store={store}>
<Router history={history}>{children}</Router>
</Provider>
);
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
// re-export everything
export * from "#testing-library/react";
// override render method
export { render };
When i click on a <Link/>(using userEvent), it does not navigate to the desired page.
I think its the way i am using <Router/> in my testing-library-utils.js.
How should I render with react-redux as well as react-router for testing using react-testing-library?
Here is how to do it in TypeScript:
import { MemoryRouter } from "react-router-dom";
import { render } from "#testing-library/react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "../reducers/rootReducer";
const middleware = [thunk];
export const renderWithRouterAndRedux = (
component: React.ReactElement,
{ initialState = {}, store = createStore(reducer, initialState, applyMiddleware(...middleware)), route = "/", ...renderOptions } = {},
) => {
window.history.pushState({}, "Initial Page", route);
const Wrapper: React.FC = ({ children }) => (
<Provider store={store}>
<MemoryRouter>{children}</MemoryRouter>
</Provider>
);
return render(component, { wrapper: Wrapper, ...renderOptions });
};
// re-export everything
export * from "#testing-library/react";
// override render method
export { render };
I am writing a test where I need to render a component, but the rendering of my component is not working and I am receiving this error:
Uncaught [TypeError: Cannot read property 'role' of undefined].
This is because in the componentDidMount function in my component I am checking if this.props.authentication.user.role === 'EXPERT'. However, this.props.authentication has user as undefined.
This is the correct initialState for my program, but for the test I want to set my initialState to have a user object. That is why I redefine initialState in my test. However, the component does not render with that new initialState.
Here is the testing file:
import { Component } from '../Component.js';
import React from 'react';
import { MemoryRouter, Router } from 'react-router-dom';
import { render, cleanup, waitFor } from '../../test-utils.js';
import '#testing-library/jest-dom/extend-expect';
afterEach(cleanup)
describe('Component Testing', () => {
test('Loading text appears', async () => {
const { getByTestId } = render(
<MemoryRouter><Component /></MemoryRouter>,
{
initialState: {
authentication: {
user: { role: "MEMBER", memberID:'1234' }
}
}
},
);
let label = getByTestId('loading-text')
expect(label).toBeTruthy()
})
});
Here is the Component file:
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
tasks: [],
loading: true,
}
this.loadTasks = this.loadTasks.bind(this)
}
componentDidMount() {
if (
this.props.authentication.user.role == 'EXPERT' ||
this.props.authentication.user.role == 'ADMIN'
) {
this.loadTasks(this.props.location.state.member)
} else {
this.loadTasks(this.props.authentication.user.memberID)
}
}
mapState(state) {
const { tasks } = state.tasks
return {
tasks: state.tasks,
authentication: state.authentication
}
}
}
I am also using a custom render function that is below
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { initialState as reducerInitialState, reducer } from './_reducers'
import rootReducer from './_reducers'
import configureStore from './ConfigureStore.js';
import { createMemoryHistory } from 'history'
function render(ui, {
initialState = reducerInitialState,
store = configureStore({}),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
Perhaps I am coming super late to the party but maybe this can serve to someone. What I have done for a Typescript setup is the following (all this is within test-utils.tsx)
const AllProviders = ({
children,
initialState,
}: {
children: React.ReactNode
initialState?: RootState
}) => {
return (
<ThemeProvider>
<Provider store={generateStoreWithInitialState(initialState || {})}>
<FlagsProvider value={flags}>
<Router>
<Route
render={({ location }) => {
return (
<HeaderContextProvider>
{React.cloneElement(children as React.ReactElement, {
location,
})}
</HeaderContextProvider>
)
}}
/>
</Router>
</FlagsProvider>
</Provider>
</ThemeProvider>
)
}
interface CustomRenderProps extends RenderOptions {
initialState?: RootState
}
const customRender = (
ui: React.ReactElement,
customRenderProps: CustomRenderProps = {}
) => {
const { initialState, ...renderProps } = customRenderProps
return render(ui, {
wrapper: (props) => (
<AllProviders initialState={initialState}>{props.children}</AllProviders>
),
...renderProps,
})
}
export * from '#testing-library/react'
export { customRender as render }
Worth to mention that you can/should remove the providers that doesn't make any sense for your case (like probably the FlagsProvider or the HeaderContextProvider) but I leave to illustrate I decided to keep UI providers within the route and the others outside (but this is me making not much sense anyway)
In terms of the store file I did this:
//...omitting extra stuff
const storeConfig = {
// All your store setup some TS infer types may be a extra challenge to solve
}
export const store = configureStore(storeConfig)
export const generateStoreWithInitialState = (initialState: Partial<RootState>) =>
configureStore({ ...storeConfig, preloadedState: initialState })
//...omitting extra stuff
Cheers! 🍻
I am not sure what you are doing in the configure store, but I suppose the initial state of your component should be passed in the store.
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { initialState as reducerInitialState, reducer } from './_reducers'
import { createMemoryHistory } from 'history'
function render(
ui,
{
initialState = reducerInitialState,
store = createStore(reducer,initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
I hope it will help you :)
React-Redux doesn't update UI when store changes.
I expect {this.props.isLogged} to get changed dynamically when Change button is clicked.
I searched tons of materials but I cannot find why the text doesn't change when button is clicked.
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore } from "redux";
import reducers from "./reducers";
import { Provider } from "react-redux";
const store = createStore(
reducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "./actions";
class App extends React.Component {
changeText = () => {
this.props.loginUser();
};
render() {
return (
<div>
<span>{this.props.isLogged}</span>
<button onClick={this.changeText}>Change</button>
</div>
);
}
}
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.isLogged
};
};
const mapDispatchToProps = { loginUser };
export default connect(mapStateToProps, mapDispatchToProps)(App);
./src/reducers/index.js
import { combineReducers } from "redux";
import { LOGIN_USER } from "../actions";
function userReducer(state = { isLogged: false }, action) {
switch (action.type) {
case LOGIN_USER:
return { isLogged: !state.isLogged };
default:
return state;
}
}
const reducers = combineReducers({
userReducer
});
export default reducers;
./src/actions/index.js
export const LOGIN_USER = "LOGIN_USER";
export function loginUser(text) {
return { type: LOGIN_USER };
}
Check this out..
https://codesandbox.io/s/react-redux-doesnt-update-ui-vj3eh
const reducers = combineReducers({
userReducer //It is actually userReducer: userReducer
});
As you assiging your UserReducer to userReducer prop, you will have to fetch the same way in mapStateToProps
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.userReducer.isLogged
};
};
Also, isLogged prop is a Boolean variable.. So you are gonna have to use toString().
<span>{this.props.isLogged.toString()}</span>
Since you are using combineReducers, the isLogged boolean lives in state.userReducer.isLogged.
Consider changing combineReducers to combineReducers({ user: userReducer }) and accessing the flag with state.user.isLogged.
I want to separate modules, so I tried to separate files in the src/store/modules directory.
To merge reducer modules, I use combineReducers() in modules/index.js.
Before separating these modules, modules/index.js file's code was modules/board.js.
Then I added board.js file. I moved code of index.js to board.js. Finally I added combineReducer() in index.js, but somehow it is not working.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App';
import store from './store';
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
src/containers/BoardContainer.js
import React from 'react';
import Board from '../components/Board';
import { connect } from 'react-redux';
import * as boardActions from '../store/modules/board';
class BoardContainer extends React.Component {
componentWillMount() {
this.props.handleReadBoards();
}
render() {
/* ... */
}
}
const mapStateToProps = (state) => {
return {
boardList: state.get('boardList')
};
}
const mapDispatchToProps = (dispatch) => {
return {
handleReadBoards: () => { dispatch(boardActions.readBoardList()) }
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BoardContainer);
src/store/index.js
// redux
import { createStore, applyMiddleware, compose } from 'redux';
import reducers from './modules';
// redux middleware
import thunk from 'redux-thunk';
const store = createStore(reducers,
compose(applyMiddleware(thunk))
);
export default store;
src/store/modules/index.js
import { combineReducers } from 'redux';
import board from './board';
export default combineReducers({
board
});
src/store/modules/board.js
import { createAction, handleActions } from 'redux-actions';
import { Map, List } from 'immutable';
import * as boardApi from '../../lib/api/board';
// Action Types
const READ_BOARD_LIST = 'board/READ_BOARD_LIST';
// Action Creators
export const readBoardList = () => async (dispatch) => {
try {
const boardList = await boardApi.getBoardList();
dispatch({
type: READ_BOARD_LIST,
payload: boardList
});
} catch (err) {
console.log(err);
}
}
// initial state
const initialState = Map({
boardList: List()
})
// reducer
// export default handleActions({
// [READ_BOARD_LIST]: (state, action) => {
// const boardList = state.get('boardList');
// return state.set('boardList', action.payload.data);
// },
// }, initialState);
// reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case READ_BOARD_LIST:
return state.set('boardList', action.payload.data);
default:
return state;
}
}
Your reducer now contains submodules. So that you have to state from which module you want to get the data: state.board.get('boardList').
You can try to setup redux tool to easy visualize your data inside redux.
const mapStateToProps = (state) => {
return {
boardList: state.board.get('boardList')
};
}
I've spent a couple of days now searching for the answer to this and I still don't know what I'm doing wrong. I have other projects set up in exactly the same way that are fetching data from api's fine. All other answers have said variations of how the actions need to return objects, which as far as I can tell mine are
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { createLogger } from 'redux-logger';
import Thunk from 'redux-thunk';
import reducer from './reducers/reducer';
import App from './App';
import './css/index.css';
import './css/font-awesome.css';
import './css/bulma.css';
const logger = createLogger();
const store = createStore(reducer, applyMiddleware(Thunk, logger));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
Component calling mapDispatchToProps
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchRepositories } from '../actions/searchActions';
class Results extends Component {
componentDidMount() {
this.props.searchRepositories();
}
render() {
return (
<div>Some Stuff</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
searchRepositories: () => {
dispatch(searchRepositories());
},
};
};
const mapStateToProps = (state) => {
return {
repositories: state.repositories,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Results);
Reducer:
import * as types from '../actions/types';
import initialState from './INITIAL_STATE';
function reducer(prevState = initialState, action) {
if (!action) return prevState;
switch (action.type) {
case types.FETCH_REPOS_REQUEST:
return { ...prevState, loading: true };
case types.FETCH_REPOS_SUCCESS:
return {
...prevState,
loading: false,
repositories: action.data,
error: '',
};
case types.FETCH_REPOS_ERROR:
return { ...prevState, error: 'Encountered an error during GET request' };
default:
return prevState;
}
}
export default reducer;
action creator:
import axios from 'axios';
import * as types from './types';
import { ROOT } from '../config';
export function searchRepositoriesRequest() {
return {
type: types.FETCH_REPOS_REQUEST,
};
}
export function searchRepositoriesSuccess(repositories) {
return {
type: types.FETCH_REPOS_SUCCESS,
data: repositories,
};
}
export function searchRepositoriesError(error) {
return {
type: types.FETCH_REPOS_ERROR,
data: error,
};
}
export function searchRepositories() {
return function (dispatch) {
dispatch(searchRepositoriesRequest());
return axios.get(`${ROOT}topic:ruby+topic:rails`).then((res) => {
dispatch(searchRepositoriesSuccess(res.data));
}).catch((err) => {
dispatch(searchRepositoriesError(err));
});
};
}
I have got this axios api request working using react this.state where I just put it in component did mount. If anyone can see where I am going wrong it would help me out a lot.
Ok, so it looks like I was using an earlier version of redux, which the version of redux-thunk I had installed, didn't like! I updated to the latest version of redux and it now calls as it should.
I'm still not sure why the other projects that have the older version installed still work...