How can I retrieve instance of children component - jestjs

I need access the instance of children component:
const wrapper = shallow(
<Provider store={store}>
<CustomForm />
</Provider>
);
I need to access CustomForm instance .. how can I do this?
I tried:
const instance = wrapper
.children(0)
.instance();

If your test needs to access the instances of child components then you will need to use mount() instead of shallow().
shallow() does not do a full DOM rendering and creates an instance for the root element if it is declared using a class (note that functional components never have instances), but it does not create instances for child components.
mount() does a full DOM rendering and creates instances for all components (root or child) declared using classes.
In your example you are using chilren(0) to access the ReactWrapper, alternatives are childAt(0), or something like find('Component').at(0).
Here is a simple working example:
import * as React from 'react';
import { mount } from 'enzyme';
class Component extends React.Component {
componentMethod = () => 'componentMethod called'
render() {
return (
<span>The Component</span>
);
}
}
test('get component instance', () => {
const component = mount(
<div>
<Component />
</div>
);
const componentInstance = component.find('Component').at(0).instance();
expect(componentInstance.componentMethod()).toBe('componentMethod called'); // PASSES
});

Related

passing function between separate components in react not between child and parent

I have a small question regarding passing functions between components that are not in parent/child relationship.
My structure inside App.
function App() {
return (
<div className="App">
<Header/>
<Pfl />
<Sdc/>
<Checkscan/>
</div>
);
}
Those 3 components have an on click function attached to a button i want the button from the pfl component to trigger all 3 on click functions.
When i click on the button in the pfl component i want to trigger the function running in the pfl component and the functions that are inside the Sdc,Checkscan component.
Whats the best way to do it and pass the functions from the other components so when i click the button inside the pfl component it will trigger all the methods from the other 2 components(Sdc,Checkscan)?
Or if I make a container that looks like this
export default function Apicontainer() {
return (
<React.Fragment>
<Pfl />
<Sdc />
<Checkscan />
<Button variant="contained">Start</Button>
</React.Fragment>
)
}
and in app.js i only have the Apicontainer.
How do i transfer all the functions to work in that button click Component
I just wrote some quick and dirty example code to show how you can share things between components via a parent component:
export default function Apicontainer() {
const [sharedState, setSharedState] = useState({sdc: null, checkScan: null})
function pflFunction() {
console.log('pflFunction')
// do your stuff here. I would update state with some reasonable data, and then pass
// the relevant data to the component that needs it. This is just an example.
setSharedState({sdc: 'sdcData', checkScan: 'checkScanData'})
}
return (
<React.Fragment>
<Pfl onClick={pflFunction} />
<Sdc data={sharedState.sdc}/>
<Checkscan data={sharedState.checkScan} />
<Button variant="contained">Start</Button>
</React.Fragment>
)
}
// Example of how to trigger a function inside a component (separate file):
export default function Sdc({data}){
const sdcFunction = useCallback(() => {
// implement the function you want to call here. useCallback makes sure to keep
// a stable reference to the function, so that you can rely on it in a useEffect
}, [])
useEffect(() => {
if(data){
// do something. This effect will run whenever the data or sdcFunction changes.
sdcFunction()
}
}, [data, sdcFunction])
return (
<div>your Sdc view code here</div>
)
}
For the record: If fplFunction is anything else than an onClick handler, you should make sure the function has a stable reference, using useCallback (as in the last component)

How can I render dynamically imported react components?

let's consider a list of modules imported arbitrarily as so :
/**
* modules is a list of absolute paths to modules exporting react components
*/
const getAllComponents = async(modules) => {
const components = [];
modules.forEach((moduleName) => {
try {
const module = await import(moduleName);
components.push(module.default);
}catch(err) {
console.warn(err.message)
}
})
return components;
}
and a parent react component in project and a random component exported from a disk based module:
// my-component.js
function MyComponent({moduleNames}) {
const [components, setComponents] = useState([]);
useEffect(() => getAllComponents.then(setComponents), []);
// rendering a random component assuming it exists
const RenderedComponent = components[0];
return (
<div>
{/* failling here: */}
<RenderedComponent />
</div>
)
}
// a-random-component.js (disk based module whose path is in moduleNames in above component props)
function RandomComponent() {
return (<div>propless component</div>)
}
I get the following error when compiling:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of `MyComponent`.
Is there a way to render arbitrary component in the dom without having to use the static import statement ?

How to make states from the keys of state object in react?

So I was trying to implement states in Child component from the Parent component state,as you can see in the code.But it gives me undefined as state value in child componenet.To test you can conosle.log(questions) and you will see undefined.
Is there a mechanism to setState in Parent component in some way such that the subsequent props in child components wil be able to get the state values?
Here is my code:
import React, { useEffect, useState } from "react";
import io from "socket.io-client";
const ENDPOINT = "http://localhost:5000";
let socket = io(ENDPOINT);
export default function Screen() {
const [qValue, setQuestion] = useState({personalInfo:{},questions:[]});
const [aValue, setAnswer] = useState({personalInfo:{},answer:""});
useEffect(() => {
socket.on("screenAns", (input) => {
setAnswer(JSON.parse(input));
});
console.log(aValue);
}, [aValue]);
useEffect(() => {
socket.on("screenQs", (arrayValue) => {
setQuestion(JSON.parse(arrayValue));
});
console.log((qValue));
}, [qValue]);
return (
<div>
<h2>Screen</h2>
<QuestionSingleMode value={qValue} />
</div>
);
}
function QuestionSingleMode(props){
var [questions,setQuestions]=useState(props.value.questions);
var [renderQuestion,setRenderQuestion]=useState("")
var [counter,setCounter]=useState(props.value.questions.length)
useEffect(()=>{
console.log(questions)
setRenderQuestion(questions[0])
},[renderQuestion])
function nextQuestion(){
setQuestions(questions.splice(0,1))
setRenderQuestion(questions[0])
setCounter(counter--)
}
return(
<div>
<h1>{renderQuestion}</h1>
<button onClick={nextQuestion}>{counter ? "next" : "finish"}</button>
</div>
)
}
Actually I solved the issue by changing the renderQuestion to props.questions in the useEffect() array.

