Is it bad practice to directly import actions into components? - javascript

Is something like this bad practice?
SendInfoButton.js
import React from 'react';
import { sendInfo } from '../actions/index';
export const SendInfoButton = ({currentUser}) => (
<div>
<button onClick={() => sendInfo(currentUser)} />
</div>
)
actions/index.js
import { store } from '../reducers/index';
import { SEND_INFO } from '../constants/index;
export const sendInfo = (currentUser) => store.dispatch({type: SEND_INFO, payload: currentUser})
It seems more efficient to import actions directly into the components this way, as opposed to using mapDispatchToProps and passing down actions to components that won't use them. I'm also more inclined to import actions like this because I already have components with a large number of props and would rather not add to that.

Importing the action creator, like import { sendInfo } from '../actions/index';, is fine - that's how you're supposed to do it.
However, you should then use connect to "bind" the action creators so that they access the correct store instance at runtime and dispatch the action automatically. This can be made shorter by using the "object shorthand" syntax - just pass an object full of action creators as the second argument to connect, like:
export default connect(null, {sendInfo})(SendInfoButton);
Similarly, you shouldn't import the store directly. As #estes said, that locks your code into the same "production" store instance all the time, and makes it harder to test or reuse your code.

Related

How to write unit test case for componentDidMount() and export default connect(null, updateProps)(<ComponentName>) with JEST/Enzyme in React?

I have created simple react component and write test cases of components that are working correctly. I have got coverage report for the test cases.
Now, I have added react redux in my other component. this component contains componentDidMount() and export default connect(null, updateProps)(ComponentName) methods. I need to write unit test cases for these methods.
Please refer to the below code sample,
class MyComponent extends Component {
componentDidMount = () => {
//some code here
)
handleSignIn = (e) => {
//some code here
}
render() {
return (
<div>
<form onSubmit={this.handleSignIn}>
<Input
type="text"
name="inputText"
placeholder="Text"
autoFocus
required
/>
</form>
</div>
);
}
const updateProps = (dispatch) => {
return {
//some code here
};
};
export default connect(null, updateProps)(MyComponent);
In your code you have two things:
class MyComponent
and
const thisIsBasicallyAnotherComponent = connect(null, updateProps)(MyComponent);
So if you want to test your component you basically have two options. You can test your component wrapped and connected to the redux store or you can write a simple unit test for your class component as it is.
What I would recommend doing is to export your class component
- class MyComponent extends Component { // replace this
+ export class MyComponent extends Component { // with this
And then you can test your React component with Jest like any other component.
test('Link changes the class when hovered', () => {
const component = renderer.create(
<MyComponent {...mockProps} /> // !! keep in mind that you have to manually pass what you have in `updateProps` because the component is not connected to Redux store anymore
);
// ... write your test and expectations here
});
Otherwise, you can test your connected component (what is exported by default) but then you would have to wrap the component in Redux provider in order to test it.
You can find more information about testing here:
How to test components
How to test connected components
You can use Provider from react-redux or redux-mock-store to avoid need to use real reducer:
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import MyComponent from './MyComponent.jsx';
const mockStore = configureStore([thunk]);
it('does something on mount', () => {
// let's mock some Redux state
const store = mockStore({ slice1: { id: 2, name: '11' } });
mount(<Provider store={store}><MyComponent /></Provider>);
expect(store.getActions()).toContainEqual({
type: 'some-type',
payload: ....
});
});
But this is that easy only to simple actions. What if you use redux-thunk and there is some loading? There are 2 ways:
Pass redux-thunk middleware to mockStore and mock network, say, by using mock-fetch or nock. Easier to set up, but also it might be overkill if you already test your Redux directly, repeating tests for "loading failed", "loading takes long" etc also to your component would mean double work.
You can mock ../yourPath/actions.js so every action there would be plain object, not a thunk. I typically go this way.
But what about "exporting unwrapped component so we could test component in isolation, without Redux"? You see, it was working when connect was the only possible API. But now with hooks like useSelector, useDispatch, useStore in mind, it's way more reliable to make tests for "my component IN Redux" first. Otherwise with "double exports" approach we may find out than converting component from class to function means way more work on patching tests, not on component itself.

Pass react-redux store and dispatch functions via props?

The following React component is given:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { store, StoreState } from "../../redux/actions";
import { setBackgroundAction } from "../../redux/title.actions";
import "./Loader.scss";
interface ReduxProps {
bgClass: string;
}
interface Props extends ReduxProps {
bgChange?: boolean;
}
export default function Loader(props: Props) {
const [bgClassOld, setBgClassOld] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const { bgChange, bgClass } = props;
if (bgChange) {
setBgClassOld(bgClass);
dispatch(setBackgroundAction("bg-white"));
dispatch(setBackgroundAction(bgClassOld));
}
});
return (
<div className="d-flex">
<div className="loader">
<img src="/loadscreen.gif" />
</div>
</div>
);
}
// function mapping(state: StoreState): ReduxProps {
// return {
// bgClass: state.title.backgroundClass,
// };
// }
This is more a theoretical question to see how to actually do the following change:
The component Loader will be imported from another npm package (shared components).
My problem is that I have a redux state in the current implementation included (changed it from Class to Functional component, so thats mapping() is still in there).
As I only import the component in my "main" client, I will not have the whole redux setup in place. So I think I need to pass the store and the dispatch functions via props.
So should I create a prop store for my component, where I pass the redux store when I import the shared component?
Do I also create two props for each dispatch functions?
Does is make sense or would there be a better approach?
You generally shouldn't import the Redux store directly into components. The hooks allow your component to access whatever Redux store has been injected into the component tree by a <Provider>.
You also don't need to pass dispatch as a prop. Any component can call useDispatch(), and dispatch actions to whatever Redux store is actually being used.
If I understand your question, you're planning on importing this component into an existing app, and it sounds like that app is already configured to use (React-)Redux with a <Provider> at the top. If that's the case, then you don't have to do anything else special to make this work. Just call the React-Redux hooks in any of your components.

Initiate data with redux better approach

hello guys I am trying to fetch initial data for redux store from given api !
here is my first approach from using store.dispatch:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer";
import { fetchNews } from "./actions";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
store.dispatch(fetchNews());
export default store;
and second approach is dispatching action from the component lifecycle method like this:
import React from "react";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
import { useSelector, useDispatch } from "react-redux";
import New from "./New";
import { fetchNews } from "../store/actions";
const Main = () => {
const news = useSelector(state => state.news);
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(fetchNews());
}, [dispatch]);
return (
<>
{news.length > 0 ? (
news.map(data => (
<New key={data.id} subTitle={data.created_at}>
{data.title}
</New>
))
) : (
<p>News not found</p>
)}
</>
);
};
OK both method works but i really want to know what is the better approach here or is there any other better method! thank you!
Dispatching action from the component lifecycle method is the recommended approach and is what should be followed in order to maintain the separation of concern design on which Redux is based on please check the attached image.
The second one. The file in which you create the store should always be all about creating the store and exporting it as is.
By the way, on your useEffect, try removing the dispatch from the variables array:
React.useEffect(() => {
dispatch(fetchNews());
}, []);
This way it will fetch the data on component mount, just like ComponentDidMount.
I wouldn't say dispatching from component lifecycle is the recommended approach , it certainly is used by many developers, but it couples in you component with data fetching (is that a good idea) ? Or should your component not know about how data is fetched and just consume it in some way. Your choice.
You could use your own custom middleware to send the request.
With that approach you have greater flexibility , you could dispatch actions like type:LOADING_DATA and any others depending on setting state while loading your data. Once the data is returned dispatch other action(s) which could be handled by other custom middleware(s) to normalise data etc and finally send it to your reducer.
This approach is described very well in https://leanpub.com/thinking-in-Redux or https://www.youtube.com/watch?v=JUuic7mEs-s
I personally liked his ideas and perhaps you would find them useful.

