Empty state in component ( react redux ) - javascript

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

Related

Redux state in component not updating after action dispatch, but shows up in redux devtools

I am running this piece of code when an item in my menu is clicked.
import React, { Component } from 'react';
import 'react-dropdown-tree-select/dist/styles.css';
import { connect } from 'react-redux';
import '../../../css/tree.css';
import updateSelectedTree from '../../../redux/actions/menu/updateSelectedTree';
import {updateTraceName} from '../../../redux/actions/plot/traceActions';
import { removeRenameProp, pushRenameProp } from '../../../redux/actions/menu/renameActions'
import resolveTraceName from '../../../utils/resolveTraceName';
import MeasureTreeSelector from './MeasureTreeSelector';
class MeasureTreeSelectorContainer extends Component {
render() {
return (<MeasureTreeSelector
isSingle={false}
tags={this.props.tags}
selected={this.props.selected}
handleTreeChange={this.handleTreeChange}
> </MeasureTreeSelector>);
}
handleTreeChange = (curr, selected) => {
console.log(curr)
// Get the order of the selected items
const selectedProperty = [...curr.path.split("/").splice(1), curr.value]
if(curr.checked) {
this.props.pushRenameProp(selectedProperty)
} else {
this.props.removeRenameProp(selectedProperty)
}
console.log(this.props.rename_props)
this.props.updateSelectedTree(this.props.rename_props)
}
updateTraces = () => {
this.props.measures.forEach(element => {
const newName = resolveTraceName(element, this.props.rename_props);
console.log(newName)
this.props.updateTraceName(newName, element.id)
})
}
}
const mapStateToProps = (state) => {
console.log(state.menuReducer)
return {
tags: state.menuReducer.tags,
selected: state.menuReducer.selected,
measures: state.menuReducer.json,
rename_props: state.menuReducer.rename_props
}
}
const mapDispatchToProps = {
updateSelectedTree,
updateTraceName,
removeRenameProp,
pushRenameProp
}
export default connect(mapStateToProps, mapDispatchToProps)(MeasureTreeSelectorContainer);
pushRenameProp and removeRenameProp are synchronous actions that are dispatched that update rename_props. These reducers don't mutate the state but create a new one which means there should be an update in the component.
export function pushRenameProp(prop) {
return {
type:'PUSH_RENAME_PROP',
payload: prop
}
}
export function removeRenameProp(prop) {
return {
type:'REMOVE_RENAME_PROP',
payload: prop
}
}
case 'PUSH_RENAME_PROP':{
console.log(action.payload)
return {
...state,
rename_props: [...state.rename_props,action.payload]
}
}
case 'REMOVE_RENAME_PROP': {
return {
...state,
rename_props: state.rename_props.filter((e,i) => {
return JSON.stringify(e) != JSON.stringify(action.payload)
})
}
}
When I execute this action, the devtools show that the redux state has updated, but the code shows that rename_props is empty.
It seems to be that the component props are one state change behind the redux state
This line this.props.updateSelectedTree(this.props.rename_props) sets the rename_props as they were on before setting them.
So it just negates the change.
You could do this in a thunk:
export function pushRenameProp(prop) {
return function (dispatch, getState) {
dispatch({
type:'PUSH_RENAME_PROP',
payload: prop
})
const state = getState();
return dispatch(updateSelectedTree(state.menuReducer.rename_props)
}
}
To ensure that the updateSelectedTree has the newest state.
*code is not tested, but I think you get the gist of this.
But also, maybe, updateSelectedTree is not needed at all.
You change the state by just push/remove, why update?

How to fix: 'TypeError: fetchProducts is not a function'

I am trying to retrieve a set of products from an API using a redux store.
To achieve this, I created actions and reducers, a function mapStateToProps() and a function mapDispatchToProps(), the latter one contains a function fetchProducts(), which is defined in the action file and should retrieve the list of products when called.
All of this is done in the following way:
This is the overview component, which should render the data (the actual rendering is left out, because it does not affect this question):
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types'
import fetchProductsAction from '../actions/ProductFetchingActionFile'
import {bindActionCreators} from 'redux';
export class Overview extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchProducts();
}
render() {
return(
<h1>test</h1>
)
}
}
const mapStateToProps = state => ({
error: state.rootReducer.productsReducer.error,
products: state.rootReducer.productsReducer.products,
pending: state.rootReducer.productsReducer.pending,
})
const mapDispatchToProps = dispatch => bindActionCreators({
fetchProducts: fetchProductsAction
}, dispatch)
Overview.propTypes = {
fetchProducts: PropTypes.func
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
This is the action file (ProductFetchingActionFile):
import {
PREFIX,
FETCH_PRODUCTS_PENDING,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_ERROR
} from "./types";
function fetchProductsPending() {
return{
type: FETCH_PRODUCTS_PENDING
}
}
function fetchProductsSuccess(products) {
return{
type: FETCH_PRODUCTS_SUCCESS,
products: products
}
}
function fetchProductsError(error) {
return{
type: FETCH_PRODUCTS_ERROR,
error: error
}
}
function fetchProducts() {
return dispatch => {
dispatch(fetchProductsPending());
return fetch(`localhost:8080/products`)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
dispatch(fetchProductsSuccess(res));
return res
})
.catch(error => {
dispatch(fetchProductsError(error));
})
}
}
export default fetchProducts
This is the reducer file:
import {
FETCH_PRODUCTS_PENDING,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_ERROR
} from "./types"
const initialState = {
pending: false,
products: [],
error: null
}
export function productsReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_PENDING:
return {
...state,
pending: true
}
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
pending: false,
products: action.products
}
case FETCH_PRODUCTS_ERROR:
return {
...state,
pending: false,
error: action.error
}
default:
return state;
}
}
Now, I get the following error when the Overview component is loaded:
TypeError: fetchProducts is not a function. This error fires on the call to fetchProducts in the componentDidMount() function.
Also, when the Overview.props is printed in the console before this call, it does not contain the function fetchProducts, so I suspect that it has to do something with the mapDispatchToProps, but I cannot find the problem.
Edit: it is good to note that if the imported fetchProductsAction is printed in the constructor of the Overview component, it shows the correct function. It does however not end up in the props of Overview, so I expect the problem to be there.
try this
const mapDispatchToProps = dispatch => {
fetchProducts : () => {
dispatch(fethcProducts())
}
}
You're importing your action as
import fetchProductsAction from '../actions/ProductFetchingActionFile'
when it looks like it's not being exported as a default.
It should probably be:
import { fetchProductsAction } from '../actions/ProductFetchingActionFile'