Why does this return two sets of data?

I have built my firs full stack app that uses a react front end to communicate with a graphql server and surface data up from a mongoDB. When I look at the app from front end it looks like I am making two calls and return two sets of data (actually the same set twice).
Here is what I see in the dev tools console...
It looks to me like the first two are calls out and the last two are the data returns. If you look on the right, those are all to do with line 17 of BookList.js which is this...
console.log(this.props);
and this is the full code of that file....
import React, { Component } from 'react';
import { gql } from 'apollo-boost';
import { graphql } from 'react-apollo';
const getBooksQuery = gql`
{
books {
name
id
}
}
`;
class BookList extends Component {
render(){
console.log(this.props);
return(
<div>
<ul id="book-list">
<li>Book name</li>
</ul>
</div>
);
}
}
export default graphql(getBooksQuery)(BookList);
I am calling that BookList component with this code...
// components
import BookList from './components/BookList';
// apollo client setup
const client = new ApolloClient({
uri: 'http://localhost:5000/graphql'
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<div id="main">
<h1>Ninja's Reading List</h1>
<BookList />
</div>
</ApolloProvider>
);
}
}
export default App;
I am unsure why I am getting dbl calls or if that is the expectation. Any guidance at all is appreciated.

How to mount styles inside shadow root using cssinjs/jss

I'm trying to use https://material-ui.com/ components inside shadow dom, and need a way to inject those styles inside shadow dom. by default material-ui, which uses jss under the hood injects styles in the head of the page.
Is that even possible? Can anyone come with an example?
This is what my web component looks like, it is a web component that renders a react app that contains material-ui styles.
import * as React from 'react';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '#material-ui/styles';
import { create } from 'jss';
import { App } from '#myApp/core';
class MyWebComponent extends HTMLElement {
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('span');
const reactRoot = shadowRoot.appendChild(mountPoint);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot
});
render(
<StylesProvider jss={jss}>
<App />
</StylesProvider>,
mountPoint
);
}
}
customElements.define('my-web-commponent', MyWebComponent);
Setting the insertionPoint on jss to the actual react root inside the shadow root will tell jss to insert those styles inside that shadow root.
Using https://github.com/Wildhoney/ReactShadow to create shadow dom (you could also do it by hand as shown in previous answer), I created a small WrapperComponent that encapsulates the logic.
import root from 'react-shadow';
import {jssPreset, StylesProvider} from "#material-ui/styles";
import {create} from 'jss';
import React, {useState} from "react"
const WrappedJssComponent = ({children}) => {
const [jss, setJss] = useState(null);
function setRefAndCreateJss(headRef) {
if (headRef && !jss) {
const createdJssWithRef = create({...jssPreset(), insertionPoint: headRef})
setJss(createdJssWithRef)
}
}
return (
<root.div>
<head>
<style ref={setRefAndCreateJss}></style>
</head>
{jss &&
<StylesProvider jss={jss}>
{children}
</StylesProvider>
}
</root.div>
)
}
export default WrappedJssComponent
Then you just need to Wrap your app, or the part of your app you want to shadow inside <WrappedJssComponenent><YourComponent></YourComponent></WrappedJssComponenent>.
Be careful, some of the material-UI component won't work as usual (I had some trouble with
ClickAwayListener, maybe because it uses the parent dom, did not investigate more than that to be honest.
Popper, and everything that will try to use document.body as container will not have access to jss defined in shadow node. You should give an element inside the shadow dom as container.
There is also a whole page in the docs now (MaterialUI 5) that covers how to integrate MUI with a shadow-dom. You also might have to set Portal defaults not to target the dom. https://mui.com/material-ui/guides/shadow-dom/
When using #material-ui/core/CssBaseline with MUI, also emotion styles are being used. In order to support both legacy jss and emotion you can extend the accepted answer above with a CacheProvider like this:
import ReactDOM from 'react-dom/client'
import App from './App'
import createCache from '#emotion/cache'
import { CacheProvider } from '#emotion/react';
import { StylesProvider, jssPreset } from '#material-ui/styles';
import { create } from 'jss';
class ReportComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('div');
const emotionPoint = this.shadowRoot!.appendChild(document.createElement('div'));
const emotionCache = createCache({
key: 'report-component',
container: emotionPoint
});
const reactRoot = this.shadowRoot!.appendChild(mountPoint);
const root = ReactDOM.createRoot(reactRoot);
const jss = create({
...jssPreset(),
insertionPoint: reactRoot
});
root.render(
<StylesProvider jss={jss}>
<CacheProvider value={emotionCache}>
<App />
</CacheProvider>
</StylesProvider>
);
}
}
customElements.define('report-component', ReportComponent);

Resources