Redux connect function with container component or presentation component - reactjs

We are having a discussion at work on what is the best practice for using with the redux connect function. Should we use the presentational component or the container component it's self by putting the presentation component inside the container component. I see some example online where people are putting the presentation component inside a container and some places outside the container It would be great if someone could tell the best practice.

I'm a Redux maintainer.
I would advise that you should default to defining both your plain component and the connect wrapper in the same file, for simplicity:
import React, {Component} from "react";
import {connect} from "react-redux";
import {addTodo, toggleTodo} from "./todos";
const mapState = (state) => ({todos: state.todos});
const mapDispatch = {addTodo, toggleTodo};
// Named export for the class
export class TodoList extends React.Component { /* */}
// Default export for the connected component
export default connect(mapState, mapDispatch)(TodoList);
Similarly, I would recommend going with a "feature folder"-type approach for structuring your files.
I have a saved chat log where I give my thoughts on the whole "container" and "presentational" concept that you might find useful. Similarly, note that Dan Abramov no longer recommends splitting "container" and "presentational" components.

Related

ReactWrapper::state() can only be called on class components Unit Testing Jest and Enzyme

Writing unit testing in react using jest and enzyme. While checking with a component state , it throws an error "ReactWrapper::state() can only be called on class components ".
import React from 'react';
import { mount } from 'enzyme';
import expect from 'expect';
import CustomerAdd from '../CustomerAdd'
import MUITheme from '../../../../Utilities/MUITheme';
import { ThemeProvider } from '#material-ui/styles';
describe('<CustomerAdd />', () => {
const wrapper = mount(
<ThemeProvider theme={MUITheme}>
<CustomerAdd {...mockProps}></CustomerAdd>
</ThemeProvider>
);
test('something', () => {
expect(wrapper.find(CustomerAdd).state('addNewOnSubmit')).toEqual(true);
});
});
In the above code CustomerAdd Component is class component.I don't what wrong with my code. Can any one help me out of this problem. Thanks in advance.
So your default export
export default withStyles(styles)(CustomerAdd);
exports functional(HOC) wrapper about your class-based component. And it does not matter if name of class and import in
import CustomerAdd from '../CustomerAdd'
are equal. Your test imports wrapped version and after calling .find(CustomerAdd) returns that HOC not your class. And you're unable to work with instance.
Short time solution: export class directly as named export.
export class CustomerAdd extends React.Component{
...
}
export default withStyles(styles)(CustomerAdd);
Use named import in your tests:
import { CustomerAdd } from '../CusomerAdd';
Quick'n'dirty solution: use .dive to access your underlying class-based component:
expect(wrapper.find(CustomerAdd).dive().state('addNewOnSubmit')).toEqual(true);
It's rather antipattern since if you add any additional HOC in your default export you will need to monkey-patch all related tests with adding appropriate amount of .dive().dive()....dive() calls.
Long-term solution: avoid testing state, it's implementation details.
Instead focus on validating what's been rendered. Then you are safe in case of lot of different refactoring technics like replacing class with functional component, renaming state/instance members, lifting state up, connecting component to Redux etc.

How do I write a storybook story for a component that has redux-connected component as grandchild?

We are building a Storybook UI library from our existing code base. The code wasn't written with component driven development in mind. There are many instances where a component renders descendants that are connected to the Redux store.
E.g., Parent (connected) -> Child (unconnected) -> Grandchild (connected)
Now if I'm building a story for Parent, I understand how to pass hard-coded data as a prop to an immediate child component in order to avoid Redux all together. However, I can't figure out how to do this when the connected component is more deeply nested.
Ideally I don't want to have to use Redux at all for stories, but even if I do initialize a Redux store and wrap the parent component in a Provider as described here, would this even work to connect the grandchild component?
Any ideas would be helpful.
When using storybook you can add a Decorator for all stories (see link for most updated API).
It is common to wrap your stories with the state manager store provider in order to not break the story avoiding "adding a store for each story".
// # config.js
import { configure, addDecorator } from '#storybook/react';
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from 'reducers/root.reducer';
const store = createStore(rootReducer);
addDecorator(S => (
<Provider store={store}>
<S />
</Provider>
));
configure(require.context('../src', true, /\.stories\.js$/), module);
Note that you can avoid connecting all your components with redux-hooks which in addition removes all the boilerplate code of redux.
React Redux now offers a set of hook APIs as an alternative to the existing connect() Higher Order Component. These APIs allow you to subscribe to the Redux store and dispatch actions, without having to wrap your components in connect().
If you want to solve the problem within your story file (and just fetch your store), use decorator like this:
import React from "react";
import { Provider } from 'react-redux'
import Parent from "./Parent";
import { store } from "../../../redux/store";
export default = {
title: "pages/Parent",
component: Parent,
decorators : [
(Story) => (<Provider store={store}><Story/></Provider>)
]
};
Sidenote, if this gives you the error useNavigate() may be used only in the context of a <Router> component., then you may need <MemoryRouter><Provider store={store}><Story/></Provider></MemoryRouter> (import {MemoryRouter} from 'react-router-dom')

