Mithril
Mithril

Reputation: 13728

react redux assign data to component

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.

Upvotes: 4

Views: 481

Answers (2)

Damien Leroux
Damien Leroux

Reputation: 11693

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().

Upvotes: 2

Mark
Mark

Reputation: 6081

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>

Upvotes: 1

Related Questions