React Component with SubComponent - hot reload does not work - reactjs

I have an issue with my project with a simple SubComponent. If I do any changes inside that SubComonent it is not hot reloaded. I am not sure how to fix it.
I have components defined like this:
// Component.tsx
export const Component = () => {
return <div>Component</div>;
};
Component.SubComponent = function SubComponent() {
return <div>Hello From Sub</div>;
};
export const SubComponent1 = function SubComponent1() {
return <div>Hello From Sub1</div>;
};
And usage:
// App.tsx
<Component.SubComponent />
<SubComponent1 />
If I do changes in Component.SubComponent it is not reloaded, but if I do changes in SubComponent1 it works well.
I have tested this with the clean create-react-app install and it does not work there too.
Any ideas on how to fix this or what is wrong with the code? I found quite a lot of articles about sub components on the internet.

The approach seems bad to me.
export const Component = () => {
return <div>Component</div>;
};
Component.SubComponent = function SubComponent() {
return <div>Hello From Sub</div>;
};
Export components one by one.
export const Form = () => (<></>)
export const Item = () => (<></>)
import * as Form from '...'
<Form.Form>
<Form.Item>
</Form.Item>
</Form.Form>
I see that <Form.Form /> is ugly. Another way would be:
export const Form = () => (<></>)
export const Item = () => (<></>)
import { Form, Item as FormItem } from '...'
<Form>
<Form.Item>
</Form.Item>
</Form>

Related

Is there a reason to not import/export state in place of prop drilling or useContext in react?

Is it possible to do something like so?
MyComponent.jsx :
let myState;
let setMyState;
const MyComponent = () => {
const [myState, setMyState] = useState(35);
return (
<div> Some Jsx </div>
)
}
export myState
export setMyState
ComponentToShareState.jsx :
import myState from MyComponent.jsx
import setMyState from MyComponent.jsx
const MyComponentToShareState = () => {
const onClick = () => {
setMyState(100);
}
return (
<div>
<button onClick=(onClick)> Change my State </button>
<p>{myState}</p>
</div>)
}
Basically is there a reason that prop drilling exists rather than just exporting and importing state from component to component? Is it just for cleanliness of unidirectional flow? Would is actually break the app? Can you explain why this is not a used practice to me?

Testing mobx react observer with react usecontext - React, Mobx

New to testing mobx with React. I'm testing a a simple side navigation bar, open/closes on hamburger menu. I have set up a store for the Navbar:
export class Store {
toggle = false;
setToggle(bool){
this.toggle = bool;
};
};
decorate(Store, {
toggle: observable,
setToggle: action
});
export default createContext(new Store());
And here is the Navbar component:
export default observer(() => {
const store = useContext(Store); //SideNavStore is imported from mobx store folder
const handleOnClick = () => {
store.setToggle(false);
}
return(
<div data-testid="test">
<Drawer open={store.toggle} onClose={handleOnClick}>
<div role="presentation">
<List>
{routes.map(route => {
return <>
<ListItem button onClick={handleOnClick}>
<ListItemText primary={route.name}/>
</ListItem>
</>
})}
</List>
</div>
</Drawer>
</div>
);
});
This is App.js
export default () => {
return (
<div className="App">
<Navbar ></Navbar>
</div >
)
}
Test.js
describe('Navbar Interaction', () => {
describe('Inpsecting Navbar Contents', () => {
beforeEach(cleanup);
class Store {
sideNavToggle = true;
}
const DecoratedStore = decorate(Store,{
sideNavToggle: observable
});
const renderWithStore = (store) => {
render(
<Navbar />
);
}
it('Expect links are present', () => {
const store = new DecoratedStore();
const { getByText } = renderWithStore(store);
expect(getByText("My Dashboard")).toBeTruthy();
});
});
})
My test here fails because it can't find the text in the document, Drawer Open is set to false configured by the store.toggle. Trying to figure out how to inject the store or dummy store in the test, there are some tutorials about using Provider/Inject but that requires mobx-react and I believe they are deprecated; I would like to stick with mobx-react-lite. In renderWithStore, I'm stuck on how to pass the dummy store in the test. I could pass the store as a props but I believe that requires provider/inject which I don't want to do that if necessary. I rather import the store directly in the Navbar component using React.useContext. I don't see tests using React.useContext and Mobx Observer in the web. Has anyone encountered this type of scenario or can you provide a better approach? Also what is the best practice with using React and Mobx stores? Using React-testing-library for my tests. Your help is appreciated!
mobx-react-lite does advice users to prefer React.useContext over provider/inject.
Take a look at https://mobx-react.js.org/recipes-context
const storeContext = React.createContext<TStore | null>(null)
export const StoreProvider = ({ children }) => {
const store = useLocalStore(createStore)
return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}
As for testing, if you want the component to use custom store or a mock store, you could do it by passing-in the store to StoreProvider. This how Redux users test their components.
export const StoreProvider = ({ store, children }) => {
return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}

