Handling complex data structures in React - javascript

I'm new in React, and I would like to know If someone could help me with this.
I have an application like slack, where I can add a new Team and add a channel.
The problem is that is a complex DS and I have been trying to modify the state with new data passed through inputs, either in team and channel, but I have not had success
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
newTeamValue: "",
teams: [
{
newChannel: "",
name: "Team1",
channels: [
{
name: "Channel1",
index: 1
},
{
name: "Channel2",
index: 2
}
]
}
]
};
this.addTeam = this.addTeam.bind(this)
this.addChannel = this.addChannel.bind(this)
}
addTeam(e) {
e.preventDefault();
this.setState(prevState => ({
newTeam: "",
teams: [
...prevState.teams,
{
name: this.state.newTeam,
channels: []
}
]
}));
}
addChannel(e){
e.preventDefault()
this.setState(prevState => ({
newChannel:"",
teams: [
...prevState.teams,
{
channels: [...prevState, {
name: this.state.teams.newChannel
}]
}
]
}))
}
render() {
return (
<div>
<ul>
{this.state.teams.map(team => {
return (
<div>
<li>{team.name}</li>
<input onChange={e => this.setState({ newChannel: e.target.value })} value={this.state.newChannel} />
<button onClick={this.addChannel}>Add New Channel</button>
<ul>
{team.channels.map(channel => {
return (
<div>
<h2>{channel.name}</h2>
</div>
);
})}
</ul>
</div>
);
})}
</ul>
<input onChange={e => this.setState({ newTeam: e.target.value })} value={this.state.newTeam} />
<button onClick={this.addTeam}>Add new Team</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));

Something like this might help.
const newTeams = [...this.state.teams, { name: "Team3", channels: [] }]
this.setState({ teams: newTeams });
newTeams is an array than contains all the existing teams (...this.state.teams), and an additional team named Team3.
There are libraries (like immutablejs) that might be of use to you. I don't personally use them very often so I can't provide you with an exmaple, but might be something to look in to.
Edit:
You mentioned you're new to React, not sure if you're also new to JS. Incase you haven't seen the ... before, it's the Spread operator.
Edit2:
Re your comment about adding channels to existing teams
const newTeams = [
...this.state.teams,
{
name: "Team123",
channels: [
...this.state.Team123.channels,
{ name: "NewChannel", index: 123 }
]
}
];
this.setState({ team: newTeams });

Related

React map over the array object

I'm quite new with react stuff, what I am trying is to generate some dynamic stuff using .map()
This is my component:
import React, { Component } from "react";
class DynamicStuff extends Component {
state = {
options: [
{ id: 1, optionOne: "OptionOne" },
{ id: 2, optionTwo: "OptionTwo" },
{ id: 3, optionThree: "OptionThree" }
]
};
render() {
const options = [...this.state.options];
return (
<>
{options.map((option) => {
return {option}
})}
<span>{options.optionOne}</span>
<span>{options.optionTwo}</span>
<span>{options.optionThree}</span>
</>
);
}
}
export default DynamicStuff;
What I am doing wrong here and why the map is not generating expected result ?
Is it ok?
import React, { Component } from "react";
class DynamicStuff extends Component {
state = {
options: [
{ id: 1, value: "OptionOne" },
{ id: 2, value: "OptionTwo" },
{ id: 3, value: "OptionThree" }
]
};
render() {
const options = [...this.state.options];
return (
<>
{options.map((option) => {
return <span>{option.value}</span>
})}
</>
);
}
}
export default DynamicStuff;
You have made your options object incorrectly. We need to have a same attribute over all the objects in the array.
class App extends React.Component {
state = {
options: [
{ id: 1, option: "OptionOne" },
{ id: 2, option: "OptionTwo" },
{ id: 3, option: "OptionThree" }
]
};
render() {
const options = [...this.state.options];
return (
<>
{options.map((option, index) => (
<li key={index}>{option.option}</li>
))}
</>
);
}
}
Another thing, If you need to map an array. You don't need to have many spans. Having a one is just enough. The map function will iterate and give you all the things.
The map used here is actually to convert the js object into a react element, but your usage here is still a js object after the map conversion. The react element may be a <p key = {option.id}> {option. optionOne} </p>.
If there is a react element after the return in your map, it is correct.
{options.map((option) => {
return <p key = {option.id}> {option. optionOne} </p>
})}
or
{options.map((option) => <p key = {option.id}> {option. optionOne} </p>)}
YOu need to map and return the HTML element
return ({
options.map((option) => {
return `<span key={option.id}>${option. option}</span>`
})
});
You should do something like
render() {
const { options } = this.state;
return (
<div className="option-wrapper">
{options.length > 0 && options.map(option => {
return <span key={option.id}>{option.option}</span>
})}
</div>
);
}

Very simple state change of specific array item in React