React native with connect helper from redux doesn't re-render when state change?

I used reactJS and i know that a component that is wrapped with connect helper that listens to specific reducer when its reducer's state changes it causes the component to re-render.
I don't know why same procedure doesn't work for react-native, i tested my action creators as well as reducers and checked hundred percent that they return new state, And when i checked componentWillRecieveProps i found that the new state is returned correctly and the component doesn't re-render.
Reducer
const INITIAL = {
isSigned: null
}
export default (state = INITIAL, action) => {
switch(action.type){
case SIGNED_IN : return {...state, isSigned: true};
case LOGGED_OUT: return {...state, isSigned: false};
default: return state;
}
}
Component
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../../actions';
class Loading extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.checkSigned();
switch(this.props.isSigned){
case null : return;
case false : this.props.navigation.navigate('Auth');
case true : this.props.navigation.navigate('App')
}
}
render(){
return (
<ActivityIndicator size="large" color="black" />
)
}
}
const mapStateToProps = ({signed}) => {
const {isSigned} = signed;
return {
isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
Actions
export const SIGNED_IN = 'SIGNED_IN';
export const LOGGED_OUT = 'LOGGED_OUT';
//Action Creators
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN})
}
dispatch({type: LOGGED_OUT})
}
You need to use bindActionCreators to dispatch your actions as props
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
const mapStateToProps = state => {
return {
isSigned: state.isSigned
}
}
export default connect(mapStateToProps, actions)(Loading);
// In actions, you need to fix action code
export const checkSigned = () => async dispatch => {
let token = await AsyncStorage.getItem('fb_token');
if(token){
dispatch({type: SIGNED_IN});
} else {
dispatch({type: LOGGED_OUT});
}
}
I think the problem is that you're running your state change logic in componentDidMount. componentDidMount doesn't run when your component re-renders, but componentDidUpdate does. Put your logic there.

React Component fails to re-render after update from Redux Store