Define a functional component inside storybook preview

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.
const { openModal, closeModal } = useContext(ModalContext);
Example code on how I use this api's:
const TestComponent = () => {
const { openModal, closeModal } = useContext(ModalContext);
const modalProps = {}; //define some props
const open = () => {
openModal({...modalProps});
}
return (
<div>
<Button onClick={open}>Open Modal</Button>
</div>
)
}
And I wrap the component inside my ModalManager
<ModalManager>
<TestComponent />
</ModalManager>
This example works absolutely fine in my Modal.stories.tsx
Problem:
But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?
import {
Props, Preview, Meta
} from '#storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
isExpanded
mdxSource={`
/* source of the component like in stories.tsx */
`}
>
<ModalManager><TestComponent /></ModalManager>
</Preview>
I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.
You may have a utility HOC to render it inside a MDX file as below
HOCComp.tsx in some Utils folder
import React, { FunctionComponent, PropsWithChildren } from 'react';
export interface HOCCompProps {
render(): React.ReactElement;
}
const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
const { render } = props;
return render();
};
export default HOCComp;
Inside MDX File
import HOCComp from './HOC';
<HOCComp render={()=> {
function HOCImpl(){
const [count,setCount] = React.useState(180);
React.useEffect(() => {
const intId = setInterval(() => {
const newCount = count+1;
setCount(newCount);
},1000)
return () => {
clearInterval(intId);
}
})
return <Text>{count}</Text>
}
return <HOCImpl />
}}
/>

On click returns null instead of an object

It's really basic I guess. I'm trying to add onClick callback to my script & I believe I'm missing a value that would be responsible for finding the actual item.
Main script
import React from 'react';
import { CSVLink } from 'react-csv';
import { data } from 'constants/data';
import GetAppIcon from '#material-ui/icons/GetApp';
import PropTypes from 'prop-types';
const handleClick = (callback) => {
callback(callback);
};
const DownloadData = (props) => {
const { callback } = props;
return (
<>
<CSVLink
data={data}
onClick={() => handleClick(callback)}
>
<GetAppIcon />
</CSVLink>
</>
);
};
DownloadData.propTypes = {
callback: PropTypes.func.isRequired,
};
export default DownloadData;
Storybook code
import React from 'react';
import DownloadData from 'common/components/DownloadData';
import { data } from 'constants/data';
import { action } from '#storybook/addon-actions';
export default {
title: 'DownloadData',
component: DownloadData,
};
export const download = () => (
<DownloadData
data={data}
callback={action('icon-clicked')}
/>
);
So right now with this code on click in the storybook I'd get null and I'm looking for an object.
One of the potential issues I can see is that your handleClick function is stored as it is in-memory, when you import the component. That means you're keeping reference of something that doesn't exists and expects to use it when rendering the component with the callback prop.
Each instance of a component should have its own function. To fix it, move the function declaration inside the component. Like this:
const Foo = ({ callback }) => {
// handleClick needs to be inside here
const handleClick = callback => {
console.log("clicked");
callback(callback);
};
return <div onClick={() => handleClick(callback)}>Click me!</div>;
};
Check this example.
If this doesn't fix your problem, then there is something wrong with how you're implementing Storybook. Like a missing context.

Instance returns NULL for connected component on mount in Jest

I am relatively new to react and apologies for any terms that dont fit the jargon.
I am trying to test a prototype method of a connected component which consists of a ref variable, as below:
app.js
export class Dashboard extends React.Component { // Exporting here as well
constructor(props) {
this.uploadFile = React.createRef();
this.uploadJSON = this.uploadJSON.bind(this);
}
uploadJSON () {
//Function that I am trying to test
//Conditions based on this.uploadFile
}
render() {
return (
<div className="dashboard wrapper m-padding">
<div className="dashboard-header clearfix">
<input
type="file"
ref={this.uploadFile}
webkitdirectory="true"
mozdirectory="true"
hidden
onChange={this.uploadJSON}
onClick={this.someOtherFn}
/>
</div>
<SensorStatList />
<GraphList />
</div>
);
}
const mapStateToProps = state => ({
//state
});
const mapDispatchToProps = dispatch => ({
//actions
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
}
Here, SensorStatList and GraphList are functional components, also connected using redux.
After some research I have my test file to this level:
app.test.js
import { Dashboard } from '../Dashboard';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
const store = configureStore();
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
describe("Dashboard", () => {
let uploadJSONSpy = null;
function mountSetup () {
const wrapper = mount(
<CustomProvider>
<Dashboard />
</CustomProvider>
);
return {
wrapper
};
}
it("should read the file", () => {
const { wrapper } = mountSetup();
let DashboardWrapper = wrapper;
let instance = DashboardWrapper.instance();
console.log(instance.ref('uploadFile')) // TypeError: Cannot read property 'ref' of null
})
Can someone help me understand why this error
console.log(instance.ref('uploadFile'))
// TypeError: Cannot read property 'ref' of null
pops up? Also, if this approach is fine? If not, what are the better options?
wrapper is CustomProvider which has no instance, and ref is supposed to work with deprecated string refs.
In case a ref should be accessed on Dashboard, it can be:
wrapper.find(Dashboard).first().instance().uploadFile.current
In case input wrapper should be accessed, it can be:
wrapper.find('input').first()

Resources