I have a list of od dicts. It looks like this:
document.__moreComments = [
{ id: 2, author: '...', text: '...', date: '...' },
{ id: 1, author: '...', text: '...', date: '...' },
];
I want to sort my data by date and rendered it.
Firstly I want to create an object in the declaration and set it in state. Then sorting and represent changing data. I have a problem.
import React, { Component } from "react";
var addcomments = document.__moreComments;
class AdditionalComments extends Component {
constructor() {
super();
// this.state = addcomments
this.state = {
comments: addcomments.map(addcomment => [
addcomment.id,
addcomment.author,
addcomment.text,
addcomment.date
])
};
console.log(this.state);
}
changeMessage() {
let sortedComments = this.state.comments;
this.setState({
comments: sortedComments.sort((a, b) => a.date > b.date)
});
console.log(this.state.comments);
}
render() {
return (
<div>
<h1>hi Eugene {this.state.comments} </h1>
<button onClick={() => this.changeMessage()}>Click</button>
</div>
);
}
}
export default AdditionalComments;
I create dict of dict. And of course I want to know. Is it right to do what I do or I need another approach?
Thanks. I'm a beginner.
Sort sorts array in-place(that means it won't return new instance of array), that's the reason your array is not getting sorted. You should try below approach:
changeMessage() {
let sortedComments = [...this.state.comments].sort((a,b) => new Date(a.date) - new Date(b.date))
this.setState({
comments: sortedComments
}, () => {console.log(this.state.comments)});
}
Related
I have nested objects as described below and updating states.
`
interface BookState {
name: string
authors: AuthorState[]
}
interface AuthorState {
name: string
}
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial)
// Add new empty author; which will later be filled from textfields
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { } as AuthorState
let authors = [...bookValues[bookIndex].authors, newAuthor]
let newBookState = update(bookValues, { [bookIndex]: { authors: { $set: authors } } })
setBookValues(newBookState) // ** edited
}
// somewhere i populate bookValues as:
bookValues = [
{name: "Book-1", authors: [{name: "Author-1"}] },
{name: "Book-2", authors: [{name: "Author-1"}, {name: "Author-2"}]}
]
`
When I add an author, suppose in "Book-1" index 0, I call the onClickAddAuthor(0), the state updates and UI updates. But when I add an author, suppose in "Book-2" index 1, i call the onClickAddAuthor(1), the state value can be seen updating when printing to console but the UI does not update. I am using https://github.com/kolodny/immutability-helper to update the state.
I expect to add a new empty author on index-1 as well, which should update the state and UI. I tried making deep copies of the book Values and updating the state with that, but it is not working. If it is working in index 0, it should work on other indexes (1, 2, 3 .. ) as well. I am not able to understand.
I tested the posted code with 4 items in bookValues, it seems that the onClickAddAuthor is working as expected. Perhaps the output logic could be checked to see if it updates correctly.
Simple test demo on: stackblitz
import { useState } from 'react';
import './App.css';
import update from 'immutability-helper';
interface AuthorState {
name: string;
}
interface BookState {
name: string;
authors: AuthorState[];
}
const bookStateInitial = [
{ name: 'Book-1', authors: [{ name: 'Author-1' }] },
{ name: 'Book-2', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
{ name: 'Book-3', authors: [{ name: 'Author-1' }] },
{ name: 'Book-4', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
];
function App() {
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial);
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { name: 'Test Author' } as AuthorState;
let authors = [...bookValues[bookIndex].authors, newAuthor];
let newBookState = update(bookValues, {
[bookIndex]: { authors: { $set: authors } },
});
setBookValues(newBookState);
};
return (
<main className="App">
<section>
{[0, 1, 2, 3].map((item) => (
<button key={item} onClick={() => onClickAddAuthor(item)}>
{`Test: add author for Book-${item + 1}`}
</button>
))}
</section>
<ul>
{bookValues.map((book) => (
<li key={book.name}>
{`name: ${book.name}, authors: ${book.authors
.map((author) => author.name)
.join(', ')}`}
</li>
))}
</ul>
</main>
);
}
export default App;
i want to replace the values of the nested array object like the below one, when button is clicked it will replace the old values of the x indexed object and set the new values there.
class compo extends React.Component {
constructor() {
super();
this.state = {
tabsData:[
{
id:1,
title:"OldTitle1"
},
{
id:2,
title:"OldTitle2"
}
],
}
this.changeTab = this.changeTab.bind(this)
}
changeTab(){
const newData={
id=3,
title="New One"
}
//replace the above new data in the second object of nested array in state
}
render(){
return(
<button type="button" >Add</button>
)
;}
}
export default compo
the state should be like this after
tabsData:[
{
id:1,
title:"OldTitle"
},
{
id:3,
title:"New One"
}
]
Not able to comment as my rep is less than 50...based on an idea of what you need here is the code.
https://codesandbox.io/s/brave-lumiere-dh9ry?file=/src/App.js
const [data, setData] = React.useState([
{
id: 1,
title: "OldTitle1"
},
{
id: 2,
title: "OldTitle2"
}
]);
const newData = { id: 3, title: "New One" };
const addData = () => {
const newArr = data;
newArr[1] = newData;
console.log("newArr>>>>", newArr);
setData([...newArr]);
};
You could do something like this...
import React from "react";
class compo extends React.Component {
constructor() {
super();
this.state = {
tabsData: [
{
id: 1,
title: "OldTitle1"
},
{
id: 2,
title: "OldTitle2"
}
]
};
this.changeTab = this.changeTab.bind(this);
}
changeTab() {
const newData = {
id: 3,
title: "New One"
};
// Make duplicate since you can't mutatue state
let newTabData = [...this.state.tabsData];
const id = 2; // id to be removed
// CASE 1: If you want to maintain order
const index = newTabData.findIndex((data) => data.id === id);
if (index > -1) {
// replace oldData with newData
newTabData.splice(index, 1, newData);
} else {
// simply add newData at last
newTabData.push(newData);
}
// CASE 2: If order doesn't matter
// // remove oldData
// newTabData = newTabData.filter((data) => data.id !== id);
// // add new data at last
// newTabData.push(newData);
// finally update the state irrespective of any case
this.setState({ tabsData: newTabData });
}
render() {
return (
<div>
<button type="button">
Add
</button>
<button type="button" onClick={this.changeTab}>
Change
</button>
<br />
{JSON.stringify(this.state, null, 2)}
</div>
);
}
}
export default compo;
I have the following Array
arrayOfItems: [{
0:
description: "item1"
id: 11
name: "item1Name"
},
1:
description: "item2"
id: 12
name: "item2Name"
},
2:
description: "item3"
id: 13
name: "item3Name"
},
3:
description: "item4"
id: 14
name: "item4Name"
}]
I want to add a new pair
{
description: "item5"
id: 15
name: "item5Name"
}
I am still very new to React and have been working on this problem. I do understand how Map works but not sure how I can add new pair in React
This component is a dropdown list so there is no input or button click related to it.
{dataArray.arrayOfItems!.map((item: any) => {
return (
<ComponentName key={item.id} value={item.description}>
{item.description}
</ComponentName>
);
})}
if you want to add item to array on page load use componentDidMount() method:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[
{id:1,name:'aaa', description:'this is description aaa'},
{id:2,name:'bbb', description:'this is description bbb'},
]
}
}
componentDidMount(){
let items=this.state.items;
let newItem={id:5,name:'ccc',description:'this is description ccc'};
let updatedItems=items.push(newItem);
// or you can use ... spread operator
// let updatedItems=[...items,newItem];
this.setState({items:updatedItems});
}
}
You can store your array into state, and then modify the state.
Here's an example
function MyComponent() {
const [items, setItems] = React.useState([{ id: 0, description: 'Old Item' }])
const loadMoreItems = () => {
setItems([...items, { id: 1, description: 'New Item' }])
}
return (
<>
{items.map((item) => (
<div key={item.id} value={item.description}>
<p>{item.description}</p>
</div>
))}
<button onClick={loadMoreItems}>Load more items</button>
</>
)
}
Add on change event to your dropdown.
onChange = (event) => {
console.log(event.target.value)
// add your value to array here
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
<select onChange={this.onChange}>
</select>
EDIT
Adding values on page load. Don't use push to add items to array in state.
componentDidMount = () => {
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
let fileInfos=this.state.fileInfos;
fileInfos.push({
"name": file.name,
"content": e.target.result
});
this.setState({fileInfos});
In my app
there's a component that renders (play)lists
(I have 2 lists hardcoded )
I can Add a new list to the list of lists.
When you click on a list
the list of songs is displayed, and at the bottom of the list is
a button that, when you click it, displays a form with inputs (title,artist,album).
Before I fixed the adding list functionality, songs were added to the 'active' list
but now
the action is dispatched (ADD_SONG) and shows up with the right values in the (Redux)state but it renders the same type of element/component as the list and is not appened/added...
I'm not sure where to look
I hope someone can spot my faulty logic
AddSongForm
export default class AddSongForm extends React.PureComponent {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
clicked: !this.state.clicked
})
}
handleChange = (event) => {
const value = event.target.value
const name = event.target.name
// console.log(name, value)
// console.log(this.state);
this.setState({
[name]: value
})
}
handleSubmit = (event) => {
event.preventDefault()
console.log(this.state);
if (this.state.title && this.state.artist) {
this.props.addSong({
title: this.state.title,
artist: this.state.artist,
album: this.state.album
})
}
}
render() {
return (<div>
<button onClick={this.handleClick}><h2>New Song+</h2></button>
{this.state.clicked ?
<form onSubmit={this.handleSubmit}>
<label>
Song Title:
<input type="text" name="title" onChange={this.handleChange} />
</label>
<label>
<br/> Artist:
<input type="text" name="artist" onChange={this.handleChange} />
</label>
<label>
<br/> Album:
<input type="text" name="album" onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
: null}
</div>)
}
}
AddSongFormContainer
import AddSongForm from './AddSongForm'
import { connect } from 'react-redux'
class AddSongFormContainer extends React.PureComponent {
addSong = (song) => {
this.props.dispatch({
type: 'ADD_SONG',
payload: {
id: Math.ceil(Math.random()*10000),
...song
}
})
}
render() {
return <AddSongForm addSong={this.addSong} />
}
}
export default connect(null)(AddSongFormContainer)
Reducer with initial state
const initState = [
{
id: 1,
title: 'Play list 1',
data: [
{
id: 1,
title: 'DogHeart II',
artist: 'The Growlers',
album: 'Gilded Pleasures'
}, {
id: 2,
title: 'Beast of No nation',
artist: 'Fela Kuti',
album: 'Finding Fela'
}, {
id: 3,
title: 'Satellite of love',
artist: 'Lou Reed',
album: 'Transformer'
}
]
}, {
id: 2,
title: 'Play list 2',
data: [
{
id: 1,
title: 'Whatever happend to my Rock and Roll',
artist: 'BlackRebelMoterCycleClub',
album: 'B.R.M.C'
}, {
id: 2,
title: 'U Sexy Thing',
artist: 'Crocodiles',
album: 'CryBaby Demon/ U Sexy Thing'
}, {
id: 3,
title: 'Oh Cody',
artist: 'NoBunny',
album: 'Raw Romance'
}
]
}
]
const reducer = (state = initState, action = {}) => {
switch (action.type) {
case 'ADD_LIST':
return [
...state,
action.payload
]
case 'ADD_SONG':
return [
...state,
action.payload
]
default:
return state
}
}
export default reducer
PlayList Component mapping over al the songs in the list
export default class PlayList extends React.Component{
constructor() {
super()
this.state = {
clicked: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: !this.state.clicked
})
console.log(this.props.selectList.name)
}
render(){
// console.log(this.props);
// console.log(this.props.playLists[0].data);
return (<div>
<button onClick={this.handleClick}><h1>{this.props.playLists.title}</h1></button>
{this.state.clicked ?
<ul>
{ this.props.playLists.data.map(song =>
<li key={song.id} onClick={() => this.props.selectSong(song.id)}>
<b>{song.title}</b><br/>
By:
<br/>
<h3><b>{song.artist}</b></h3><br/>
Appears on:
<b>{song.album}</b><br/><br/>
</li>
) }
<AddSongFormContainer/>
</ul>
: null}
</div>)
}
}
Container for al the playlists in the initialstate(array)
class PlayListsContainer extends React.PureComponent {
selectSong = (id) => {
this.props.dispatch({
type: 'SELECT_SONG',
payload: id
})
}
selectSong(id) {
console.log('selected song:', id)
}
selectList = (id) => {
this.props.dispatch({
type: 'SELECT_LIST',
payload: id
})
}
selectList(id) {
console.log('selected song:', id)
}
render() {
const playlistsArray = this.props.playLists
// console.log(playlistsArray)
return (
playlistsArray.map((playlist) => <PlayList
playLists={playlist}
selectSong={this.selectSong}
selectList={this.selectList}
key={playlist.id}
/>)
)
}
}
const mapStateToProps = (state) => {
// console.log(state.playLists);
return {
playLists: state.playLists
}
}
export default connect(mapStateToProps)(PlayListsContainer)
With your comment describing your redux problems, and the screenshot of the Redux dev tools - the problem is clear now.
When you are adding a song, you are simply adding it to the top level of the store, without actually adding it to a play list.
It would be entirely possible to fix this as is. In your reducer, rather than adding the song like you do now, you need to add it specifically to a playlist. If you need a code example, I can provide one.
However, I encourage you to refactor your redux store - and follow the best practicing of having a normalized, flat state.
What this means is, you want to have two top-level objects for your redux store.
playlists
songs
Rather than including all of the data about a song in a playlist, you simply reference the id of the songs.
Your playlists would look like this:
playlists {
1: {
title: 'my playlist'
songs: [1,2,3]}
And the songs can stay the same.
Whenever you add a song to a playlist, you simply add the song, and then update the playlist with the new song id.
Another practice you can do, to make your code a bit cleaner is to use mapDispatchToProps rather than defining your redux action dispatches inline. Docs for that are here.
#
To fix the code as is, the main thing we need to do is pass along the playlistid that you want to add the song to. Otherwise, how else will we know where to put the song?
First, update your action in your addSongFormContainer to accept an additional argument, targetPlaylist (that the song will go into)
addSong = (song, targetPlaylist) => {
this.props.dispatch({
type: 'ADD_SONG',
payload: {
playlist: targetPlaylist
id: Math.ceil(Math.random()*10000),
...song
}
})
}
The usage of this action now requires you pass along a target playlist. For brevity, I am going to hardcode that the song is being added to playlist 1. I'll leave the exercise of passing the selected playlist down to the component up to you.
I cleaned up the handleSubmit to make it more clear, by moving the song into it's own variable as well.
handleSubmit = (event) => {
event.preventDefault()
console.log(this.state);
if (this.state.title && this.state.artist) {
let song = {
title: this.state.title,
artist: this.state.artist,
album: this.state.album
}
let selectedPlayList = 1 //Fix this later :)
this.props.addSong(song, selectedPlayList)
}
}
Now the last problem is the reducer.
case 'ADD_SONG':
const index = store.getState().findIndex(playlist => playlist.id ===
action.payload.playlist)
console.log(index) //This should be index 0, after looking up playlist id: 1
const updatedPlaylistSongs = this.state[index].data
updatedPlaylistSongs.push(action.playload.song)
return [
...state.slice(0, index), // All playlists before current
{
...state[index], //The targeted playlist.
...updatedPlaylistSongs //The updated songs
},
...state.slice(index + 1), //All playlists after current
]
I hope the reducer works for you, though it might need a bit of work - I am not used to writing reducers dealing with arrays. I typically have normalized data, which results in much easier modification. I highly encourage you to attempt to normalize your redux store. Stop using arrays, try using objects where the key is generated (use uuidv4 to make a unique & random key). This makes "selecting" what you want to edit/update significantly easier.
I hope this helps!
I'm using ReactJs + flux. In my store I have a method - getCurrentEvents(key).It recieves key and creates array of objects correctly. After that it emitts change.
I have component-list, which has handler. This handler answers to store change and calls method displayEvents(). This method updates component's state. This conception works but not correctly. When I call getCurrentEvents(key) it begins updating EventList-component and doesn't stop, as a result tab in browser freezes.As I understand I got something like limitless cycle of updates, I think that something wrong is in component's methods, but I can't understand where is the mistake. How to fix this bug?
store code:
class EventStore extends EventEmitter {
constructor() {
super();
this.events = [{
id: 1,
title: 'title 1',
date: '2017-04-11 09:14:01'
}, {
id: 2,
title: 'title 2',
date: '2017-04-11 09:24:01'
}, {
id: 3,
title: 'title 3',
date: '2017-04-12 09:14:01'
}, {
id: 4,
title: 'title 4',
date: '2017-11-12 19:14:01'
}, {
id: 5,
title: 'title 5',
date: '2017-06-13 19:00:01'
}
];
}
getCurrentEvents(key) {
var currentEvents = [];
for (event in this.events){
if (this.events[event].date.includes(key)) {
currentEvents.push(this.events[event]);
}
}
return currentEvents;
this.emit("change");
}
createEvent(new_event) {
this.events.push ({
title: new_event.title,
date: new_event.date
})
this.emit("change");
}
getAll() {
return this.events
}
handleActions(action) {
switch(action.type) {
case 'CREATE_EVENT' : {
this.createEvent(action.new_event);
}
case 'DISPLAY_CURRENT_EVENTS' : {
this.getCurrentEvents(action.key);
}
}
}
}
const eventStore = new EventStore;
dispatcher.register(eventStore.handleActions.bind(eventStore))
export default eventStore;
EventList component code:
export default class EventList extends React.Component {
constructor () {
super();
this.state = {
events: EventStore.getCurrentEvents()
};
this.displayEvents = this.displayEvents.bind(this);
}
componentWillMount() {
EventStore.on("change", this.displayEvents)
}
displayEvents() {
this.setState ({
events: EventStore.getCurrentEvents()
})
}
render() {
console.log('form events LIST', this.state)
const events = this.state.events ;
var EventsList = [];
for (event in events) {
EventsList.push(
<li key={events[event].id} id={events[event].id}>
{events[event].title} , {events[event].date}
</li>
)
}
return (
<div className="">
<h3> Events on this day </h3>
<ul className="event-list-wrapper">
{EventsList}
</ul>
</div>
);
}
}
Looks like it's because the getCurrentEvents(key) function is emitting a 'change' which is triggering displayEvents...which calls getCurrentEvents(key)...which emits a 'change' event which calls displayEvents...