How to pass custom props to Single SPA child React apps? - javascript

I want to start my React microapp with props I'm passing from Single SPA (customProps). The only way I've figured out is:
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import App from './where/my/root/is.js';
function domElementGetter() {
return document.getElementById("mounting-node")
}
let EnhancedRootComponent = App; /* 1 */
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: EnhancedRootComponent, /* 1 */
domElementGetter,
})
export const bootstrap = [
(args) => {
/* 2 */ EnhancedRootComponent = () => <App myArgs={args.thePropsIWannaPass} />;
return Promise.resolve();
},
reactLifecycles.bootstrap,
];
export const mount = [reactLifecycles.mount];
export const unmount = [reactLifecycles.unmount];
This does work (I can see and use the passed props in my component) but I'm not completely OK with the fact that the root component changes in between calling singleSpaReact (1) and calling bootstrap(2). Would there be side effects to this that I'm not seeing now? Does anyone know a better approach for this?

You have this value inside the props variable without this reassign.
Check this out:
Root-config.js, file responsible for passing prop to microfrontend
import { registerApplication, start } from 'single-spa';
import * as isActive from './activity-functions';
registerApplication('#company/micro2', () => System.import('#company/micro2'), isActive.micro2);
registerApplication('#company/micro1', () => System.import('#company/micro1'), isActive.micro1, { "authToken": "test" });
start();
micro1 Root.tsx
import React from 'react';
export default class Root extends React.Component {
constructor(props: any){
super(props)
}
state = {
hasError: false,
};
componentDidCatch() {
this.setState({ hasError: true });
}
render() {
console.log(this.props)
return (
<div>test</div>
);
}
}
console.log output:
props:
authToken: "test" <---- props which you pass
name: "#company/micro1"
mountParcel: ƒ ()
singleSpa: {…}
__proto__: Object
for more advance usage
const lifecycles = singleSpaReact({
React,
ReactDOM,
loadRootComponent: (props) =>
new Promise((resolve, reject) => resolve(() =>
<Root {...props} test2={'test2'}/>)),
domElementGetter,
});

Related

unit test react container component

I am trying to unit test React container component using chai/sinon. This is how the component is defined:
import { connect } from 'react-redux';
import React, { Component } from 'react';
class TestComponent extends Component {
componentDidMount() {
const { loadData } = this.props;
loadData();
}
render() {
const { links, heading } = this.props;
return (
<p>
<h1>{heading}</h1>
<div>{links.map(link => link.label)}</div>
</p>
);
}
}
const mapStateToProps = state => ({
heading: state.heading,
links: state.links
});
const mapDispatchToProps = dispatch => ({
loadData: dispatch('LOAD_DATA')
});
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent);
I am writing a unit test to assert that container component has the props loadData, heading and links. Below is the unit test:
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import React from 'react';
import { shallow } from 'enzyme';
import TestContainer from './TestContainer';
const mockStore = configureStore({});
describe('TestContainer', () => {
const initialState = {
heading: 'A test component',
links: []
};
const store = mockStore(initialState);
it(
'renders correctly',
sinon.test(() => {
const component = shallow(
<Provider store={store}>
<TestContainer />
</Provider>
);
console.log(component.prop('heading')); // prints undefined
expect(component.prop('heading')).to.equal('A test component'); // gives AssertionError: expected undefined to equal 'A test component'
})
);
});
Above unit test fails and I get all props as undefined. Can somebody explain what am I doing wrong OR how a container component props should be asserted?
Thanks.

GlobalContext is not update in class component

I'm trying to create GlobalContext but when I update this inside Class Component it didn't work in Class Component it's showing value of globalState but it's not updating globalState via setGlobalState
GlobalContext
import React, { useState ,ReactNode} from 'react'
const initialMapContext: { globalState: any; setGlobalState: React.Dispatch<React.SetStateAction<any>> } = {
globalState: {},
// will update to the reducer we provide in MapProvider
setGlobalState: () => {},
};
const GlobalContext = React.createContext(initialMapContext );
interface Props { children?: ReactNode;}
export function GlobalProvider({children}:Props){
const [ globalState, setGlobalState ] = useState({name:'pranjal'});
return <GlobalContext.Provider value={{ globalState, setGlobalState }}>{children}</GlobalContext.Provider>;
}
export default GlobalContext
my code in classComponent is
static contextType = GlobalContext;
getData = async () =>{
const { globalState, setGlobalState } = this.context;
console.log(globalState); // pranjal
setGlobalState({name:"please login"});
console.log(globalState); // pranjal
// my rest code
}
but setGlobalState is not updating globalState value .
Although it works fine in the Functional component
Function.js
const { globalState, setGlobalState } = useContext(GlobalContext);
setGlobalState({name:'Please login'})
Rather than using static contextType = GlobalContext; , I would recommend you to use a Higher Order Component (HOC) which wraps a component with your GlobalContext.
Impementation:
GlobalContext
Export one HOC method called withGlobalContext as follows,
export const withGlobalContext = (Component) => (props) => (
<GlobalContext.Consumer>
{({ globalState, setGlobalState }) => (
<Component
globalState={globalState}
setGlobalState={setGlobalState}
{...props}
/>
)}
</GlobalContext.Consumer>
)
ClassComponent
Wrap the component with the HOC, to get the global context values as the props. And being available in the props, you can use it anywhere in the component, even outside render()
class ClassComponent extends Component {
componentDidMount() {
console.log(this.props.globalState)
console.log(this.props.setGlobalState)
}
render() {
return (
// Your JSX here
)
}
export default withGlobalContext(ClassComponent)
Also, I prefer exporting a custom hook, for using context in functional components, rather than using useContext
Implementation:
export function useGlobalContext() {
const context = useContext(GlobalContext)
if (context === undefined) {
throw new Error('You did something wrong')
}
return [context.globalState, context.setGlobalState]
}
Then in your functional component, use it like following:
function FunctionalComponent(){
const [globalState, setGlobalState] = useGobalContext()
return (
// Your JSX here
)
}
Cheers!

How do I make a client-side only component for GatsbyJS?

How do I create a component for Gatsby that will load on the client-side, not at build time?
I created this one and it renders with gatsby develop but not with the rendered server-side rendering
import React from 'react';
import axios from 'axios';
import adapter from 'axios-jsonp';
export default class Reputation extends React.Component<{}, { reputation?: number }> {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await axios({
url: 'https://api.stackexchange.com/2.2/users/23528?&site=stackoverflow',
adapter
});
if (response.status === 200) {
const userDetails = response.data.items[0];
const reputation = userDetails.reputation;
this.setState({
reputation
});
}
}
render() {
return <span>{ this.state.reputation?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
}
}
If you don't want the component to be bundled in the main js file at build time, use loadable-components
Install loadable-components and use it as a wrapper for a component that wants to use a client-side only package. docs
import React, { Component } from "react";
import Loadable from "#loadable/component";
const LoadableReputation = Loadable(() =>
import("../components/Reputation")
);
const Parent = () => {
return (
<div>
<LoadableReputation />
</div>
);
};
export default Parent;
before render this component, make sure you have a window, to detect if there is a window object. I would write a hook for that:
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return ()=> setIsWindow(false);
}, []);
return isWindow;
}
In the parent component check if there is a window object:
function Parent(){
const isWindow = hasWindow();
if(isWindow){
return <Reputation />;
}
return null;
}

