React - Tree Component - javascript

I am trying to make a component in React to recursively display the names in a data tree. I am not that familiar with React and am not sure what I can do in my code to remove the error I get in the console.
The error is Uncaught SyntaxError: embedded: Adjacent JSX elements must be wrapped in an enclosing tag
Here is a jsFiddle to my code: https://jsfiddle.net/go79b0dp/
Here is my example code:
var treeObj = [
{
id: 1,
name: 'Bob',
children: [
{
id: 2,
name: 'Mary',
children: [
{id: 4, name: 'Suzy'}
]
},
{
id: 3,
name: 'Phil',
children: [
{id: 5, name: 'Jon'},
{id: 6, name: 'Paul'}
]
}
]
}
];
var TreeView = React.createClass({
getInitialState: function() {
return {
people: treeObj
};
},
render: function() {
var people = this.state.people;
var nodes = people.map((i) => <TreeNode node={i} children= {i.children} />)
return (
<ul>{nodes}</ul>
);
}
});
var TreeNode = React.createClass({
render: function() {
var nodes;
if (this.props.children) {
nodes = this.props.children.map((i) => <TreeNode node={i} children={i.children} />);
}
return (
<li>{this.props.node.name}</li>
<ul>{nodes}</ul>
);
}
});
ReactDOM.render(<TreeView />, document.getElementById('container'));

Your TreeNode component returns two sibling components: <li> and <ul>. Basically, you're trying to return two things from the render function, and you can't do that.
Normally, the recommended solution is to wrap them both in another element. For example:
return (<div>
<li>{this.props.node.name}</li>
<ul>{nodes}</ul>
</div>);
However, for the tree structure you're trying to create, it would probably be better to put the <ul> inside the <li>. That would be:
return (<li>
{this.props.node.name}
<ul>{nodes}</ul>
</li>);
This is how nested lists are commonly done in HTML.

Related

Working with state on recursive components