I'm trying to change the state of only one specific array item from the reviews array. How can this be done? This code doesn't seem to work:
this.setState({
reviews[2].current: true
});
Here's the full code:
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null
},
{
name: "Simon Arnold",
current: null
},
{
name: "Claire Pullen",
current: null
}
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit
};
}
change = () => {
this.setState({
reviews[2].current: true
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Demo: https://stackblitz.com/edit/react-tbryf5
As you can probably tell I'm new to react! Thanks for any help here
For some context, React detects state change when reference of the state object changes. It does not track deep changes happening in array or the object.
Solution
We need to make another variable with same data (mostly destructuring). Change the value needed. And assign that to state again.
For Object
this.setState({...oldState, keyToChange: 'newValue'});
For Array
const temp = [...oldState];
temp[index] = 'newValue';
this.setState(temp);
Hope it helps.
It's common for an Array state to copy first then update one of its value
change = () => {
const result = [...this.state.reviews];
result[2].current = true;
this.setState({reviews: result});
};
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null,
},
{
name: "Simon Arnold",
current: null,
},
{
name: "Claire Pullen",
current: null,
},
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit,
};
}
change = () => {
const prevState = [...this.state.reviews];
prevState[2].current = true;
this.setState({
reviews: prevState,
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));

objects sent to react state looping issue

I am building a recipe app and I have an api that fetches me recipes based on what i type in. the issue is that whenever i type the search phrase and search, it makes the state super unstable by sending in insane amounts of objects into the state (normally it should be like 10-12 results. These objects are repeat of each other (you can see it in the screenshot i have attached). The code is provided below, can anyone show me why this might be so?
import React, { Component } from 'react';
import RecipeDisplay from '../RecipeDisplay/RecipeDisplay';
import Form from '../Form/Form';
import './RecipeUI.css';
import uuid from 'uuid/v4';
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: [ '' ],
RecipeUI: [ { title: '', thumbnail: '', href: '' } ]
};
this.search = this.search.bind(this);
}
search(x) {
this.setState({ food: x });
}
componentDidUpdate() {
let url = `https://api.edamam.com/search?q=${this.state
.food}&app_id=cf711&app_key=b67d194436b01d1f576aef`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) =>
data.hits.map((n) => {
let wow = {
key: uuid(),
title: n.recipe.label,
thumbnail: n.recipe.image,
href: n.recipe.url
};
this.setState({ RecipeUI: [ ...this.state.RecipeUI, wow ] });
console.log(this.state);
})
);
}
render() {
return (
<div className="RecipeUI">
<div className="RecipeUI-header">
<h1>Welcome to the Recipe Fetcher</h1>
<Form search={this.search} />
</div>
<div className="RecipeUI-RecipeDisplay">
{this.state.RecipeUI.map((recipe) => (
<RecipeDisplay
key={recipe.key}
title={recipe.title}
thumbnail={recipe.thumbnail}
ingredients={recipe.ingredients}
href={recipe.href}
/>
))}
</div>
</div>
);
}
}
Please, try this as you are concatenating the existing items in state with that of the items that are being brought from search results, the state has got lot of data. Assuming you need only the search results in state, here is the code below:
import React, { Component } from 'react';
import RecipeDisplay from '../RecipeDisplay/RecipeDisplay';
import Form from '../Form/Form';
import './RecipeUI.css';
import uuid from 'uuid/v4';
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: '',
RecipeUI: []
};
this.search = this.search.bind(this);
}
search(x) {
this.setState({ food: x });
}
componentDidUpdate() {
let url = `https://api.edamam.com/search?q=${this.state.food}&app_id=cf7165e1&app_key=
946d6fb34daf4db0f02a86bd47b89433`;
fetch(url).then((response) => response.json()).then((data) => {
let tempArr = [];
data.hits.map((n) => {
let wow = {
key: uuid(),
title: n.recipe.label,
thumbnail: n.recipe.image,
href: n.recipe.url
};
tempArr.push(wow);
});
this.setState({RecipeUI:tempArr})
});
}
render() {
return (
<div className="RecipeUI">
<div className="RecipeUI-header">
<h1>Welcome to the Recipe Fetcher</h1>
<Form search={this.search} />
</div>
<div className="RecipeUI-RecipeDisplay">
{this.state.RecipeUI.map((recipe) => (
<RecipeDisplay
key={recipe.key}
title={recipe.title}
thumbnail={recipe.thumbnail}
ingredients={recipe.ingredients}
href={recipe.href}
/>
))}
</div>
</div>
);
}
}

List childs are not beeing updated correctly? (React / Preact)

