How do I properly dispatch actions inside the React Lifecycle Methods? - javascript

How do I properly dispatch actions in React Lifecycle Methods?
Hello, friends
I have an action called checkSession() inside my very parent component (App.jsx). This action helps me to get user info.
This is how App.jsx looks like:
class App extends Component {
componentWillMount() {
this.props.checkSession();
}
render() {
if (this.props.auth === null) {
return <div>Loading...</div>;
} else {
return (
<BrowserRouter>
<Route path="/cart" component={Cart} />
</BrowserRouter>
);
}
}
}
const mapStateToProps = ({ auth }) => { auth };
export default connect(mapStateToProps, { checkSession })(App);
How do I properly dispatch another action inside the Cart.jsx component.
I try to dispatch the action inside the componentWillReceiveProps methods but it creates an infinite loop.
My Cart.jsx component looks like this:
import { getCart } from "../../actions";
class Cart extends Component {
componentWillReceiveProps(nextProps, nextState) {
if (nextProps.auth) {
this.props.getCart(nextProps.auth.googleId);
} else {
this.props.getCart(null);
}
}
render() {
......some staff
}
}
const mapStateToProps = ({ auth }) => { auth };
export default connect(mapStateToProps, { getCart })(Cart);
I have just tried to dispatch this action inside the ComponentWillMount - but my props are not ready yet so I got an error.
Help me please with any advice, and sorry for my English.

Maybe you have to detect if the props have changed before firing the dispatch:
componentWillReceiveProps(nextProps, nextState) {
const currentId = this.props.auth && this.props.auth.googleId;
const nextId = nextProps.auth && nextProps.auth.googleId;
if (currentId !== nextId) {
this.props.getCart(nextProps.auth.googleId);
}
}

Related

Prop is not updating as expected when value is set in a reducer

I encountered this problem when I was testing my newly created action and reducer. The prop is not being updated even though I'm setting it to a fixed value within my reducer.
Component:
class <ComponentName> extends Component {
componentDidMount() {
login()
}
render() {
if(this.props.isLogged)
return (
<App/>
);
else
return (
<ErrorScreen/>
);
}
}
function mapStateToProps(state) {
return {
isLogged:state.auth.isLogged
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: () => dispatch(login())
};
};
export default connect(mapStateToProps,mapDispatchToProps)(<ComponentName>)
Action:
export function login() {
return {
type:"TEST"
}
}
Reducer:
const initState = {
isLogged: false,
}
export default (state=initState, action) => {
switch(action.type) {
case "TEST":
return {
...state,
isLogged: true
}
break;
default:
return state
}
}
Combine Reducer:
import {combineReducers} from 'redux'
import AuthenticationReducer from './authenticationReducer'
export default combineReducers({
auth: AuthenticationReducer
})
Provider:
import React, {Component} from "react";
import <ComponentName> from './app/screens/<ComponentName>'
import store from './app/store'
import {Provider} from 'react-redux'
export default () =>
<Provider store={store}>
<<ComponentName>/>
</Provider>;
Been trying to debug this for some time now. I still don't know why this is happening. Maybe I implemented it wrongly? If there are some files I forgot to include, please inform me. Thanks and have a nice day!
The reason your code isn't working as expected is because you're calling the login() action creator, rather than the login() method that is returned from mapDispatchToProps() (and injected into the props of <ComponentName/>).
Try revising your code by adding this.props before your call to login() like so:
class <ComponentName> extends Component {
componentDidMount() {
// Update this line here so that the login() method
// injected by connect() is called (ie via this.props)
this.props.login()
}
render() {
if(this.props.isLogged)
return <App/>
else
return <ErrorScreen/>
}
}

How to get state via React-Loadable? React-Redux

I written a custom logic for handling async route loading bundles in react-router-dom .v4. It's work perfectly. But also I heard about useful package with nice API to do the same, like React-Loadable. It has one problem, I cannot get the props/state pushed from Redux on the mount of the component throw this package.
My code is rewritten from the custom style to react-loadable style in two examples below. The last one is react-loadable version, that does not throw state/props.
My personal code:
const asyncComponent = getComponent => {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
const { Component } = this.state
if (!Component) {
getComponent().then(({ default: Component }) => {
const { store } = this.props // CAN GET THE REDUX STORE
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />
}
return null;
}
};
};
export default withRouter(asyncComponent(() => import(/* webpackChunkName: "chunk_1" */ './containers/Component')))
The same code, but with React-Loadable:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // CANNOT GET THE REDUX STORE!!
}),
loading: Loading
})
export default withRouter(asyncComponent)
To get the state from Redux store via Provider you should place your asyncComponent in Stateful Component wrapper, like you do in your custom async logic (1st case).
It because Loadable library returns you asyncComponent like a function, not a constructor, that way he cannot get access to current Redux store. So, the working solution will be the next:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // YOU WILL GET THE REDUX STORE!!
}),
loading: Loading
})
class asyncComponentWrapper extends Component{ // Component wrapper for asyncComponent
render() {
return <asyncComponent {...this.props} />
}
}
export default withRouter(asyncComponentWrapper)
P.S.
I do not know what you try to do, but in case how to make reducer injection inside the current store (probably it's exactly what you trying to do), you need to include you Redux store explicitly by import, not from the Provider state.

How I can unit test if log is called

I have such component. It is a wrapper component for another component. There is onClick function, which should call the log if is mouse event
import log from './log';
export function withPressedLog(
Component,
options,
) {
class WithPressedLogComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
public render() {
const { ...props } = this.props;
return <Component {...props} onClick={this.onClick} />;
}
private onClick(e) {
if (this.props.onClick !== undefined) {
this.props.onClick(e);
}
if (e) {
this.props.log();
}
}
}
const mapDispatchToProps = {
log: () => log(options),
};
return connect(
undefined,
mapDispatchToProps,
)(WithPressedLogComponent);
}
I need to test is it called this.props.log. I have a unit test, but it not works. How I can do it using jest, enzyme?
it("should not log if has not mouse event", () => {
const onClickMock = jest.fn();
const logMock = jest.fn();
const ButtonWithLog = withPressedLog(Button, {
type: "BUTTON_PRESSED",
});
const subject = mountProvider(ButtonWithLog, { onClick: onClickMock, log: logMock });
const mockedEvent = { target:{} };
subject.find(ButtonWithLog).simulate("click", mockedEvent);
expect(onClickMock.mock.calls).toHaveLength(1);
expect(logMock.mock.calls).toHaveLength(0); // not works correctly, always return []
});
store
const store = createStore(() => ({}));
const dispatchMock = jest.fn();
store.dispatch = dispatchMock;
mountProvider function
function mountProvider(
Component,
props,
) {
return mount(
<Provider store={store}>
<Component {...props} />
</Provider>,
);
}
I think the problem here is that you are actually testing the connected component, not the unwrapped component.
Try to isolate the components you are testing more. For example, you can use enzyme's shallow wrapper and the dive method on the connected component, to directly get to the unwrapped component.
Specifically, your problem could be that your connected component is getting the log prop from the store (through mapDispatchToProps), but the store is mocked, so it does not work. In your test, you pass some mock function as prop to the component, but the reference gets lost once the components connects.
helpful thread on github