Jest manual mock not getting executed for Higher Order Function

I am new to Jest, I am trying to mock my context HOC function to test my component
Here is my folder structure
Folder Structure
Basically I am trying to mock the homeContext.js file which is being used by my HomeScreen component. As per the docs I have created a __mocks__ folder under the contexts folder and created a homeContext.js file
Here are the contents of the files:
/contexts/__mocks__/homeContext.js
const mock = jest.fn().mockImplementation(() => {
console.log('Mocking');
return {
withHomeContext: jest.fn(() => {}),
};
});
module.exports = mock;
contexts/homeContext.js
export const withHomeContext = ChildComponent => props => {
console.log('Not Hitting');
return (
<HomeContext.Consumer>
{context => <ChildComponent {...props} homeContext={context} />}
</HomeContext.Consumer>
);
};
Here is how I am trying to use it in my scenes/home/index.test.js
jest.mock('../../contexts/homeContext');
import React from 'react';
import {mount} from 'enzyme';
import HomeScreen from './index';
describe('<HomeScreen /> renders', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<HomeScreen />);
expect(wrapper).toBeDefined();
});
})
This is how the component which I am testing uses it
import * as React from 'react';
//Context
import {withHomeContext} from '../../contexts/homeContext';
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
relatedItems: [],
};
}
render() {
//Some render logic
}
}
export default withHomeContext(HomeScreen);
Please help me why my __mocks__/homeContext.js never gets hit, it is hitting the actual file, which console logs ('Not Hitting')

How to write proper jest tests with HOC?

I’m trying to understand why my jest/enzyme tests are failing. I have a component that I use the compose function from redux in, structured as following:
class MyComponent extends Component {
//code here
}
export default compose(
connect(mapStateToProps),
)(MyComponent)
In my jest test, I do this:
Import { MyComponent } from ‘app/MyComponent’;
describe(‘<MyComponent />’, () => {
let component;
beforeEach(() => {
props = {
id: ‘23423’
}
component = shallow(<MyComponent {…props} />);
}
it(‘Loads correctly’, () => {
expect(component.state(‘isLoading’)).toEqual(true);
expect(component.find(‘InnerComponent’).length).toBe(1);
}
However, I get errors like "Cannot read property 'state' of undefined". I understand that using shallow rendering doesn't give me the exact structure that I need, but I'm not sure what else to try to get the right structure. I tried shallow-rendering the wrapped component and then finding the unwrapped component within it, like so, component = shallow(<MyComponent {...props} />).find('MyComponent').shallow();, with no luck. Any other suggestions would be appreciated.
`
Usually you want to test the component and not the integration of the component with redux:
class MyComponent extends Component {
//code here
}
export { MyComponent } // export the component
export default compose(
connect(mapStateToProps),
)(MyComponent)
Also on your test you would import the named export import { MyComponent } from '...' instead of importing the default: import MyComponent from '..'
import { MyComponent } from ‘app/MyComponent’;
describe(‘<MyComponent />’, () => {
let component;
beforeEach(() => {
props = {
id: ‘23423’
}
component = shallow(<MyComponent {…props} />);
}
it(‘Loads correctly’, () => {
expect(component.state(‘isLoading’)).toEqual(true);
expect(component.find(‘InnerComponent’).length).toBe(1);
}
}
If you want to test component interactions with your redux store you need to wrap your component with a <Provider />
import { MyComponent } from ‘app/MyComponent’;
import { Provider } from 'react-redux'
describe(‘<MyComponent />’, () => {
let component;
beforeEach(() => {
props = {
id: ‘23423’
}
component = shallow(<Provider><MyComponent {…props} /></Provider>);
}
You should definitely read the testing section of the redux documentation

Categories

Resources