albertogasparin / react-magnetic-di Goto Github PK
View Code? Open in Web Editor NEWDependency injection and replacement for React components and hooks
License: MIT License
Dependency injection and replacement for React components and hooks
License: MIT License
Hi, I'm trying to use react-magnetic-di in a project created using https://github.com/facebook/create-react-app along with Jest and storybook snapshots.
For some reason the mocking does not appear to work. Apologies in advance if I have misunderstood something, I do not understand the whole development stack very well.
Is the Babel plugin critical for the functioning of magnetic-di, or is it optional? It seems create-react-app out of the box does not allow custom Babel plugins.
In any case, I tried ejecting from create-react-app and created babel.config.json
with:
{
"plugins": [["react-magnetic-di/babel-plugin", {"forceEnable": true}]]
}
Here's my test case:
import {di, DiProvider} from 'react-magnetic-di';
import React from 'react';
const useBla = () => 'real';
const useBlaMock = di.mock(useBla, () => 'mocked');
const Bla: React.FC = () => {
di(useBla);
const value = useBla();
return <div>{value}</div>;
};
storiesOf('DiTest', module).add('bla', () => (
<DiProvider use={[useBlaMock]}>
<Bla />
</DiProvider>
));
// ^^^^^^^^^^^ test case
The snapshot output is:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots DiTest bla 1`] = `
<div>
real
</div>
`;
So the mocked return value of mocked
was not used.
Whenever <DiProvider global />
is used and the tree throws an error, we skip the unmount use effect and replacements between tests are not cleared.
We should convert DiProvider to a class and add componentDidCatch
to call globalDi.clear()
Seems like this code breaks eslint on the JSX tag:
// @flow strict-local
import React from 'react';
import { shallow } from 'enzyme';
import type { Props } from './types';
import CommitWarningNotification from './view';
test('Should render commit warning notifications', () => {
const props: Props = {
show: true,
successfulCommitCount: 8,
failedCommitCount: 7,
onClose: () => {},
};
const tree = shallow(<CommitWarningNotification {...props} />);
expect(tree).toMatchSnapshot();
});
Cannot read property 'name' of undefined Occurred while linting ....
Probably due to missing check on no-extraneous
JSXOpeningElement:exit
for diIdentifier
presence
Hi, thank you for this amazing work!
I want to do dependency injection on production mode (as for mocking data, for now), and i have added these lines in my .babelrc
plugins: [
["react-magnetic-di/babel-plugin", { "forceEnable": true }],
],
the problem was in production mode, di
was not applied. And if it helps, i cannot import things from react-magnetic-di
even after i put those line above in my .babelrc
.
is there something i did miss?
Thank you.
Currently, even on prod builds, we have variable assignment and one function call (a pass through). We can optionally strip the entire di(...)
so the performance cost in prod is actually zero.
Hi, I encountered a specific scenario in which I need to make a prop provided by the mocked hook dynamic, here is the code:
passedProps: SelectRoomPageUIProps & { selectResort: ResortComparisonDropdownProps },
) => {
const [selectedPropertyId, setSelectedPropertyId] = React.useState('2');
const [selectedCardId, setRoomRateCardId] = React.useState('2');
const onChange = useCallback(
(id: any) => {
setSelectedPropertyId(id);
},
[setSelectedPropertyId],
);
const useResortControllerDi = injectable(useResortController, () => ({
apolloResult: {
loading: false,
} as ApolloResult,
uiProps: {
...passedProps.selectResort,
selectedPropertyId,
onChange: onChange,
},
}));
return (
<DiProvider use={[useResortControllerDi]}>
<SelectRoomPageUI
{...passedProps}
/>
</DiProvider>
);
};
As you can notice the value of selectedPropertyId
is dynamic, the reason I ended up needing to mock the hook is because there's a component nested under SelectRoomPageUI
that consumes from the hook directly(the idea was to avoid prop drilling), thanks to your library I fixed it and the story now renders the list of values being mocked properly on the nested component, but when I trigger the onChange
method it does update and if I use useEffect
to watch the selectedPropertyId
value it reflects it properly on the console but the value returned by useResortControllerDi
never updates.
If I'm doing this wrong please feel free to correct me, and thanks again for making this library it saved me a lot of time of r&d.
The rule contains a few react hooks - useState
, useContext
, useReducer
- but not any other.
Is it a feature or a bug? I can understand if someone wants to override useEffect
, but am a little concerned about useRef
In fact useState
is overridden the most.
However adding any exemptions to the rule may cause friction to the testing process as something you might expect to be overridden will not be.
Hi there, loving this package, thank you! I have a question - am I right in assuming this sentence here means that you can make the di()
call in a parent component and it will replace all usages in child components?
In the example above we replace all
Modal
anduseQuery
dependencies across all components in the tree with the custom versions.
I'm trying to create a wrapper component for storybook so that all the di code can be contained in the story itself, and am testing this in a wrapper component, but am thinking I might've misinterpreted the sentence above. For example, this is my storybook for the component.
// Header.stories.jsx
import { useState } from "react";
import { DiProvider, injectable } from "react-magnetic-di";
import Header from "./Header";
const useStateDi = injectable(useState, () => ["hello", () => null]);
export default {
title: "My Components/Header",
component: Header,
};
const Template = () => (
<DiProvider use={[useStateDi]}>
<Header />
</DiProvider>
);
export const Default = Template.bind({});
And then, in my actual component, if I'm looking to replace useState for example, can I call di(useState)
in a component that wraps the actual useState
usage?
import { useState } from "react";
import { di } from "react-magnetic-di";
const ChildComponent = () => {
const [title, _setTitle] = useState("Hi there");
return <h2>{title}</h2>;
};
const Header = () => {
di(useState);
return <ChildComponent />;
};
export default Header;
Hi Alberto,
thanks for the project, looks very cool!
I was trying it out on a React app bootstrapped with the TypeScript template and when I import the library like so:
import { di } from "react-magnetic-di/macro";
I get: Cannot find module 'react-magnetic-di/macro' or its corresponding type declarations.
I guess it makes sense since they seem to be missing when you import it like that.
Anyway, using the library with TS doesn't seems to be working properly for me.
For example if you include the doc example for testing with RTL it complains about missing required children prop (as per your types):
const { container } = render(<MyComponent />, {
wrapper: (p) => <DiProvider use={[ModalOpenMock, useQueryMock]} {...p} />,
});
Should be:
const { container } = render(<MyComponent />, {
wrapper: (p: {children?: React.ReactNode}) => <DiProvider use={[ModalOpenMock, useQueryMock]} children={p.children} />,
});
It's just a documentation issue but will probably trip other users.
I wasn't able to run it in the end unfortunately but maybe because of the first error above that results in Warning: Seems like you are using react-magnetic-di without Babel plugin...
if I import without /macro
and Cannot find module 'react-magnetic-di/macro' from '/Users/XXXX/repos/repo/src/components/AdminMessages'
if I add //@ts-ignore
to it.
Thanks!
Magnetic is mostly used in a preconfigured testing environment with so many things expected to be "global". Having this in mind what would one think about adding alternative way to configure di
- via global configuration, not ContextApi
That will allow using di
for plain functions, not components/hooks. One can do (and doing) this today by testing such functions as hooks, which is not the correct way.
All required is to wire a configuration variable to https://github.com/albertogasparin/react-magnetic-di/blob/master/src/react/consumer.js#L14
โ A short story "How I met code erosion"
Desired goal: be notified about missing mocks as well as no longer required ones.
Proposal: implement tracking of injectable usage:
injected
got di-ed
.
di
(if eslint by any reason is not working). Still happening once-twice a week in my team.injected
functions(only) got executed
expected.toHaveBeenCalled
and which are not.di
-ing something on environment level (intl), or some tests where stuff is excepted not to be called.I am not sure that Magnetic can do what is required due to the need of integration with the test runner, or at least with stubs (aka jest.fn
). Thus desired goal might be achievable only by building an abstraction on top of Magnetic.
Deciding what should be build inside Magnetic and what should be outside - is the task for this issue.
Error: "cannot call 'some' of undefined" from "exhaustive deps"
Thanks!
The DiProvider
prop-types
use definition prevents forward refs from being used, and thus produces a warning when using the provided withDi
utility. This issue is more problematic in environments that treat any console warnings as errors.
withDi
utility| src/app/examples.js
export const DefaultApp = withDi(App, [injectable(Foo, DefaultFoo)]);
| src/foo/examples.js
// DefaultFoo will produce the warning
export const DefaultFoo = withDi(Foo, [
injectable(Container, ({ children }) => <div>injected {children}</div>)
]);
// or: export const DefaultFoo = withDi(Foo);
Reproducible example here: react-magnetic-di-sandbox
prop-types
warningsprop-type
warning:Warning: Failed prop type: Invalid prop
use[0]
of typeobject
supplied toDiProvider
, expectedfunction
.
at DiProvider (https://yh3sc.csb.app/node_modules/react-magnetic-di/lib/esm/react/provider.js:29:23)
at withDi(App)
This recommended form for enzyme doesn't actually work for some reason:
const container = mount(<MyComponent />, {
wrappingComponent: DiProvider,
wrappingComponentProps: { use: [ModalOpen, useQuery] },
});
No errors, the context is just not provided.
Works okay if you do this though:
const container = mount(
<DiProvider use={[ModalOpen, useQuery]}>
<MyComponent />
</DiProvider>
);
Looks like node 10.8 has issues so better force 10.16 to be the minimum one in package json
The babel plugin 'cannot be found' at the moment if using react-magnetic-di/babel
in babel config.
Workaround: reference the full babel plugin path:
// ... other stuff like presets
plugins: [
// ... other plugins
'react-magnetic-di/lib/cjs/babel',
],
Dependency injection doesn't work properly when a component is being rendered via react-test-renderer
:
import React, { useState } from 'react';
import { create } from 'react-test-renderer';
import { DiProvider, injectable } from 'react-magnetic-di';
import { Input, useTheme } from '../input';
const useThemeDi = injectable(useTheme, () => {
return useState({ color: '#B00' });
});
describe('Input', () => {
it('should render with theme', () => {
const tree = create(
<DiProvider use={[useThemeDi]}>
<Input />
</DiProvider>
).toJSON();
expect(tree).toMatchInlineSnapshot(`
<input
placeholder="Type..."
style={
{
"border": "1px solid #777", // <-- Wrong, should be #B00
}
}
/>
`);
});
});
});
Supposedly due to how react-test-renderer
's create
works, the di
consumer does not get a value from a Provider
in
react-magnetic-di/src/react/consumer.js
Line 10 in 4194976
react-magnetic-di/src/react/context.js
Lines 4 to 6 in 4194976
Replacing const { getDependencies = (v) => v } = Context._currentValue || {}
with const { getDependencies = (v) => v } = useContext(Context)
works, but breaks the ability to use di
in both functional and class components.
Would you consider a major version release to remove the hack in di
function and providing di
and useDi
separately?
I'm seeing the following error whenever I use <DiProvider />
inside jest
:
Invalid hook call. Hooks can only be called inside of the body of a function component.
I thought I was doing something wrong but I tried this by building an MVP:
// jest spec
// SomeTest.spec.js
import { useState } from 'react'
import { DiProvider, di, injectable } from 'react-magnetic-di'
import { render } from '@testing-library/react'
import '@testing-library/jest-dom'
function useSomeHook() {
return useState('dummy')
}
function Testing() {
di(useSomeHook)
const [dummy] = useSomeHook()
return <div>{dummy}</div>
}
const useSomeHookDi = injectable(useSomeHook, () => ['testing'])
function WithDep() {
return (
<DiProvider use={[useSomeHookDi]}>
<Testing />
</DiProvider>
)
}
test('loads and displays input text', async () => {
const { getByText } = render(<WithDep />)
expect(() => getByText('testing')).not.toThrow()
})
In this example Component
is injected via HOC, but react-magnetic-di/no-extraneous
eslint rule considers it to be injectable via DI
export const withNestingOptimisation = <Props: { children?: Node, ... }, Ref>(
Component: AbstractComponent<Props, Ref>,
) => {
const WithNestingOptimisation = forwardRef<Props, Ref>(
({ children = null, ...rest }: Props, ref) => {
// "Component" here should not be DI'ed
di(Component, useShouldNestedElementRender);
return true ? (
<Component {...rest} ref={ref}>
{children}
</Component>
) : (
children
);
},
);
return WithNestingOptimisation;
};
I have a sneaky suspicion that I might be doing something wrong, but I copy/pasted the examples on the readme and shooting console.log(debug(ModalOpenDi));
seems to always come back with undefined
. I am using react native and storybook 7, so I wonder if maybe my setup is not supported somehow?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.