Using NetInfo middleware in React Native with Redux

I want to test in all components whether the user has connection to the internet.
I could use NetInfo in each component, but since I am using redux, I thought it could be done easier with a middleware(?).
I have used
import { createStore, applyMiddleware } from 'redux';
const netInfo = store => next => action => {
const listener = (isConnected) => {
store.dispatch({
type: types.NET_INFO_CHANGED,
isConnected,
});
};
NetInfo.isConnected.addEventListener('change', listener);
NetInfo.isConnected.fetch().then(listener);
return next(action);
};
const store = createStore(AppReducer, applyMiddleware(netInfo));
where AppReducer is just combineReducers(navReducer, netInfoReducer, ...).
It does seem to work, but I am really worried if this performs well enough. It seems it is only run once, but I am never removing the listener or anything.
Is this how you normally would do if you want to populate all components with an isConnected variable?
I would create a Higher-Order Component for this:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
function withNetInfo(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange);
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
}
export default withNetInfo;
Then you can wrap whatever component you would like to render:
class MyComponent extends Component {
render() {
const { isConnected } = this.props;
return(
<View>
<Text>
{`Am I connected? ${isConnected}`}
</Text>
</View>
);
}
}
export default withNetInfo(MyComponent);
Bonus: if you want to keep the statics methods of your original component (if you have defined some) you should use the package hoist-non-react-statics to copy the non-react specific statics:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
import hoistStatics from 'hoist-non-react-statics';
function withNetInfo(WrappedComponent) {
class ExtendedComponent extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange)
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
return hoistStatics(ExtendedComponent, WrappedComponent);
}
export default withNetInfo;
There shouldn't be a performance issue using middleware to keep "isConnected" in your redux store, but you would want to make sure the listener is only added once. I use https://github.com/michaelcontento/redux-middleware-oneshot to achieve that.
I considered middleware, too, but was also afraid how to handle the sub/unsub. I've decided to go with adding and removing the listener in componentDidMount and componentWillUnmount of my AppContainer class, which holds the rest of the app in my MainNavigator. This class' lifecycle should follow that of the app, and thus make sure to sub/unsub correctly. I am, however, also going to use a redux action to set the status and listen to it in the relevant views to show a 'no connection' banner.

Empty state in component ( react redux )

Have problem with state in my component.
I'm trying to get status from my reducer but state is empty just getting undefined
Here is my actionCreator
export function checkLogin() {
return function(dispatch){
return sessionApi.authCheck().then(response => {
dispatch(authSuccess(true));
}).catch(error => {
throw(error)
})
}
}
My reducer
export const authStatus = (state = {}, action) => {
switch(action.type){
case AUTH_FALSE:
return{
status: action.status
}
case AUTH_TRUE:
return {
...state,
status: action.status
};
default:
return state;
}
};
And here is my component where i'm trying to get state
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
I need to get status from the state
Component
import * as React from "react";
import Navigation from './components/navigation';
import { connect } from 'react-redux';
import { setLocale } from 'react-redux-i18n';
import cookie from 'react-cookie';
import {checkLogin} from "./redux/actions/sessionActions";
class App extends React.Component<any, any> {
constructor(props:any) {
super(props);
this.state = {
path: this.props.location.pathname
};
}
componentDidMount(){
this.props.checkAuth();
this.props.changeLanguage(cookie.load('lang'));
}
componentWillUpdate(){
}
render() {
return (
<div>
<Navigation path={this.state.path} />
{this.props.children}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
export class Myapp
extends App {}
You cannot access props that are asynchronous inside of the constructor. As the constructor will be executed only once, when you instantiate your component. When you instantiate your component your asynchronous call has not responded yet, therefore this.props.status is undefined.
You could use componentWillReceiveProps from React lifecycle methods for example:
componentWillReceiveProps(nextProps) {
console.log(nextProps.status);
}
This method will be executed everytime a prop connected, or passed, to the component will change.
You could also use this.props.status inside of the render as this one is also executed everytime a prop changed.
For a better understanding of react lifecycle you could have the look at the different methods available, here : https://facebook.github.io/react/docs/react-component.html

Categories

Resources