I'm writing a component that renders itself inside recursively and is data-driven
Attaching my sandbox snippet, as it will be easier to see there.
This is my data:
var builderStructureData = [
{
id: 123,
value: 3,
children: []
},
{
id: 345,
value: 5,
children: [
{
id: 4123,
value: 34,
children: [
{
id: 342342,
value: 33,
children: []
}
]
},
{
id: 340235,
value: 3431,
children: [
{
id: 342231342,
value: 3415,
children: []
}
]
}
]
}
];
and it renders like this:
This is my App.js:
import { useState } from "react";
import "./App.css";
import Group from "./components/group";
import builderStructureData from "./components/builderStructureData";
function App() {
const [builderStructure, setBuilderStructure] = useState(
builderStructureData
);
return (
<div className="App">
{builderStructure.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
export default App;
And this is my recursive component:
import React from "react";
export default function Group(props) {
let childrenArray = [];
if (props.children) {
props.children.map((x) => childrenArray.push(x));
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={props.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
I can render the components based on the data, and it seems to be handling recursion fine. I need to store the state on the App.js page and be able to change it from within child components. For example, if I update the "value" field of the component with ID = 342342, I want it to update that corresponding object in the state no matter how deeply nested it is, but not sure how to do that as it is not as simple as just passing a prop.
Am I taking the right approach with my code snippet? How can I do the state update?
I would advise the state normalization approach - here is an example for redux state - https://redux.js.org/usage/structuring-reducers/normalizing-state-shape - but you can use this approach with your state. So - your state will look like this:
state = {
items: {
[123]: {
id: 123,
value: 3,
childrenIds: []
},
[345]: {
id: 345,
value: 5,
childrenIds: [4123, 340235]
},
[4123]: {
id: 4123,
value: 34,
parentId: 345,
childrenIds: [342342]
},
[342342]: {
id: 342342,
value: 33,
parentId: 4123,
childrenIds: []
},
[340235]: {
id: 340235,
value: 3431,
parentId: 345,
childrenIds: [342231342]
},
[342231342]: {
id: 342231342,
value: 3415,
parentId: 340235
childrenIds: []
}
}
}
Here the field "childrenIds" is an optional denormalization for ease of use, if you want - you can do without this field. With this approach, there will be no problem updating the state.
You are thinking this in a wrong way, it should be very easy to do what you want.
The most imported thing is to make a little small changes in Group
Please have a look
import React from "react";
export default function Group(props) {
const [item, setItem] = React.useState(props.item);
let childrenArray = [];
if (item.children) {
item.children.map((x) => childrenArray.push(x));
}
const updateValue = ()=> {
// this will update the value of the current object
// no matter how deep its recrusive is and the update will also happen in APP.js
// now you should also use datacontext in app.js togather with state if you want to
// trigger somethings in app.js
item.value =props.item.value= 15254525;
setState({...item}) // update the state now
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={item.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group item={x} />;
})}
</div>
);
}
The code above should make you understand how easy it is to think about this as an object instead of keys.
Hop this should make it easy for you to understand

How to build React checkbox tree

I'm trying to work with a checkbox tree component like this: https://www.npmjs.com/package/react-checkbox-tree, except I'm storing the items that I have selected in Redux. Moreover, the only items that I'm actually storing are the leaf nodes in the tree. So for example, I'd have the full options data which would be used to render the tree:
const fam = {
cuz2: {
name: 'cuz2',
children: {
cuzKid2: {
name: 'cuzKid2',
children: {
}
}
}
},
grandpa: {
name: 'grandpa',
children: {
dad: {
name: 'dad',
children: {
me: {
name: 'me',
children: {}
},
sis: {
name: 'sis',
children: {}
}
}
},
aunt: {
name: 'aunt',
children: {
cuz: {
name: 'cuz',
children: {
name: 'cuzkid',
children: {}
}
}
}
}
}
}
and a separate object that stores the items selected. The following would be the only items that would appear if every checkbox was checked:
const selected = {
cuz2: true,
me: true,
sis: true,
cuz: true
}
I seem to be struggling with this method for having the UI determine which boxes to have fully, partially, or un-checked based on the selected object. I was wondering if anyone can recommend another strategy of accomplishing this.
So I have used react-checkbox-tree but I have customised a bit the icons in order to use another icons library.
Check my example on sandbox:
The library provides a basic example of how to render a tree with selected and/or expanded nodes.
All you need to do is:
set up the nodes with a unique 'value'
Choose which items should be selected (it may comes from Redux)
pass nodes & checked list to the CheckBox constructor
also be sure that when user select/unselect, you update the UI properly using the state
Your code should look similar to this:
import React from 'react';
import CheckboxTree from 'react-checkbox-tree';
const nodes = [{
value: '/cuz2',
label: 'cuz2',
children: [],
},
// other nodes
];
class BasicExample extends React.Component {
state = {
checked: [
'/cuz2'
],
expanded: [
'/cuz2',
],
};
constructor(props) {
super(props);
this.onCheck = this.onCheck.bind(this);
this.onExpand = this.onExpand.bind(this);
}
onCheck(checked) {
this.setState({
checked
});
}
onExpand(expanded) {
this.setState({
expanded
});
}
render() {
const {
checked,
expanded
} = this.state;
return (<
CheckboxTree checked={
checked
}
expanded={
expanded
}
nodes={
nodes
}
onCheck={
this.onCheck
}
onExpand={
this.onExpand
}
/>
);
}
}
export default BasicExample;

Correct way to rerender my React component?

I'm pretty new to React (coming from Angular 1), and have been playing around with it somewhat. I have a test script that loops through a multidimensional object, and binds it to the dom.
I then add a new item to the object wrapped in a setTimeout. Is calling the ReactDOM.render below that the best way to rerender the React component?
var items = [
{ name: 'Matt', link: 'https://google.com' },
{ name: 'Adam', link: 'https://bing.com' },
{ name: 'Luke', link: 'https://yahoo.com' },
{ name: 'John', link: 'https://apple.com' }
];
var RepeatModule = React.createClass({
getInitialState: function() {
return { items: [] }
},
render: function() {
var listItems = this.props.items.map(function(item) {
return (
<li key={item.name}>
<a className='button' href={item.link}>{item.name}</a>
</li>
);
});
return (
<div className='menu'>
<h3>The List</h3>
<ul>
{listItems}
</ul>
</div>
);
}
});
ReactDOM.render(<RepeatModule items={items} />, document.getElementById('react-content'));
setTimeout(function() {
var newline = { name: 'Added item', link: 'https://amazon.com' };
items.push(newline);
ReactDOM.render(<RepeatModule items={items} />, document.getElementById('react-content'));
}, 2000);
Much appreciated :)
React docs advise to place async calls in the componentDidMount method.
Load Initial Data via AJAX Fetch data in componentDidMount. When the
response arrives, store the data in state, triggering a render to
update your UI.
https://facebook.github.io/react/tips/initial-ajax.html
Here is a demo: http://codepen.io/PiotrBerebecki/pen/KgZGao
const App = React.createClass({
getInitialState: function() {
return {
items: [
{ name: 'Matt', link: 'https://google.com' },
{ name: 'Adam', link: 'https://bing.com' },
{ name: 'Luke', link: 'https://yahoo.com' },
{ name: 'John', link: 'https://apple.com' }
]
};
},
componentDidMount: function () {
setTimeout(() => {
this.setState({
items: [
...this.state.items,
{ name: 'Added item', link: 'https://amazon.com' }
]
});
}, 2000);
},
render: function() {
var listItems = this.state.items.map(function(item) {
return (
<RepeatModule key={item.name} href={item.link} itemName={item.name} />
);
});
return (
<div>
<h3>The List</h3>
{listItems}
</div>
);
}
});
const RepeatModule = React.createClass({
render: function() {
return (
<div className='menu'>
<ul>
<li>
<a className='button' href={this.props.href}>{this.props.itemName}</a>
</li>
</ul>
</div>
);
}
});
ReactDOM.render(
<App />,
document.getElementById('app')
);
Wrap the RepeatModule within a parent component. Items should be part of the state. Have a button to add new item. On click of the new item, pass the item details to the parent component. The parent component should update the state.
Your code won't work because you are pushing item to items. You should slice it before pushing it. React checks for props / state change using the === operator.
setTimeout(function() {
var newline = { name: 'Added item', link: 'https://amazon.com' };
var newItems = items.slice();
newItems.push(newline);
ReactDOM.render(<RepeatModule items={newItems} />, document.getElementById('react-content'));
}, 2000);
You could use the react state
working demo: http://codepen.io/anon/pen/WGyamK
var RepeatModule = React.createClass({
getInitialState: function() {
return { items: [
{ name: 'Matt', link: 'https://google.com' },
{ name: 'Adam', link: 'https://bing.com' },
{ name: 'Luke', link: 'https://yahoo.com' },
{ name: 'John', link: 'https://apple.com' }
] }
},
componentDidMount() {
var _this = this
setTimeout(function() {
var newline = { name: 'Added item', link: 'https://amazon.com' };
_this.setState({items: _this.state.items.concat(newline)});
}, 2000);
},
render: function() {
var listItems = this.state.items.map(function(item) {
return (
<li key={item.name}>
<a className='button' href={item.link}>{item.name}</a>
</li>
);
});
return (
<div className='menu'>
<h3>The List</h3>
<ul>
{listItems}
</ul>
</div>
);
}
});
ReactDOM.render(<RepeatModule />, document.getElementById('react-content'));
That being said, you should use some library trigerring the rerender for you. You are coming from Angular, so firstly you need to know that React is not a whole framework like angular, but just the "V in MVC".
I use react in combination with redux. Check out https://github.com/reactjs/redux for more.
There are some good boilerplate codes out there to get started quickly. I like this https://github.com/davezuko/react-redux-starter-kit
Hope you find this useful.

React: How to know which component calls the function

I am doing my first project using React and there is one thing I can't figure out. So I have many different Type components which are being set as the main component's TypesPage state. And when the onChange event happens on Type component I want to know which type it is in a TypesPage state or what index it is in a types array, so I can reupdate my data state.
Inside handleChange function I used jQuery's grep function comparing clicked Type title value with all the types array, but I am sure that is not the right way to do it and it would be an overkill with huge arrays.
Why I want to know which
handleChange:function(element, event){
var typeIndex;
$.grep(types, function(e, index){
if(element.title === e.title){
typeIndex = index
}
});
types[typeIndex] //Now I know that this is the Type that was changed
}
Fiddle
var types = [
{
type_id: 1,
type_name: "Logo"
},
{
type_id: 2,
type_name: "Ad"
},
{
type_id: 3,
type_name: "Catalog"
},
];
var Type = React.createClass({
render: function() {
return(
<li>
<input type="text" value={this.props.title}
onChange={this.props.handleChange.bind(null, this.props)} />
</li>
);
}
});
var TypesContainer = React.createClass({
render: function() {
var that = this;
return(
<ul>
{this.props.data.map(function(entry){
return(
<Type
key={entry.type_id}
title={entry.type_name}
handleChange={that.props.handleChange}
/>
);
})}
</ul>
);
}
});
var TypesPage = React.createClass({
getInitialState: function(){
return({data: types})
},
handleChange: function(element, event){
},
render: function() {
return(
<TypesContainer
data={this.state.data}
handleChange={this.handleChange}
/>
);
}
});
ReactDOM.render(
<TypesPage />,
document.getElementById('container')
);
I prefer ES6. The problem is, you have to bind your handleChange event with correct context of this and pass your arguments which you are expect to get inside your handle. See example below
class Example extends React.Component {
constructor(){
super();
this.state = {
data: [{id: 1, type: 'Hello'},{id: 2, type: 'World'},{id: 3, type: 'it"s me'}],
focusOn: null
};
}
change(index,e){
const oldData = this.state.data;
oldData[index].type = e.target.value;
this.setState({data:oldData, focusOn: index})
}
render(){
const list = this.state.data.map((item,index) =>
// this is the way how to get focused element
<input key={item.id} value={item.type} onChange={this.change.bind(this, index)}/>
);
return <div>
{list}
<p>Focused Element with index: {this.state.focusOn}</p>
</div>
}
}
React.render(<Example />, document.getElementById('container'));
fiddle
Thanks

React: What happens on consecutive renders where a list shuffles?

I am trying to create a list which shuffles randomly with some animation.
Here is the fiddle for it where I have used key prop to identify each child.
http://jsfiddle.net/1wcpLLg4/
var ListAnimate = React.createClass({
list: [
{id: 1, caption: "Hello"},
{id: 2, caption: "There"},
{id: 3, caption: "Whatsup"},
{id: 4, caption: "Sanket"},
{id: 5, caption: "Sahu"},
],
shuffle: function() {
this.list.shuffle(); // Shuffles array!
this.forceUpdate();
},
render: function() {
return <div>
<button onClick={this.shuffle}>Shuffle</button>
<ul>
{this.list.map(function(el, i){
return <li key={el.id} style={ {top: (i*60)+'px'} }>{el.caption} {el.id}</li>;
})}
</ul>
</div>;
}
});
React.render(<ListAnimate />, document.body);
From the React docs about Dynamic Children, it states that key prop is used in order to identify the elements in an array during successive renders. So, that the items which are just re-ordered must not unmount and mount to new position rather they should just be re-positioned but the does not seem to be happening in the fiddle, where the Nodes at the top of the list are always being unmounted and being mounted at a different position.
But for the elements at the bottom seems to be working well with animation.
Keep in mind that for the kind of animation you are looking for, you need to always keep the DOM in the same order and only update their position in the render function of your component.
I modified your first fiddle using this strategy: http://jsfiddle.net/0maphg47/1/
render: function() {
// create a sorted version of the list to render the DOM
var sortedCopy = this.state.list.slice().sort(function(a, b) {
return a.id - b.id;
});
return <div>
<button onClick={this.shuffle}>Shuffle</button>
<ul>
{sortedCopy.map(function(el, i) {
// find the position of the element in the shuffled list
// which gives the position the element must be
var pos = this.state.list.indexOf(el);
return <li key={el.id} style={ {top: (pos*60)+'px'} }>
{el.caption} {el.id}
</li>;
}, this)}
</ul>
</div>;
}
There is still room for improvement but I'll leave that up to you.
I have created a fiddle to show that li elements are not actually being mounted / unmounted.
http://jsfiddle.net/jq9p7hnd/
I have converted li element to MyLi element and logged messages when the componentDidMount and componentWillUnmount functions are called. Only the componentDidMount callbacks are called during the first render and none of them are called after shuffle:
var MyLi = React.createClass({
componentDidMount : function(){
console.log("MyLi component did mount.");
},
componentWillUnmount : function(){
console.log("MyLi component will unmount.");
},
render : function(){
return <li {...this.props}>{this.props.children}</li>
}
});
var ListAnimate = React.createClass({
list: [
{id: 1, caption: "Hello"},
{id: 2, caption: "There"},
{id: 3, caption: "Whatsup"},
{id: 4, caption: "Sanket"},
{id: 5, caption: "Sahu"},
],
shuffle: function() {
this.list.shuffle();
this.forceUpdate();
},
render: function() {
return <div>
<button onClick={this.shuffle}>Shuffle</button>
<ul>
{this.list.map(function(el, i){
return <MyLi key={el.id} style={ {top: (i*60)+'px'} }>{el.caption} {el.id}</MyLi>;
})}
</ul>
</div>;
}
});
window.React = React;
React.render(<ListAnimate />, document.body);
Array.prototype.shuffle = function() {
var i = this.length, j, temp;
if ( i == 0 ) return this;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
}

Categories

Resources