I have the following component
import {h, Component} from 'preact'
import {getPersons} from '../../lib/datalayer'
import Person from '../person'
import {SearchInput} from '../search'
export default class Persons extends Component {
state = {
allPersons: [],
persons: [],
search: ''
}
async fetchData () {
try {
const allPersons = await getPersons()
this.setState({allPersons: allPersons.slice(), persons: allPersons.slice()})
} catch (error) {
....
}
}
constructor (props) {
super(props)
this.state = {
allPersons: [],
persons: [],
search: ''
}
this.fetchData()
}
onSearchInput = (search) => {
if (search === '') {
this.setState({search: search, persons: this.state.allPersons.slice()})
} else {
const persons = this.state.allPersons.filter(p => p.name.toLowerCase().includes(search.toLowerCase())).slice()
this.setState({search: search, persons: persons)})
}
}
render () {
const {persons} = this.state
return (
<div>
<SearchInput onInputChange={this.onSearchInput} placeHolder={'filter: name'} />
{persons.map(p => <Person person={p} />)}
</div>
)
}
}
The page renders a list of Persons and it has a filter on top. The filter seems to work fine, I tested it by doing a console.log of the results are just fine
The problem is that, if my list contains the objects:
[{name: 'thomas'}, {name: 'john'}, {name: 'marcus'}, {name: 'usa'}]
And I write in the search input: 'us'
The filter works fine and the result is:
[{name: 'marcus'}, {name: 'usa'}] \\ (the expected result)
In the page this objects are rendered
[{name: 'thomas'}, {name: 'john'}] \\ (wrong, this are the two first elements of the list)
If I search: 'joh'
The filter's result is
[{name: 'john'}] \\ (this is fine)
And the page renders only
[{name: 'thomas'}] \\ (the first element in the list)
It looks like the amount of elements that are rendered it's fine, but the content of the childs of the list is not beeing re-rendered.
Whats's wrong with my code?
React uses keys on the children of a list to determine which items changed and which of them remains the same. Since you have not specified a key on person, it takes index to be the key.
When index is key, you can see how shortening the list to two items, shows up the first two items in the list (the other indices are now missing). To get around this, you have to give a unique identifier on the person as key.
From your object, assuming name is unique (it usually isn't):
{persons.map(p => <Person person={p} key={p.name} />)}
Why are keys necessary - Docs
I cannot reproduce the error with react, did remove some unneeded slice and added unique id to each element (React will complain if you do not give each element a unique key and maybe so will preact).
const Person = React.memo(props => (
<pre>{JSON.stringify(props, undefined, 2)}</pre>
));
class Persons extends React.Component {
state = {
allPersons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
persons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
search: '',
};
onSearchInput = search => {
if (search === '') {
//slice not needed here
this.setState({
search: search,
persons: this.state.allPersons,
});
} else {
//filter already copies allPersons
const persons = this.state.allPersons.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
this.setState({ search: search, persons: persons });
}
};
render() {
const { persons } = this.state;
return (
<div>
<input
type="text"
value={this.state.search}
onChange={e => this.onSearchInput(e.target.value)}
placeHolder={'filter: name'}
/>
{persons.map(p => (
<Person person={p} key={p.id} />
))}
</div>
);
}
}
ReactDOM.render(
<Persons />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

state is updated but unable to display the item in react todo app

I am unable to display the updated list on adding new item to the list. I tried console logging the state and found that state has new user added to the list but item is not shown in the list. I have dummy data in this app so list is on localstorage. I tried console log at each function to trace out the reason but only dummy data was displayed each time and on adding new user a blank <li> block was displayed
Below is the code
import React, { Component } from 'react';
import { render } from 'react-dom';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
names: [
{ name: 'Joel', id: 1 },
{ name: 'Alena', id: 2 },
{ name: 'Andrew', id: 3 },
{ name: 'Harry', id: 4 },
{ name: 'Ron', id: 5 },
],
newName: ''
};
this.handleChange = this.handleChange.bind(this);
this.submitHandle = this.submitHandle.bind(this);
}
componentWillMount() {
console.log(this.state.names)
}
handleChange(e) {
this.setState({ newName: e.target.value }, () => {
console.log(this.state.newName)
});
}
submitHandle(e) {
e.preventDefault();
this.setState({ names: [...this.state.names, this.state.newName] },
() => {
console.log(this.state.names)
})
}
render() {
const { names } = this.state;
let list = names.map((user) =>
<li key={user.id} className="list-group-item">
{user.name}
</li>
)
return (
<div className="container">
<br />
<div>
<form onSubmit={this.submitHandle} className="input-group">
<input type="text" placeholder="Invite Someone"
className="form-control"
value={this.state.newName}
onChange={this.handleChange} />
<span className="input-group-btn">
<button
className="btn btn-primary" type="submit">Submit</button>
</span>
</form>
</div>
<div className="jumbotron ">
<ul className="list-group">
{list}
</ul>
</div>
</div>
);
}
}
render(<App />, document.getElementById('root'));
You have used an array of objects. So you need to define the exact object before concatenate with the array. Please find the code below with the updated change
this.setState(
{ names: [...this.state.names, { name: this.state.newName }] },
() => {
console.log(this.state.names);
}
);

Categories

Resources