React lazy load for a function, not a component - reactjs

I am working on a React v16 app and need to load a heavy (IMO) library for xlsx export of data.
I'm using functional components/hooks.
I understand and have used the <Suspense /> component and lazy for lazy loading modules. but since this item is not a component, it is simply a library function i need to run with an onClick event, i can't use lazy/suspense.
How can i lazy load this function only when needed? (writeXlsxFile)
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
//...
import writeXlsxFile from "write-excel-file";
//...
const fileCreate = async () => {
await writeXlsxFile(exportData, {
schema,
fileName: `file.xlsx`,
});
};
return(//...

JavaScript, and by direct association, React, supports Dynamic Imports.
So,
const fileCreate = async () => {
const {default: writeXlsxFile} = await import ('write-excel-file')
void await writeXlsxFile(exportData, {
schema,
fileName: `file.xlsx`,
});
}

Related

React/Redux global state management based on Socket.IO responses on client side?

I have an web app with multiple features like private messaging, buying, offers etc. I want to make it to work real time so I decided to use socket.io. I use redux for global state management, but I don't know how can I combine this with socket.IO. This was my idea:
1.Creating a file for socket handling with with exported functions to App.js to create a socket connection, sending and listening different data.
2.Whenever I got something relevant for example a notification or a buying request I update my redux state.
3.Finally in my components I will use useEffect for those global redux states and if it changes I will rerender my component based on my changed state.
Is this a good approach? If not which is a proper way to globally mangage my components based on socket recieved informations?
In general, depending on your needs I see nothing wrong with this approach. I will provide one actionable example here. My example will assume TypeScript as it's easier to transform to JavaScript (in case you do not use TypeScript) than the other way around.
In relation to your 1st question I would suggest to establish and pass Websocket connection as a context as you use it everywhere in your application and create custom hook to use the connection anywhere:
import React, { createContext, FunctionComponent, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import io from 'socket.io-client';
export const WebsocketContext = createContext<SocketIOClient.Socket | null>(null);
const WebsocketProvider: FunctionComponent<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => {
const [connection, setConnection] = useState<SocketIOClient.Socket | null>(null);
const options: SocketIOClient.ConnectOpts = useMemo(() => ({}), []);
useEffect(() => {
try {
const socketConnection = io(process.env.BASE_URL || '127.0.0.1', options);
setConnection(socketConnection);
} catch (err) {
console.log(err);
}
}, [options]);
return <WebsocketContext.Provider value={connection}>{children}</WebsocketContext.Provider>;
};
export const useWebsocket = (): SocketIOClient.Socket | null => {
const ctx = useContext(WebsocketContext);
if (ctx === undefined) {
throw new Error('useWebsocket can only be used inside WebsocketContext');
}
return ctx;
};
export default WebsocketProvider;
Above we create context which has type SocketIOClient.Socket and defaults to null, as when connection is not yet ready we must assign default value. Then we create Websocket provider as FunctionComponent which accepts children(s) and holds connection state with useState hook eventually returning provider with Websocket connection. I also mention SocketIOClient.ConnectOpts as depending on your needs you might want to provide connection options; either statically or dynamically when using the hook. Furthermore useEffect hook which will try to establish the connection or throw an error. The only dependency which will rerun this hook is connection options in case they will dynamically change.
Finally we have custom hook useWebsocket which we can import in any component and use inside our context provider. Simply wrap your root component (or any other hierarchy level) with context provider to provide the context like in the example below:
import React, { FunctionComponent } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import routes from './App.routes';
import WebsocketProvider from './websocket.context';
const App: FunctionComponent = () => {
return (
<WebsocketProvider>
<Router>
<Switch>
{routes.map((route) => (
<Route key={uuid()} {...route} />
))}
</Switch>
<Redirect to='/' />
</Router>
</WebsocketProvider>
);
};
export default App;
In relation to your 2nd question you can for example have ´useEffect´ hook to react when connection emits and update your Redux (or other global state management) store. Here I also use Elvis operator to check if the connection is not ready yet (if its not ready yet as null the useEffect hook will re-render on socket connection change when its ready):
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useWebsocket } from './websocket.context';
const Foo: FunctionComponent = () => {
const dispatch = useDispatch();
const socket = useWebsocket();
useEffect(() => {
socket?.on('myEmitEvent', (data: myEmitData) => {
dispatch(myStoreAction(data));
});
return () => {
socket?.off('myEmitEvent');
};
}, [socket, dispatch]);
return ...
};
export default Foo;
In relation to your 3rd question as you mention you can use useEffect hook or more simply useSelector hook from react-redux package which automatically captures your state changes triggering re-render on necessary elements.
In short, your idea hits the ballpark and I hope that with this brief actionable example you will be able to refine solution which works for you.

Getting global config to a react hook

I have a React App, that talks to several REST APIs.
I have refactored my app from redux-thunks to use react-query for the business logic of calling the APIs.
Watching videos on react-query, it was advised to abstract this into a custom hook.
So, for example:
//
// useTodos.js
import { useQuery } from 'react-query';
import { TodoApi } from 'my-api-lib';
import config from '../config';
const todoApi = new TodoApi(config.TODO_API_BASE_URL);
const useTodos = (params) =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
I have another App where I could use these hooks to also talk to the REST APIs. So I'd like to move the hooks into a common library. But the config is provided by the client. How do I get the config (TODO_BASE_API_URI) or even the "todoApi" instance, to the custom hook from the client?
In Redux I essentially dependency-injected the TodoApi instance at startup with "thunk with extra argument"
Is there a "hooky" way to get the global config to my custom hook?
The library (I assume it's my-api-lib) should export a function that expects the url (or any other config), and returns the useTodoApi hook.
In your common library:
import { useQuery } from 'react-query';
import { TodoApi } from './TodoApi';
export const createUseTodoApi = url => {
const todoApi = new TodoApi(url);
return params =>
useQuery(
[todo, params],
() => todoApi.fetchTodos(params)
);
}
In your apps:
import { createTodoApi } from 'my-api-lib';
import config from '../config';
export const useTodoApi = createUseTodoApi(config.TODO_API_BASE_URL);

unit testing custom hook for getting data, redux useSelector and dispatch

I have been testing a custom hook for getting data from redux state and all is working nice within the app but I am having some issues with testing coverage, namely with unit testing my custom hook. here is my testing code:
useGetHomes custom hook:
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getHomes } from '../pages/Homes/actions';
const useGetHomes = () => {
const dispatch = useDispatch();
const homes = useSelector(state => state.homes);
useEffect(() => {
dispatch(getHomes());
}, [dispatch]);
//TO DO - extend more
return {
homes
};
};
export default useGetHomes;
current test file:
import React from 'react';
import { mount } from 'enzyme';
import useGetHomes from '../useGetHomes';
//for testing
const TestHook = ({callback}) => {
callback();
return null;
};
const testHook = callback => {
mount(<TestHook callback={callback}/>);
};
//end for testing
describe('useGetHomes', () => {
let homes;
beforeEach(() => {
testHook(() => {
homes = useGetHomes();
});
});
it('should do first test', () => {
console.log('first test')
console.log(homes)
});
});
my current test gives me the following error:
could not find react-redux context value; please ensure the component is wrapped in a Provider
I tried wrapping it in a Provider as error is clear but I get the following error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
I guess this is also clear as custom hook returns an object, so is there anything I could do to unit test my custom hook ? On the other hand my integration tests on the components that use the hook are good and working properly.
You have two options:
Mock useDispatch and useSelector if you want to test your Hook in isolation
Add a dummy component with your custom Hook inside it and wrap with a Provider etc if you want to test how your Hook interacts with the component(s) it will be used in
For anyone looking for a solution to this now, #testing-library/react offers a solution: https://react-hooks-testing-library.com/usage/advanced-hooks
Redux is simply a React Context, so the above strategy should work for testing a hook that requires any type of provider, be it a custom one in your app, redux, or a drag drop context.

Testing react component that is dependent on Stateful Context Providers chain in React Testing Library

I'm aware of this, but it's not my case.
Example:
<AuthProvider>
<SessionProvider>
<AnotherProvider>
<OneMoreProvider>
<MyComponent />
All of these providers are actually regular React Components with state and effects, that fetch some data via GraphQL and pass that data as a 'value' prop to MyContext.Provider in return statement.
This enforces me to create lots of mocks for modules that are being used in all of these providers, just to render my own component in testing env.
Any thoughts about what can be done to avoid creating so many mocks?
You can create a helper test lib with a custom render function that wrap your component with the contexts then export all react testing library methods from there
- test/lib.jsx
import React from 'react';
import { render as reactRender } from '#testing-library/react';
export * from '#testing-library/react';
export const render = (MyComponent, options) => {
return reactRender(
<AuthProvider>
<SessionProvider>
<AnotherProvider>
<OneMoreProvider>
{MyComponent}
</OneMoreProvider>
</AnotherProvider>
</SessionProvider>
</AuthProvider>,
options
)
}
Then use this helper lib to import test functions instead of using #testing-library/react directly
import { render } from 'test/lib'
import MyComponent from './MyComponent';
test("My component", () => {
const { getByTestId, ... } = render(<MyComponent>);
...
});

Unit Testing dynamically imported React Component

I have a very simple React component that uses react-loadable to dynamically import another component. The code looks something akin to the following:
import React from 'react';
import Loadable from 'react-loadable';
import LoaderComponent from 'path/to/LoaderComponent';
export default outerLoadableComponent = Loadable({
loader: () => import('path/to/innerComponent'),
loading() {
return <LoaderComponent />
}
});
I am attempting to test this component by using Enzyme to mount outerLoadableComponent, which creates a wrapper around outerLoadableComponent where I can see that the LoaderComponent wrapping it has the loadingState set to true. However, I am stuck at the point where the inner import does not resolve. It seems to be a promise that would only resolve should the import actually go through, however even with some timeouts, it does not work:
const expect = chai.expect;
chai.use(sinonChai);
it('should render the loading state, and innerComponent', (done) => {
const wrapper = mount(
<outerLoadableComponent />
);
expect(wrapper.loading).to.be.true;
setTimeout(() => {
expect(wrapper.loading).to.be.false;
expect(wrapper.loaded).to.exist; // loaded state returns a function
expect(wrapper.find(innerComponent)).to.exist;
done();
}, 500);
});
My babel-rc has dynamic-import-node so running this outside of the test works all fine. But there seems to be no clear/documented way of mocking (with sinon) the results of an import promise. Any ideas?

Resources