4

I have searched around, all questions are something about How to pass props to {this.props.children}

But my situation is different, I fill App with a initial data -- nodes, and map nodes to a TreeNodelist, and I want each TreeNode has the property of passed in node.

Pseudo code:

App.render:

{nodes.map(node => 
<TreeNode key={node.name} info={node} /> 
)} 

TreeNode.render:

const { actions, nodes, info } = this.props 
return ( 
<a>{info.name}</a> 
); 

Seems node not be passed in as info, log shows info is undefined.

warning.js?8a56:45 Warning: Failed propType: Required prop `info` was not specified in `TreeNode`. Check the render method of `Connect(TreeNode)`. 

TreeNode.js?10ab:57 Uncaught TypeError: Cannot read property 'name' of undefined 

below just a more complete code relate to this question(store and action is not much relation I think):

containers/App.js:

import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Footer from '../components/Footer';
import TreeNode from '../containers/TreeNode';
import Home from '../containers/Home';
import * as NodeActions from '../actions/NodeActions'

export default class App extends Component {

  componentWillMount() {
    // this will update the nodes on state
    this.props.actions.getNodes();
  }

  render() {
    const { nodes } = this.props
    console.log(nodes)
    return (
      <div className="main-app-container">
        <Home />
        <div className="main-app-nav">Simple Redux Boilerplate</div>
        <div>
          {nodes.map(node =>
            <TreeNode key={node.name} info={node} />
          )}
        </div>

        <Footer />
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    nodes: state.opener.nodes
  };
}


function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(NodeActions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

containers/TreeNode.js

import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import classNames from 'classnames/bind'
import * as NodeActions from '../actions/NodeActions'

class TreeNode extends Component {


  handleClick() {
    this.setState({ open: !this.state.open })
    if (this.state.open){
      this.actions.getNodes()
    }
  }

  render() {
    const { actions, nodes, info } = this.props

    if (nodes) {
      const children =<div>{nodes.map(node => <TreeNode info={node} />)}</div>
    } else {
      const children = <div>no open</div>
    }

    return (
      <div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
        <a>{info.name}</a>
        {children}
      </div>
    );
  }
}

TreeNode.propTypes = {
  info:PropTypes.object.isRequired,
  actions: PropTypes.object.isRequired
}


function mapStateToProps(state) {
  return {
    open: state.open,
    info: state.info,
    nodes: state.nodes
  };
}


function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(NodeActions, dispatch)
  };
}


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TreeNode);

reducers/TreeNodeReducer.js

import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';

const initialState = {
  open: false,
  nodes: [],
  info: {}
}


const testNodes = [
  {name:'t1',type:'t1'},
  {name:'t2',type:'t2'},
  {name:'t3',type:'t3'},
]


function getFileList() {
  return {
    nodes: testNodes
  }
}


export default function opener(state = initialState, action) {
  switch (action.type) {
  case OPEN_NODE:
    var {nodes} = getFileList()  
    return {
      ...state,
      open:true,
      nodes:nodes
    };
  case CLOSE_NODE:
    return {
      ...state,
      open:false
    };
  case GET_NODES:
    var {nodes} = getFileList()
    return {
      ...state,
      nodes:nodes
    };
  default:
    return state;
  }
}

For complete code, can see my github https://github.com/eromoe/simple-redux-boilerplate

This error make me very confuse. The sulotion I see are a parent already have some children, then feed props to them by using react.Children, and them don't use redux.

2 Answers 2

2

When looping on nodes values, you call TreeNode and give the property info: that is good!

But when your component is rendered, this function is called:

function mapStateToProps(state) {
  return {
    open: state.open,
    info: state.info,
    nodes: state.nodes
  };
}

As you can see, the prop info will be overriden with the value in state.info. state.info value is undefined I think. So React warns you that TreeNode requires this value. This warning comes from your component configuration:

TreeNode.propTypes = {
  info:PropTypes.object.isRequired
}

Why state.info is undefined? I think you doesn't call it as it should. You should call state['reducerNameSavedWhenCreatingReduxStore].infoto retreive{}`.

You shouldn't fill ThreeNode through both props & connect().

Sign up to request clarification or add additional context in comments.

Comments

1

It's because you are rendering a Redux connected component from within a parent Redux connected component and trying to pass props into it as state.

Why does TreeNode.js need to be connected to Redux? Props/Actions should be passed uni-directionally with only the top level component connected to state and all child components being essentially dumb components.

TreeNode should look similar to this:

class TreeNode extends Component {


  handleClick() {
    this.setState({ open: !this.state.open })
    if (this.state.open){
      this.props.actions.getNodes();
    }
  }

  render() {
    const { nodes, info } = this.props

    if (nodes) {
      const children =<div>{nodes.map(node => <TreeNode info={node} />)}</div>
    } else {
      const children = <div>no open</div>
    }

    return (
      <div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
        <a>{info.name}</a>
        {children}
        <div>{nodes.map(node => <TreeNode info={node} />)}</div>
      </div>
    );
  }
}

TreeNode.propTypes = {
  info: PropTypes.object.isRequired,
  actions: PropTypes.object.isRequired
}

export default class TreeNode;

and the parent component would render TreeNode like this, passing the props in to the component:

<div>
   {nodes.map(node =>
      <TreeNode key={node.name} info={node} actions={this.props.actions} />
   )}
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.