So I've been given vague instruction on a school project:
.map an icon that when clicked, runs a function that puts the key of
the .map in as the argument which then grabs the image and audio
I haven't been given any base code to work with. I'm just not sure where to start. Any help would be appreciated on using .map
This is really vague. Are you sure your professor didn't have any additional instructions? Does your professor want this as an element on a webpage or a standalone icon/app/exe that runs on the desktop?
So map is a method on the Array class. It creates a new array from the results of performing a function (callback) on every element of the initial array. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
//example
const arrayOfNumbers = [1, 2, 3, 4]
// returns array of each element multiplied by 2
arrayOfNumbers.map(num => num*2)
// = [2, 4, 6, 8]
It sounds like you have some sort of mapping (object/hash) that has a key and points to the image/audio source (url). It looks like you would want some image/icon that when click, fires off an event hander whose callback would be to take the keys from this object and key into the object using map to grab your files sources. Then you can display it or something on the DOM?
But it definitely sounds like you should've received additional information to solve this problem.
Here's an actual example that you can dig around.
We start off by creating a library of records. This library is just an array of items (in the form of objects) that we want to access. Each object within the array has an id (that we will use to access records and store an active record id), title, image url and a sound file url.
We create a parent component that contains simple logic to set an active record id. We don't actually use this function within the parent components render method but pass it down to our <Record/> component to use.
We render the libary in the method but we just map the library id's into a new array before mapping the <Record/> component because your assignment criteria is to return an image and a sound by mapping an id.
We create a simple component that renders a record and uses the set prop that we have passed down from the parent component to set the active record id. Since we need to locate the record first we use a find to filter down the Library array, so that we have a single object that we can use to display the title.
The handle click method was created to run the set prop function as we don't want to just do onClick={() => this.props.set(this.props.id)} in the render method because it'll create a new function every time the component is re-rendered. (This is an optimisation)
Lastly but not least, the <ActiveRecord/> presentation component is similar to the above but this finds a record based on the active record id that has been passed down. This component renders the image and sound.
Have a play around! This code could be shortened but we really want to hit the assessment criteria where we need to map id's which makes the <Record/> component a little more complicated (by not passing down the whole record down and having to use .find)
Let me know if you need more information.
Notes: We are not using class transform properties.
/**
* Static const that keeps a list of records
*/
const Library = [
{
id: 1,
title: 'Service Bell',
sound: 'http://soundbible.com/grab.php?id=2218&type=mp3',
image: 'https://i.ebayimg.com/images/i/401039903298-0-1/s-l1000.jpg',
},
{
id: 2,
title: 'Dog',
sound: 'http://soundbible.com/grab.php?id=2215&type=mp3',
image: 'https://lovinlife.com/wp-content/uploads/2018/09/Dog.jpg',
}
]
class App extends React.Component {
constructor(props) {
super(props);
/**
* State for storing selected record id
*/
this.state = {
activeRecordId: null,
};
this.setActiveRecord = this.setActiveRecord.bind(this);
};
/**
* Sets active record
* #param id {string}
*/
setActiveRecord(id) {
this.setState({
activeRecordId: id,
});
};
/**
* Render
*/
render() {
return (
<div className="App">
<h5>Library</h5>
{Library.map(record => record.id).map(recordId => <Record recordId={recordId} set={this.setActiveRecord} />)}
{this.state.activeRecordId && <ActiveRecord activeRecordId={this.state.activeRecordId}/>}
</div>
)
}
}
/**
* Displays a record
*/
class Record extends React.Component {
constructor(props) {
super(props);
// binds handle click so that you can access
// props within the function
this.handleClick = this.handleClick.bind(this);
};
/**
* Call parent prop that sets the active record id back
* in the parent container
*/
handleClick() {
this.props.set(this.props.recordId);
};
/**
* Render record based on props
*/
render() {
const record = Library.find(record => record.id === this.props.recordId);
return (
<div>
{record.title}
<button onClick={this.handleClick}>View</button>
</div>
);
}
}
/**
* Displays an active record
*/
function ActiveRecord({ activeRecordId }) {
const activeRecord = Library.find(record => record.id === activeRecordId);
return (
<div>
<h3>Active Record</h3>
<img width="100" src={activeRecord.image} />
<audio controls>
<source src={activeRecord.sound} type="audio/mp3" />
</audio>
</div>
)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
I'm working on a React component that the user can dynamically add / remove children (kind of like a todo-list style).
The challenge is that the user can click a button to add in / remove children and, so, any new child may not have anything unique about it to set as the key element (aside from, maybe, the creation time) and, since, the user can delete any one of these component and, then, re-add more, the index won't work.
Here's what I've come up with that seems to work:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [ XXXX ]
};
}
addSection(title, content, key = this.generateNewKeyVal()) {
this.setState({
data:
[...this.state.data,
{
title: title,
content: content,
key: key
}
]
});
}
generateNewKeyVal() {
if(this.state.data.length === 0)
return 1;
return Math.max.apply(Math, this.state.data.map(d => d.key)) + 1;
}
removeFunc(key) {
this.setState({
data: this.state.data.filter(e1 => e1.key !== key)
});
}
}
export default ParentComponent;
As I said, my generateNewKeyVal() function seems to work perfectly since it ensures it generates a new, unique integer value for key (based upon the values currently in the array, that is) and, so long as that key remains in the array, the function will ensure a higher number will be created for a new item's key.
My challenge is that I'm SO new to React that I'd like to make sure I'm not making some huge mistake here or if there is a better way to generate a key in this kind of situation.
I currently dynamically render the same component when clicking a button and the latest component is rendered on the top of the list.
Now, I want to delete the component. Each component has a cancel button to delete the rendered component. So I should be able to delete the component no matter where it is in the list.
Here's what I have so far:
local state:
state = {
formCount: 0
}
add and cancel:
onAddClicked = () => {
this.setState({formCount: this.state.formCount + 1});
}
onCancelButtonClicked = (cancelledFormKey: number) => {
const index = [...Array(this.state.formCount).keys()].indexOf(cancelledFormKey);
if (index > -1) {
const array = [...Array(this.state.formCount).keys()].splice(index, 1);
}
}
Parent component snippet:
{ [...Array(this.state.formCount).keys()].reverse().map(( i) =>
<Form key={i}
onCancelButtonClicked={() => this.onCancelButtonClicked(i)}
/>)
}
The only thing is I'm not sure how to keep track of which form was cancelled/deleted. I think I would need to create a new object in my local state to keep track but how do I know which index of the array I deleted as part of state? I'm not sure how do that? As I am using the count to make an array above.
Usually, this isn't how you'd generate a list of items. You're not storing the form data in the parent, and you're using index based keys which is a no-no when you're modifying the array. For example, I have an array of size 5 [0, 1, 2, 3, 4], when I remove something at position 2, the index of all the items after it changes causing their key to change as well, which will make react re-render them. Since you're not storying the data in the parent component, you will lose them.
Just to humor you, if we want to go with indexed based keys, we may have to maintain a list of removed indexes and filter them out. Something like this should do the trick:
state = {
formCount: 0,
deletedIndex: []
}
onCancelButtonClick = (cancelledIndex: number) => setState((prevState) => ({
deletedIndex: [...prevState.deletedIndex, cancelledIndex]
});
And your render would look like:
{
[...Array(this.state.formCount)].keys()].reverse().map((i) => (
if (deletedIndex.includes(i) {
return null;
} else {
<Form key={i} ... />
}
))
}
As a rule of thumb though, avoid having index based keys even if you don't care about performance. It'll lead to a lot of inconsistent behavior, and may also cause the UI and the state to be inconsistent. And if you absolutely want to for fun, make sure the components that are being rendered using index based keys have their data stored at the parent component level
I am making a task manager app with React.js and I want to use Drag&Drop function in order to throw an element into the trash can. I am able to use (index) in .forEach, but not sure how to do it inside the trashcan <'div>
Currently I am using array of categories in order to store the tasks depending on their category, but I want to know if there is a way of using .splice() on this situation.
Video of my app for better understanding : https://youtu.be/ug9jzjdbF-I
My state is just an empty where I am pushing data from my "submit" button.
this.state = {tasks:[]}
Empty array of categories (busy,todo,removed,complete) to show the state of the task.
let tasksInBoard = {
todo : [],
busy : [],
complete:[],
removed : []
}
onDragStart function:
onDragStart = (e,id) => {
console.log("dragstart ",id);
e.dataTransfer.setData("id",id);
}
And .forEach to put them on the arrays depending on their category.
this.state.tasks.forEach((t,index)=>{
tasksInBoard[t.category].push(
<CreatedTask
index={index}
key = {index}
member={t.member}
todo={t.toDo}
dod={t.dod}
time={t.time}
draggable = "true"
onDragStart={(e)=> this.onDragStart(e,(t.dod+t.member+t.toDo+t.time))}
styling = {t.styling}
/>
)
}
)
My trash can code div :
<div className="trashCan"
onDrop={(e)=>this.onDrop(e,"removed")}
onDragOver={(e)=>this.onDragOver(e)}>
<p>this is trahscan</p>
</div>
I have an issue trying to update a list of stateful components created with by mapping a list of strings. The issue is showing when i remove one of this components by slicing the array to remove an element by its index.
Every component has his own state, that im fetching from an API. the problems is that when i remove an element of the array, the state of next component overlaps the one that i deleted Component.
My code looks something similar to this:
class MyDashboard extends React.Component {
constructor(props){
super(props);
this.state = {
activeItems: [0,1,2,3],
}
this.removeItem = this.removeItem.bind(this);
}
removeItem(indx){
let tempItems= this.state.activeItems;
tempItems.splice(indx,1);
this.setState({activeItems:tempItems});
}
render(){
let conditionalDash;
let conditionalComponent;
let temporalArray = this.state.activeEntries.map((entry , i) => {
return (<MyDash key={i} index {i}/> removeItem={this.removeItem});
});
render(){
return (
<div id='dashContainer'>
{temporalArray}
</div>
)
}
}
In my MyDashComponent i have a something like this:
class MyDash extends React.Component{
constructor(props){
super(props);
this.state={
fetchedData:null,
}
}
componentDidMount(){
API.fetchData(this.props.index).then(response => {
this.setState({fetchData:response.data})
)
}
render(){
return(
<div> {this.props.index} {this.state.fetchedData}</div>
)
}
}
Is there something that i'm missing?
The behavior that i'm getting is that when the i remove the this.state.activeItems[2] the state of this element is the same as the previous component. I was expecting that the state of the element[2] will be the same state that has the element[3].
Edit:
Something that i forget to tell, is that the props of MyDash component are correct, is just the state that doesnt belong to the component, it is from the deleted component.
Thanks for reading and i hope that somebody can help me with this.
I found the bug it was that the key of the list that i was using, it was the index of the map method, i read that it has to be a unique key. Luckily this fixed the render action and the state doesnt overlap anymore.
Who have mixed the behaviour or slice and splice
slice returns you a new array whereas splice modifies the existing one
According to MDN docs:
splice: The splice() method changes the contents of an array by
removing existing elements and/or adding new elements.
Syntax: array.splice(start, deleteCount)
slice: The slice() method returns a shallow copy of a portion of an
array into a new array object selected from begin to end (end not
included). The original array will not be modified.
syntax:
arr.slice()
arr.slice(begin)
arr.slice(begin, end)
You might change your code to
removeItem(indx){
let tempItems= this.state.activeItems;
tempItems.splice(indx,1);
this.setState({ activeItems:tempItems });
}
Also you shouldn't mutate the state directly, you should create a copy of the state array and then update it.
removeItem(indx){
let tempItems= [...this.state.activeItems]; // this is do a shallow copy, you could use something else depending on your usecase
tempItems.splice(indx,1);
this.setState({ activeItems:tempItems });
}
You can also use Array.prototype.filter to remove the item:
removeItem(idx) {
this.setState({
activeItems: this.state.activeItems.filter((_, index) => index !== idx)
});
}
or
removeItem(idx) {
this.setState(prevState => ({
activeItems: prevState.activeItems.filter((_, index) => index !== idx)
}));
}
I'm using React to render long scrollable list of items (+1000). I found React Virtualized to help me with this.
So looking at the example here I should pass down the list as a prop to my item list component. What's tripping me up is that in the example the list is immutable (using Immutable.js) which I guess makes sense since that's how the props are supposed to work - but if I want to make a change to a row item I cannot change its state since the row will be rerendered using the list, thus throwing out the state.
What I'm trying to do is to highlight a row when I click it and have it still be highlighted if I scroll out of the view and back into it again. Now if the list is not immutable I can change the object representing the row and the highlighted row will stay highlighted, but I'm not sure that's the correct way to do it. Is there a solution to this other than mutating the props?
class ItemsList extends React.Component {
(...)
render() {
(...)
return(
<div>
<VirtualScroll
ref='VirtualScroll'
className={styles.VirtualScroll}
height={virtualScrollHeight}
overscanRowCount={overscanRowCount}
noRowsRenderer={this._noRowsRenderer}
rowCount={rowCount}
rowHeight={useDynamicRowHeight ? this._getRowHeight : virtualScrollRowHeight}
rowRenderer={this._rowRenderer}
scrollToIndex={scrollToIndex}
width={300}
/>
</div>
)
}
_rowRenderer ({ index }) {
const { list } = this.props;
const row = list[index];
return (
<Row index={index} />
)
}
}
class Row extends React.Component {
constructor(props) {
super(props);
this.state = {
highlighted: false
};
}
handleClick() {
this.setState({ highlighted: true });
list[this.props.index].color = 'yellow';
}
render() {
let color = list[this.props.index].color;
return (
<div
key={this.props.index}
style={{ height: 20, backgroundColor: color }}
onClick={this.handleClick.bind(this)}
>
This is row {this.props.index}
</div>
)
}
}
const list = [array of 1000+ objects];
ReactDOM.render(
<ItemsList
list={list}
/>,
document.getElementById('app')
);
If you only render let's say 10 out of your list of a 1000 at a time, then the only way to remember highlighted-flag, is to store it in the parent state, which is the list of 1000.
Without immutability, this would be something like:
// make a copy of the list - NB: this will not copy objects in the list
var list = this.state.list.slice();
// so when changing object, you are directly mutating state
list[itemToChange].highlighted = true;
// setting state will trigger re-render
this.setState({ list: list });
// but the damage is already done:
// e.g. shouldComponentUpdate lifecycle method will fail
// will always return false, even if state did change.
With immutability, you would be doing something quite similar:
// make a copy of the list
var list = this.state.list.slice();
// make a copy of the object to update
var newObject = Object.assign({}, list[itemToChange]);
// update the object copy
newObject.highlighted = true;
// insert the new object into list copy
list[itemToChange] = newObject;
// update state with the new list
this.setState({ list : list );
The above only works if the object does not contain more nested objects.
I am not familiar with immutable.js, but I'm sure they have excellent methods to deal with this more appropriately.
The argument for immutability in react is that you can reliably and transparently work with state changes (also react's life-cycle methods expect them). There are numerous questions on SO with a variant of "why is nextState == this.state", with answers coming down to "not keeping state and props immutable screwed things up"