Could not find "store" in the context of "Connect

I have an app that imports js-common library created by me. This library exports several components. When I try to import one of them in my app I get:
"Could not find "store" in the context of "Connect(ListComponent)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(ListComponent) in connect options."
If I move this component (the file) in my app it's working. So the problem is when I export from the library.
In my library I'm using babel to transform every file.
UPDATED
WORK
import ListComponent from "./ListComponent";
class App extends Component {
render() {
return (
</ListComponent>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
DOES NOT WORK
import ListComponent from "js-common";
class App extends Component {
render() {
return (
</ListComponent>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
The issue here, from what I can understand, is that you have used react-redux for state management which requires you to wrap your root component with the Provider component that ships with react-redux. It works in your app because you have probably provided the store in that app but when you export it, you don't have a store in whatever place you import it in.
If you are trying to write a library to be exported, then you would be better off not using redux...Or, at the very least, you need to provide a Provider to the top level component you intend to ship. The Provider is used like this.
<Provider store={store}>
<Component />
</Provider>
where store is the redux store you are using

Exposing React child component to JavaScript on webpage

I'm pretty new to rxjs, and trying to understand what's needed here to expose the Chat object in the Bot Framework, as I need to call some methods in it I'll add. I essentially need access to the created Chat component from the webpage, which right now has a BotChat.App. There's also a BotChat.Chat, but that doesn't seem to be the instance I need access to.
The following is used from the Bot Framework by calling BotChat.App({params});
That in turn creates a Chat component (eventually in App.tsx below). I need to basically expose the Chat instance that is used, as I want to modify it.
BotChat.ts (Complete)
export { App, AppProps } from './App';
export { Chat, ChatProps } from './Chat';
export * from 'botframework-directlinejs';
export { queryParams } from './Attachment';
export { SpeechOptions } from './SpeechOptions'
export { Speech } from './SpeechModule'
import { FormatOptions } from './Types';
// below are shims for compatibility with old browsers (IE 10 being the main culprit)
import 'core-js/modules/es6.string.starts-with';
import 'core-js/modules/es6.array.find';
import 'core-js/modules/es6.array.find-index';
And here in App.tsx note the Chat component used below. That is what I need to expose up through the webpage. A bit confused as to if it's exporting a "Chat" type as opposed to getting access to the instance of Chat being used in App.tsx. Hope this makes some sense :)
App.tsx (Complete)
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Chat, ChatProps } from './Chat';
import * as konsole from './Konsole';
export type AppProps = ChatProps;
export const App = (props: AppProps, container: HTMLElement) => {
konsole.log("BotChat.App props", props);
ReactDOM.render(React.createElement(AppContainer, props), container);
}
const AppContainer = (props: AppProps) =>
<div className="wc-app">
<Chat { ...props } /> //<--------------This is what I want to get
//access to on the webpage, which currently
//only uses BotChat.App() to initialize the
//web chat control. Not sure how to expose
//this _instance_ to App.tsx and then expose
//that instance to the webpage.
</div>;
Web Chat has a Redux store and a RxJS stream that you can expose to interact with the <Chat> component. In React, you can, but people usually don't expose any functions out of it. Short reason: the contract of a React component is props, not functions.
For RxJS stream that you can access the chat history, you can look at the backchannel sample. But it's a read-only stream.
For other interactivities, look at Store.ts to see what actions it is using. Then, expose the Redux store at Chat.ts (easy hack: thru window variable, better: make a callback props at <Chat>).
If you need more interactivities that is not in the existing Redux actions, feel free to add some more. For example, this pull request should give you a sense of injecting chat history.

Unwrap a component in React

This is so simple I hate to ask it, but after searching the react router docs and googling around I have found nothing. I have a component that is wrapped using withRouter when it is exported:
export default withRouter(MyComponent)
I want to test this component but I am getting the following when testing:
<Login router={[undefined]} />
I usually just import my components to test them individually using enzyme's shallow method. Is there a way to unwrap this component so I can import it for testing?
You will just need to export that component as well:
export const MyComponent
and in your test:
import { MyComponent } from '../src/components/MyComponent'

Resources