Can anyone help me figure out why my Component is not updating after dispatch?
function mapStateToProps(state) {
const { isAuthenticated } = state
return { isAuthenticated }
}
class LoginForm extends Component {
handleSubmit(event) {
event.preventDefault();
const { dispatch } = this.props
const credentials = this.state
dispatch(attemptLogIn(credentials));
}
// Dispatch works properly when API validates Login:
// Redux Log:
// nextState:{ authReducer: {isAuthenticated: true}}
render() {
const { isAuthenticated } = this.props;
return (
<div>
{ isAuthenticated && <div> Authenticated: True </div> }
// Does not render even after dispatch
<form onSubmit={this.handleSubmit}>
{... Form Stuff ...}
</form>
</div>
)
}
export default withRouter(connect(mapStateToProps)(LoginForm))
Just simple conditional render from Redux store, I am expecting the extra div to show up to inform the user that he has authenticated, but It does not render.
This type of example of conditional rendering was used in the AsyncApp example during the Redux Async Tutorial, so I'm not sure why it doesn't work. My actions are dispatched, and reducers successfully update the state, passing it down to the connected component. Here are my reducers:
const initialState = { isAuthenticated: false}
const authReducer = (state = initialState, action) => {
switch(action.type){
case ('USER_AUTHENTICATED'): {
return Object.assign({}, state, {
isAuthenticated: true,
userPermissions: action.userInfo.userPermissions,
instanceType: action.userInfo.instanceType
}
)
}
case ('INVALID_CREDENTIALS'): {
return Object.assign({}, state, {
isAuthenticated:false
}
)
}
case ('LOG_OUT'): {
return initialState
}
default:
return state
}
}
const rootReducer = combineReducers({
authReducer,
routerReducer
})
export default rootReducer
Does anyone know why my Component does not re-render?
Change your mapStateToProps function to this.
function mapStateToProps(state) {
const { isAuthenticated } = state.authReducer;
return { isAuthenticated };
}

Updating state before render: propType marked as required but undefined

I'm having an issue with a component that has some required propTypes.
The error I'm getting is:
Warning: Failed prop type: The prop `firstname` is marked as required in `UserHeader`, but its value is `undefined`.
in UserHeader (at App.js:32)
in App (created by Connect(App))
in Connect(App) (at index.js:17)
in Provider (at index.js:16)
And my code ...
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ErrorBoundary from './containers/ErrorBoundary'
import UserHeader from './components/UserHeader';
import Header from './components/Header';
class App extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
// Pretend this is an API call that takes a second
setTimeout(() => {
const data = {
user: {
firstname: 'bughunter',
level: 55
}
};
this.props.didMountHandler(data)
}, 1000);
}
render() {
return (
<ErrorBoundary>
<Header />
<UserHeader
firstname={this.props.firstname}
level={this.props.level}
/>
</ErrorBoundary>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
firstname: state.user.firstname,
level: state.user.level
}
};
const mapDispatchToProps = dispatch => ({
didMountHandler: data => {
dispatch({
type: 'USER_DATA_RECEIVED',
data
});
}
})
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default AppContainer;
I'm modifying the state before the render method has called so I'm confused how props for UserHeader are being checked before the setTimeout has finished?
How am I able to hold off on rendering/propChecking until setTimeout has completed?
I thought about setting some initial default state when creating the store, like so:
{
user: {
firstname: '',
level: 0
}
}
... but that seems a little hackish.
You are NOT modifying the state before render, because you are using a setTimeout. Render does not wait for your setTimeout and is directly called after componentWillMount is called.
You have to set some default values in the redux reducer. You can just set in the reducer
user: null,
....
and when rendering check if you have the user or not
render() {
const { user } = this.props;
{ user && <UserHeader firstname={user.firstname} level={user.level} /> }
And in mapStateToProps just copy the entire user obj:
const mapStateToProps = (state, ownProps) => {
return {
user: state.user
}
};
There are also other ways to do it. You could just set default values for firstName and level in the reducer as you suggested, or you could always render the UserHeader with a user prop and decide in it what to display if you don't have the values subValues set.
Add a loader component or null like below. And the use local state.
state = { initialized: false }
componentWillMount() {
// Pretend this is an API call that takes a second
setTimeout(() => {
const data = {
user: {
firstname: 'bughunter',
level: 55
}
};
this.props.didMountHandler(data)
this.setState({ initialized: true });
}, 1000);
}
render(){
if(!this.state.initialized){
return null;
}
return (
<ErrorBoundary>
<Header />
<UserHeader
firstname={this.props.firstname}
level={this.props.level}
/>
</ErrorBoundary>
);
}

Categories

Resources