Can't access React props modified by Redux MapStateToProps - reactjs

Got undefined when trying to access this.props.state value
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
const mapStateToProps = ({ movies }) => ({
state: movies,
});
class App extends Component {
componentDidMount() {
this.props.addMovie({ title: 'Movie' }); //redux action
}
render() {
const { state } = this.props;
console.log(state.test); //Object {title: "Movie"}
console.log(state.test.title); //Uncaught TypeError: Cannot read property 'title' of undefined
return (
<div>Hello {state.test.title}!</div> //doesn't work
);
}
}
export default connect(mapStateToProps, actions)(App);
After redux complete the action i've got state object in component props
I can access it from Chrome DevTools $r.props.state.test.title but can't access it from render function
For example I can get value from this.props.params.movieID but can't get any params modified by mapStateToProps
How can I put this.props.state.test.title value in my div?

Inside your render function check if the title is undefined because the render function is being called before your redux action is resolving. Once your state is updated and your component renders it should show up the second time.
render() {
const { state } = this.props;
console.log(state.test);
if(state.test && state.test.title){
console.log(state.test.title);
return (
<div>Hello {state.test.title}!</div>
)
} else {
return (
<div>No movies listed</div>
);
}
}

Related

Redux cant access state after added

i have a parent component and one child component (custom function component). in my child component i have event handler that will change the state (redux) from the parent. everything work fine when i call the addProduct function the state is added i can see in my redux tools product state is changed. But Why after i added i cant access that state (console.log (product._id)) i get a error message a product is null and one thing make me confused is i can access that state in my JSX from parent Component ({JSON.stringify(product)}) but not after i added.
here is my code
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {addProduct} from '../action/product';
const ChildComponent = ({onClick}) => {
return (
<button onClick={onClick}>Add</button>
)
}
const ParentComponent = ({product}) => {
const onNotFoundThenAddNew = () => {
addProduct ({name : 'new product'});
console.log(product._id);
}
return (
<Fragment>
{JSON.stringify(product)}
<ChildComponent onClick={onNotFoundThenAddNew}/>
</Fragment>
)
}
ParentComponent.propTypes = {
addProduct : PropTypes.func,
product : PropTypes.object
};
const mapStateToProps = (state) => ({
product : state.product.product
})
export default connect (mapStateToProps, {addProduct}) (ParentComponent)

React error Expected an assignment or function call and instead saw an expression no-unused-expressions while passing props to child component

im getting Expected an assignment or function call and instead saw an expression no-unused-expressions while trying to pass props to child component.
fetchMovie() make axios request to api and returns a valid JSON object.
im trying to pass the object as a prop to child component and im getting this error
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchMovie } from '../actions/movies';
import { MovieItem } from './MovieItem';
import Spinner from './spinner/Spinner';
import PropTypes from 'prop-types';
export class Movies extends Component {
componentDidMount() {
this.props.fetchMovie();
}
render() {
const { movie } = this.props.movies;
let movieItem;
if (movie === null) {
movieItem = 'loading';
} else {
<MovieItem movie={movie} />
}
return (
<div>
<div className="text-center"><p>{movieItem}</p></div>
</div>
)
}
}
Movies.propTypes = {
movies: PropTypes.object.isRequired,
fetchMovie: PropTypes.func.isRequired
}
const mapStateToProps = state => ({
movies: state.movies
})
export default connect(mapStateToProps, { fetchMovie })(Movies);
how to solve this issue? and what is the error meaning?
Look at the render method, in the else branch you create a MovieItem component, but you don't do anything with it. Looking at your code, I guess you need to assign it to movieItem variable, so it gets than rendered in the p element.

How do I allow connect to rerender an object based on state change