In react-admin get access to redux store

My question is related to react-admin repo.
I want to dispatch an action, outside of scope of a component, in order to do that, I've read that I need to get access to the actual redux store itself, and dispatch on in directly,
so I know that the Admin component has an initialState prop, but it only accepts default state object, not the store. So I can't make a store and pass it in.
My question is:
How do I access redux store of an Admin component?
How can I dispatch an action outside of a component, when using Admin as my main app component?
my current app entry looks like this:
<AppLayoutDirection>
<Admin
title="My App"
locale="en"
dataProvider={dataProvider}
authProvider={authProvider}
i18nProvider={i18nProvider}
theme={themeProvider}
customSagas={customSagas}
appLayout={AppLayout}
>
{DynamicResource}
</Admin>
</AppLayoutDirection>
When you say that you need to dispatch an action outside the scope of a component, I suppose that it's in reaction to another action that was dispatched in the past.
In that case, that's what react-admin calls a side effect. React-admin handles side effects using redux-saga. Here is how to create a custom saga:
// in src/bitcoinSaga.js
import { put, takeEvery } from 'redux-saga/effects';
import { showNotification } from 'react-admin';
export default function* bitcoinSaga() {
yield takeEvery('BITCOIN_RATE_RECEIVED', function* () {
yield put(showNotification('Bitcoin rate updated'));
})
}
Register this saga in the <Admin> component as follows:
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import bitcoinSaga from './bitcoinSaga';
const App = () => (
<Admin customSagas={[ bitcoinSaga ]} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;
This is documented in the react-admin documentation, in the <Admin> chapter.
You could also simply use custom reducers if the computation is no async
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import reducers from './reducers';
const App = () => (
<Admin customReducers={customReducers} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;

React native: undefined is not a function (evaluating 'this.props.passwordChanged(text)')

I defined the following actions in ../actions/AuthActions.js:
export const emailChanged = (text) => {..};
export const passwordChanged = (text) => {..};
and exported them in "./index.js" as
export * from './AuthActions';
Imported the actions in LoginForm.js with
import { passwordChanged, emailChanged } from '../actions';
and used the actions in the following way:
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
On running the code in the emulator, I get the error 'undefined is not a function...', even though I have defined the function, exported the name. One of the exported names 'emailChanged' works as expected but only that one and as far as I can see there is nothing special about that name. I know I must be missing something glaringly obvious, but would appreciate any help.
I assume you are using redux, and trying to call actions from your component. The problem is that you did not connect your action creators with to component with redux's connect Higher Order Component.
What you need to do, is add these actions with connect like this:
import { connect } from 'react-redux';
export default connect(mapStateToProps, { emailChanged, passwordChanged })(LoginScreen)
This way, those actions will be available in this.props.

Categories