Reputation: 13748
I am building a file manager webui base on react redux(My purpose is to master react and redux through this project)
As you know, a file manager need a tree explorer.I want to build a component which can contain it self and each has self state. like below:
TreeNode
can contain children which are TreeNode
too.Each TreeNode
hold its state {path, children_nodes, right .....}
, children_nodes
is get from server, path
is passed by parent. That's what I imagine.
Struct like:
App:
TreeNode
--TreeNode
----TreeNode
----TreeNode
TreeNode
TreeNode
--TreeNode
TreeNode
--TreeNode
----TreeNode
----TreeNode
But trouble come here, because redux connect
store to the tree root, all node under the root receive same state...
For example, I have a OPEN_NODE
action, which is design to trigger getFileList fucntion
base this node's path and set this node's state.open
to true
.(note: getFileList fucntion
not implement yet, just give fake data for now)
The screen shot:
Click each element , but states are equal
.
My code:
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} actions={this.props.actions}/>
)}
</div>
{/*<Footer />*/}
</div>
);
}
}
function mapStateToProps(state) {
return {
nodes: state.opener.nodes,
open: state.opener.open
};
}
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'
export default class TreeNode extends Component {
constructor(props, context) {
super(props, context)
this.props = {
open: false,
nodes: [],
info:{}
}
}
handleClick() {
let {open} = this.props
if (open) {
this.props.actions.closeNode()
} else {
this.props.actions.openNode()
}
}
render() {
const { actions, nodes, info } = this.props
return (
<div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
<a>{info.name}</a>
{nodes &&
<div>{nodes.map(node => <TreeNode info={node} />)}</div>
}
{!nodes &&
<div>no children</div>
}
</div>
);
}
}
TreeNode.propTypes = {
open:PropTypes.bool,
info:PropTypes.object.isRequired,
nodes:PropTypes.array,
actions: PropTypes.object.isRequired
}
actions/NodeActions.js
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
export function openNode() {
return {
type: OPEN_NODE
};
}
export function closeNode() {
return {
type: CLOSE_NODE
};
}
export function getNodes() {
return {
type: GET_NODES
};
}
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, see my github https://github.com/eromoe/simple-redux-boilerplate
I don't see an example cover such component, and google result nothing helpful. Any idea to overcome this?
update: I see this How to manage state in a tree component in reactjs
But the solution is pass the whole tree to state, can not use in file manager.
Upvotes: 3
Views: 2034
Reputation: 3310
All of your TreeNode
has the same state from redux because your mapStateToProps
for all of them are same.
mapStateToProps
can take ownProps
(the props
of the wrapped component) as second parameter. You can use it to distinguish your TreeNodes
. In your case, path
is a good option.
considering writing a state selector like this getChildrenNodes(state, path)
and return the nodes accordingly.
You may want to consider reading through redux docs first, especially this seciont: http://redux.js.org/docs/recipes/ComputingDerivedData.html
Upvotes: 0
Reputation: 547
I'm implementing a Github like app using React and Redux.
For now, I only list repositories and show its files as well as navigate through them.
I don't know if this is considered a good or bad practice, but this is how I implemented my Tree component.
Inside each Tree Component, I have a Link to itself. And I pass some data on the route, so I'm able to get the next tree when I render it.
Component
class Tree extends Component {
constructor(props) {
super(props);
this.renderList = this.renderList.bind(this);
}
componentWillMount() {
this.props.getTree(this.props.params.sha);
}
componentWillReceiveProps(nextProps) {
if(nextProps.params.sha !== this.props.params.sha) {
this.props.getTree(nextProps.params.sha);
}
}
renderList(file) {
return (
<tr key={ file.sha }>
{ file.type == 'tree'
? <td><Link to={`/repository/${this.props.params.repoName}/tree/${file.path}/${file.sha}`}>{ file.path }</Link></td>
: <td><Link to={`/repository/${this.props.params.repoName}/blob/${file.sha}/${file.path}`}>{ file.path }</Link></td>}
</tr>
)
}
render() {
const treeFile = this.props.tree;
const fileName = this.props.params.path;
return (
<div className="row">
<h3>{ fileName }</h3>
<div className="col-md-12">
<table className="table table-hover table-bordered">
<tbody>
{ isEmpty(treeFile.tree) ? <tr>Loading</tr> : treeFile.tree.map(this.renderList) }
</tbody>
</table>
</div>
</div>
)
}
}
export default Tree;
Action
const setTree = (tree) => {
return {
type: actionTypes.GET_TREE,
tree
};
};
export const getTree = (sha) => {
return (dispatch, getState) => {
const { repository, profile } = getState();
const repo = GitHubApi.getRepo(profile.login, repository.name);
repo.getTree(sha, function(err, data) {
dispatch(setTree(data));
});
}
}
Reducer
const initialState = "";
export const tree = (state = initialState, action) => {
switch (action.type) {
case actionTypes.GET_TREE:
return getTree(state, action);
}
return state;
}
const getTree = (state, action) => {
const { tree } = action;
return tree;
}
For the complete code, you can check my repository on github
https://github.com/glundgren93/Github-redux
Upvotes: 1