In the below example, I would like component to rerender when list is updated. But even though connect is passed new state, it doesn't rerender the component.
I know that connect performs shallow compare, but don't know how to make it compare the values of object. I couldn't find any example of connect with the options enabled.
I have seen How does a redux connected component know when to re-render? and some more but it doesn't help either.
I have tried
const ConnectList = connect(mapStateToProps,null,null,{areStatesEqual : () => false})(List)
to just try to make it rerender for any change. That doesn't seem to be working as well.
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import thunk from 'redux-thunk'
import {connect, Provider} from 'react-redux'
function testReducer (state=null,action) {
console.log(`Reducer: Reducer received action ${action.type}. ${action.comment}`)
switch(action.type){
case 'LIST': {
return ({ ...state, list: action.list })
}
case 'OTHER': {
return ({ ...state, other: action.other })
}
default:
return state
}
}
function testAction() {
return {
type: 'LIST',
list: ['first','second'],
comment: `This will trigger both connect() and mount Component List mount because, both reducer and connect changes state after this action`
}
}
function testActionChange() {
return {
type: 'LIST',
list: ['first','second','third'],
comment: `This will trigger both connect() and mount Component List mount because, both reducer and connect changes state after this action`
}
}
function testOther() {
return {
type: 'OTHER',
other: `some other value`,
comment: `This will trigger connect(), but not mount Component List because the return from connect() doesn't change`
}
}
function inertAction() {
return {
type: 'INERT',
comment: 'This action should not trigger either connect() or mount Component List , because reducer returs the same state'
}
}
const store = createStore(testReducer, [thunk])
store.dispatch(testAction())
//Dispatch an action after 2 secs
setTimeout(store.dispatch.bind(null,testOther()),2000)
setTimeout(store.dispatch.bind(null,inertAction()),4000)
setTimeout(store.dispatch.bind(null,testActionChange()),6000)
class List extends Component {
componentDidMount(){
console.log(`Component List mounted`)
}
render(){
const {list} = this.props
return(
<div>
{list.map((element) => {
return(<Element key={element} element={element} />)
})}
</div>
)
}
}
function mapStateToProps({list}){
console.log(`connect() triggered`)
return( {
list
})
}
const ConnectList = connect(mapStateToProps)(List)
class Element extends Component {
render(){
const {element} = this.props
return(
<div>{element}</div>
)
}
}
ReactDOM.render(<Provider store={store}>
<ConnectList />
</Provider>,
document.getElementById('root')
);
Output
Added console.log in connect.
I don't recognize the syntax your using in mapStateToProps
try:
function mapStateToProps(state){
console.log(`connect() triggered`)
const list = state.list;
return { list };
}
I figured out that connect in fact calls the component. But only the render method. So I had to move my action creator calls at componentDidMount to a middleware in redux to add appropriate dispatcher when state changes.

react-async-poll with a connected component

Looking at the docs for react-async-poll I'm following the Usage example to integrate asyncPoll into my component, but I'm getting a Uncaught TypeError: dispatch is not a function complaint from within my onPollinterval function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import asyncPoll from 'react-async-poll';
import { fetchCaCities, } from '../actions';
import MyMap from './my-map';
class CaliforniaMap extends Component {
componentDidMount() {
this.props.fetchCaCities();
}
render() {
return (
<div>
<h1>California Map</h1>
<MyMap center={[37.5, -120]} zoom={6} layers={[this.props.caCities]} />
</div>
);
}
}
const onPollInterval = (props, dispatch) => {
console.log(dispatch); // undefined
return dispatch(fetchCaCities());
};
const mapStateToProps = state => ({
caCities: state.map.california.caCities,
});
export default asyncPoll(60 * 1000, onPollInterval)(connect(
mapStateToProps, { fetchCaCities }
)(CaliforniaMap)
Maybe react-async-poll doesn't work for connected components?
According to the docs:
The dispatch parameter is only passed to [onInterval] if it is
available in props, otherwise it will be undefined.
The example they give is confusing because it does not define dispatch anywhere, but they show onPollInterval using it.

React / Redux wait for store to update

I have a problem that a react component is rendering before the redux store has any data.
The problem is caused by the React component being rendered to the page before the existing angular app has dispatched the data to the store.
I cannot alter the order of the rendering or anything like that.
My simple React component is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const tasks = this.props.state.editorFlow[0].flow.tasks
return (
<div>
Flow editor react component in main container
</div>
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
So how can I hold off the rendering until editorFlow array has elements ?
You can use Conditional Rendering.
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
return (
{tasks &&
<div>
Flow editor react component in main container
</div>
}
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
As far as I know, you can't.
the way redux works is that it first renders everything, then actions take place with some async stuff(such as loading data), then the store gets populated, and then redux updates the components with the new state(using mapStateToProps).
the lifecycle as I understand it is this :
render the component with the initial state tree that's provided when you create the store.
Do async actions, load data, extend/modify the redux state
Redux updates your components with the new state.
I don't think mapping the entire redux state to a single prop is a good idea, the component should really take what it needs from the global state.
Adding some sane defaults to your component can ensure that a "loading" spinner is displayed until the data is fetched.
In response to Cssko (I've upped your answer) (and thedude) thanks guys a working solution is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
if(tasks){
return (
<div>
Flow editor react component in main container
</div>
)
}
else{
return null;
}
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)

Resources