I have two components in my react app:
Component A
Performs a lazy fetch of users. This looks like:
const ComponentA = () => {
const [trigger, {data}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
useEffect(() => {
trigger();
}, []);
return <div>{data.map(user => user.id)}</div>
}
Component B
Wants to render a loading indicator while useLazyLoadUsers's isLoading property equals true. This component looks like this:
const ComponentB = () => {
const [, {isLoading}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
if (!isLoading) {
return <div>Users loaded</div>
}
return <div>Loading users</div>
}
The issue
While this works well (the states are in sync via the fixedLoadUsersKey), I'm struggling to find documentation or examples on how to test Component B.
Testing Component A is well documented here https://redux.js.org/usage/writing-tests#ui-and-network-testing-tools.
I already have an overwritten react testing library render method that provides a real store (which includes all my auto-generated queries).
What I would like to do is testing that Component B loading indicator renders - or not - based on a mocked isLoading value. I want to keep my current or similar implementation, not duplicating the isLoading state into another slice.
So far, I have tried mocking useLazyLoadUsers without success. I also tried dispatching an initiator before rendering the test, something like
it('should render the loading indicator', async () => {
const store = makeMockedStore();
store.dispatch(myApi.endpoints.loadUsers.initiate());
render(<ComponentB />, {store});
expect(await screen.findByText('Loading users')).toBeVisible();
})
This didn't work either.
Does someone have a hint on how to proceed here or suggestions on best practices?
I have a React component that doesn't render until fetchUser status is not loading. My test fails because the component is not rendered the first time and therefore can not find page loaded text. How can I mock my const [fetchStatusLoading, setFetchStatusLoading] = useState(false) on my test so that the page would get rendered instead of loading text?
const fetchUser = useSelector((state) => (state.fetchUser));
const [fetchStatusLoading, setFetchStatusLoading] = useState(true);
useEffect(() => {
setFetchStatusLoading(fetchUser .status === 'loading');
}, [fetchStatusLoading, fetch.status]);
useEffect(() => {
// this is a redux thunk dispatch that sets fetch.status to loading/succeeded
dispatch(fetchAPI({ username }));
}, []);
if(fetchStatusLoading) return 'loading..';
return (<>page loaded</>)
// test case fails
expect(getByText('page loaded')).toBeInTheDocument();
If you were able to mock the loading state you would only be testing the code on the "render" part and not all the component logic you are supposed to test, as this is the way Testing Library means to shine.
If this is just an asynchronous problem, you could do something like:
test('test page loaded', async () => {
render(<MyComponent />);
expect(await screen.findByText('page loaded')).toBeInTheDocument();
});
But it seems that your component contains an API request, so if the component accepts a function you could mock it and return the value, or you could mock fetch. Here are some reasons why you should not do that.
Using nock or mock-service-worker you can have a fake server that responds to your API requests and therefore run your component test without having to mock any internal state.
Testing Library just renders the component in a browser-like environment and does not provide an API to modify any of the props or the state. It was created with this purpose, as opposite of Enzyme, which provides an API to access the component props and state.
Check out this similar question: check state of a component using react testing library
I'm using a few third-party React hook libraries that aren't required for the initial render. E.g. react-use-gesture, react-spring, and react-hook-form. They all provide interactivity, which can wait until after the UI is rendered. I want to dynamically load these using Webpack's codesplitting (i.e. import()) after I render my component.
However, I can't stub out a React hook because it's essentially a conditional hook, which React doesn't support.
The 2 solutions that I can think of are:
Somehow extract the hook into a component and use composition
Force React to reconstruct a component after the hook loads
Both solutions seem hacky and it's likely that future engineers will mess it up. Are there better solutions for this?
As you say it, there are two ways to go about using lazy loaded hooks:
Load library in a Parent Component, conditionally render Component using library when available
Something along the lines of
let lib
const loadLib = () => {...}
const Component = () => {
const {...hooks} = lib
...
}
const Parent = () => {
const [loaded, setLoaded] = useState(false)
useEffect(() => loadComponent().then(() => setLoaded(true)), [])
return loaded && <Component/>
}
This method is indeed a little hacky and a lot of manual work for each library
Start loading a component using the hook, fail, reconstruct the component when the hook is loaded
This can be streamlined with the help of React.Suspense
<Suspense fallback={"Loading..."}>
<ComponentWithLazyHook/>
</Suspense>
Suspense works similar to Error Boundary like follows:
Component throws a Promise during rendering (via React.lazy or manually)
Suspense catches that Promise and renders Fallback
Promise resolves
Suspense re-renders the component
This way is likely to get more popular when Suspense for Data Fetching matures from experimental phase.
But for our purposes of loading a library once, and likely caching the result, a simple implementation of data fetching can do the trick
const cache = {}
const errorsCache = {}
// <Suspense> catches the thrown promise
// and rerenders children when promise resolves
export const useSuspense = (importPromise, cacheKey) => {
const cachedModule = cache[cacheKey]
// already loaded previously
if (cachedModule) return cachedModule
//prevents import() loop on failed imports
if (errorsCache[cacheKey]) throw errorsCache[cacheKey]
// gets caught by Suspense
throw importPromise
.then((mod) => (cache[cacheKey] = mod))
.catch((err) => {
errorsCache[cacheKey] = err
})
};
const SuspendedComp = () => {
const { useForm } = useSuspense(import("react-hook-form"), "react-hook-form")
const { register, handleSubmit, watch, errors } = useForm()
...
}
...
<Suspense fallback={null}>
<SuspendedComp/>
</Suspense>
You can see a sample implementation here.
Edit:
As I was writing the example in codesandbox, it completely escaped me that dependency resolution will behave differently than locally in webpack.
Webpack import() can't handle completely dynamic paths like import(importPath). It must have import('react-hook-form') somewhere statically, to create a chunk at build time.
So we must write import('react-hook-form') ourselves and also provide the importPath = 'react-hook-form' to use as a cache key.
I updated the codesanbox example to one that works with webpack, the old example, which won't work locally, can be found here
Have you considered stubbing the hooks? We used something similar to async load a large lib, but it was not a hook, so YMMV.
// init with stub
let _useDrag = () => undefined;
// load the actual implementation asynchronously
import('react-use-gesture').then(({useDrag}) => _useDrag = useDrag);
export asyncUseDrag = (cb) => _useDrag(cb)
I am new to React, and when I was reading about the docs, I found there were two ways to implement React components, functional-based and class-based. I know before React 16.8 it's not possible to manage state in functional components, but after that there is React Hooks.
The problem is, there seems to be one restriction for React Hooks, they can only be used inside functional components. Take a server-client as an example, which needs to change an isAuthenticated state while 401 received.
//client.js
import { useUserDispatch, signOut } from "auth";
export function request(url, args) {
var dispatch = useUserDispatch();
return fetch(url, args).then(response => {
if (response.status === 401) {
logout(dispatch);
}
}
);
//auth.js
import React from "react";
var UserStateContext = React.createContext();
var UserDispatchContext = React.createContext();
function userReducer(state, action) {
...
}
function UserProvider({ children }) {
var [state, dispatch] = React.useReducer(userReducer, {
isAuthenticated: false,
});
return (
<UserStateContext.Provider value={state}>
<UserDispatchContext.Provider value={dispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
function useUserState() {
return React.useContext(UserStateContext);
}
function useUserDispatch() {
return React.useContext(UserDispatchContext);
}
function signOut(dispatch) {
dispatch({});
}
export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };
The client code above will produce error "Hooks can only be called inside of the body of a function component".
So maybe I have to move line var dispatch = useUserDispatch() upward to the component where request is called, and pass dispatch as props to request.
I feel this is not right, no only request is forced to care about some meaningless(to it) dispatch, but also this dispatch will spread everywhere a component needs to request.
For class-based components, this.state doesn't solve this problem either, but at least I can use mobx.
So are there some other ideal ways to solve this problem?
I came at this point too. Long story short you need to use Redux and Thunk with Async Logic, as described in detail with examples in the link below [1] if you want to do all of the stuff by hand on your own.
[1] https://redux.js.org/tutorials/essentials/part-5-async-logic
There is another solution that gives out-of-the box experience with Asynchronous API (can work with OpenAPI and GraphQL, handles request, provides caching with lifecycle, etc) wrapping stuff from [1] and its called RTK Query [2].
[2] https://redux-toolkit.js.org/rtk-query/overview
Diagram below explains [1] process visually.. but I think RTK Query [2] wraps everything in one place and could be better solution. There is a Quick Start Guide [3]. I will give it a try :-)
[3] https://redux-toolkit.js.org/tutorials/rtk-query/
Mobx and hooks are very similar in implementation. Both use a render context that is in a sense "global". React ties that render context to the component render context, but Mobx keeps that render context separate. Therefore that means that hooks have to be created within a component render lifecycle (but can sometimes be called outside that context). Mobx-react ties the Mobx render lifecycle to the react lifecycle, triggering a react re-render when observed objects change. So Mobx-react nests the react render context within the Mobx render context.
React internally keeps tracks of hooks by the number of times and order the hook is called within a component render cycle. Mobx, on the other hand, wraps any "observable" object with a proxy that lets the Mobx context know if any of its properties were referenced during a Mobx "run context" (an autorun call, essentially). Then when a property is changed, Mobx knows what "run contexts" care about that property, and re-runs those contexts. This means that anywhere you have access to an observable object you can change a property on it and Mobx will react to it.
For react state hooks, react provides a custom setter function for a state object. React then uses calls to that setter to know when it needs to re-render a component. That setter can be used anywhere, even outside a React render, but you can only create that hook inside a render call, because otherwise react has no way to tell what component to tie that hook to. Creating a hook implicitly connects it to the current render context, and that's why hooks have to be created inside render calls: hook builders have no meaning outside a render call, because they have no way to know what component they are connected to -- but once tied to a component, then they need to be available anywhere. In fact, actions like onClick or a fetch callback don't occur within a render context, although the callback is often created within that context - the action callback happens after react finishes rendering (because javascript is single threaded, so the render function must complete before anything else happens).
Hooks comes as an alternatively to class based components, you should pick up one to your project and stick to it, don't mix it up. there are some motivation for the creation of hooks, as it's better stated at docs: hook motivation.
you can create hook functions apart, but they are meant to be consumed by components. it's something like using HOC (high order component) with class based components.
const myHook = () => {
[foo, setFoo] = useState('john')
// use effect for example if you need to run something at state updates
useEffect(() => {
// do something on foo changes
}, [foo])
return [foo, setFoo] // returning state and setState you can use them by your component
}
now you have a reusable hook and you can consume at your components:
const myComponent = (props) => {
[foo, setFoo] = myHook()
const handleFoo = () => {
// some logic
setFoo(someValue)
}
return (
<div>
<span>{foo}<span>
<button onClick={handleFoo}>click</button>
</div>
)
}
obs: you should avoid declare variables as var nowadays, pick const for most, and if it's a value variable (like number) that needs update use let.
When you are creating a hooks you must refer to the Rules of Hooks
You can only call hooks from a react functions.
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (learn about them on this page).
If you want to create a reusable hooks then you can create a custom hooks for your functions.
You can call as many functions inside a hooks.
For example, here I'm refactoring the request function as a hook.
export function useRequest(url, args) {
var userDispatch = useUserDispatch();
const fetcher = React.useCallback(() => {
return new Promise((resolve, reject) =>
fetch(url, args)
.then((response) => {
if (response.status === 401) {
logout();
reject();
}
resolve(response);
})
.catch(reject)
);
}, [url, args]);
return [fetcher, userDispatch];
}
and then consumes it.
function App() {
const [fetch, userDispatch] = useRequest("/url", {});
React.useEffect(() => {
fetch().then((response) => {
userDispatch({ type: "USER_REQUEST", payload: response });
});
}, []);
return <div>Hello world</div>;
}
Yes, you have to use Redux or MobX to solve this problem. You have to maintain isAuthenticated state in the global state of Redux or MobX. Then make an action that could be named like, toggleAuthState and pass is to the child component and toggle the state from there.
Also you can use functional components for this case. Class based components is not mandatory to use MobX or Redux. If you maintain a HOC as a Container then you can pass the actions and states to the child.
I am showing an example of using a container as a HOC:
// Container
import React from "react"
import * as actions from "../actions"
import ChildComponent from "../components/ChildComponent"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
const Container = props => <ChildComponent { ...props } />
const mapStateToProps = state => ({ ...state })
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Container)
Then in ChildComponent you can use your states and dispatch actions whenever you need.
I have a container component where I'm fetching the data via ajax operator from rxjs
const data = ajax(someUrl).pipe(map(r => r.response));
And in my componentDidMount I have
data.subscribe((data) => {
this.setState({ data });
});
// test.js
import React from 'react';
import { mount } from 'enzyme';
import { ajax } from 'rxjs/ajax'
import App from '../src/App';
describe('<App />', () => {
const wrap = mount(<App />);
const data = [{ 1: 'a' }];
const mock = ajax('http://url.com').pipe(map(() => data));
it('renders', () => {
console.log(mock.subscribe(x => x));
expect(wrap.find(App).exists()).toBe(true);
});
});
How do I go about mocking the response so that when I run the test it I can pass that data on to other components and check if they render?
All the testing examples I've found have been redux-Observable ones which I'm not using.
Thanks a lot!
First you need to understand that you should be testing one thing at a time.
Meaning that testing your async method execution should be separated from testing your components rendering proper content.
To test async methods you can mock your data and than mock timers in Jest.
https://jestjs.io/docs/en/tutorial-async
https://jestjs.io/docs/en/asynchronous
https://jestjs.io/docs/en/timer-mocks.html
with jest.useFakeTimers() and techniques mentioned above.
For testing component proper rendering use jest snapshots and e2e testing (can be done with ex. TestCafe)
To connect those approaches you need to design you app in a way that will allow you to:
The API you call in your component, should be external to component and be called from that external source (different file, different class, however you design it), so you can replace it in test.
Whole API should be modular, so you can take one module and test it without initializing whole API just for this case.
If you design your app in such manner, you can initialize part of the API with mock data, than render your component in test and as it will call mocked API, you can check if it renders what you expect it to.