React design question about creating remove functionality - reactjs

This is a little long winded.
I would like advice on how to go about making the functionality of a remove button. I'm relatively new to react and should have planned this project before I embarked. Either way I am here now and looking for some advice before I remove functionality tomorrow.
In this component I create a method called "displayFood" in which I take an array from props that has in it string values of the names of foods that the user wanted to add to the refrigerator. For example: [yogurt, milk, egg, yogurt, yogurt]. I then create an object that maps this array to key value pairs based on name and quantity, for example: {"yogurt": 3, "milk": 1, "egg": 1}. After this I create an array that holds what I want to render to the user which is each item that they put in the fridge and the quantity of that item. I also have a remove button. I have been thinking about how to remove Items but do not know how to go about doing so.
If, for example the user deletes yogurt I want the value to decrease by one, and if the user deletes a item with quantity 1 then it should go away.
This is a pretty specific question thank you for your time.
import React from 'react';
import "./style.scss";
function InFrige(props) {
const handleRemove = (e, counts) => {
console.log(e.target.name)
}
const displayFood = () => {
var counts = {};
props.foodAddedByUser.forEach(function(x) { counts[x] = (counts[x] || 0) + 1; });
let foodItems = []
for(var index in Object.entries(counts)){
foodItems.push(
<div className="inFrige-food-item" key={index}>
<h3>{Object.keys(counts)[index]} x{Object.values(counts)[index]}</h3>
<button onClick={handleRemove} name={Object.keys(counts)[index]}>Remove</button>
</div>
)
}
return foodItems
}
return (
<div>
<div className="inFrige-food-container">
{displayFood()}
</div>
</div>
)
}
export default InFrige;

Your problem is you are trying to alter your props from within the component. You could either handle this inside the component with state or give a callback via props from the parent component and handle the deletion there, something like this:
<button onClick={()=>{this.props.handleRemove(Object.keys(counts)[index])}} name={Object.keys(counts)[index]}>Remove</button>
in parents render:
<InFridge handleRemove={(item)=>{foodAddedByUser.delete(item)} foodAddedByUser={foodAddedByUser} />

Related

React hooks - Prevent rerender if parent state that holds children props changed

I have an issue with my current project that uses react hooks.
What I'm trying to do is just to select my tasks by using (shift+click). Look like this:
Here is the code:
...
const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([])
const selectTask = useCallback(
(e: MouseEvent<HTMLDivElement>, taskId: string): void => {
e.stopPropagation()
const previousTaskId = selectedTaskIds[selectedTaskIds.length - 1]
if (previousTaskId && e.shiftKey) {
// handle shift+click
const previousIdx = tasks.findIndex((task) => task.id === previousTaskId)
const selectedIdx = tasks.findIndex((task) => task.id === taskId)
const rangeTasks =
previousIdx < selectedIdx
? tasks.slice(previousIdx, selectedIdx + 1)
: tasks.slice(selectedIdx, previousIdx + 1)
const rangeIds = rangeTasks.map((task) => task.id)
setSelectedTaskIds([...new Set([...selectedTaskIds, ...rangeIds])])
} else {
// if no key clicked, just select 1 task item
setSelectedTaskIds([taskId])
}
},
[selectedTaskIds, tasks] // <==== in here I notice that activeTaskIds is changed overtime that causes all of my <TaskItem> rerender
)
return (
{tasks.map((task) => (
<TaskItem
key={task.id}
taskId={task.id}
onClick={selectTask} // <=== selectTask will be different if user click on one of the task items
active={selectedTaskIds.includes(task.id)}
/>
))}
)
The problem is, to know which tasks should I select when the user uses shift+click, I need to know the currently selected task ids, so that I need to pass selectedTaskIds as a useCallback() deps.
That makes whenever the user selects the tasks or even just a click on one of the task items to select the task, it will re-render all of my <TaskItem> since the selectTask() function change due to useCallback's deps changed.
How can I solve this without rerender all of my <TaskItem>s? Thank you so much!
I tested your code on my machine and tested out a few scenarios. As far as I can tell, it looks natural for the component to re-render the all of the <TaskItem>s because any change in the selectedTaskIds state will guarantee everything inside the component that holds selectedTaskIds to render. To show you a concrete example,
<div className="App">
<TaskItems />
<div>hahaha</div>
<div>selectedTaskIds</div>
</div>
Let's say you have the above code. (I named your component that holds multiple <TaskItem/>s as <TaskItems/>) When onClick of <TaskItem/> triggers, only <TaskItems/> will re-render. The two other divs are not re-rendered. However, if you place the two divs inside the <TaskItems/> component, they will re-render:
// assuming this is inside <TaskItems/>
...
return (
<div>
{tasks.map((task) => (
<TaskItem
key={task.id}
taskId={task.id}
onClick={(e) => { selectTask2(e, task.id)}} // <=== selectTask will be different if user click on one of the task items
// active={selectedTaskIds.includes(task.id)}
active={true}
title={task.title}
/>
))}
<div>hahaha</div>
<div>selectedTaskIds</div>
</div>
);
above code will re-render the two divs.
I have tried to fulfill your request to get rid of the re-renders of the tasks that weren't changed, but it was really hard to do so. When I try to prevent re-rendering I usually use one of the two techniques:
create a child component and separate the code base to isolate groups of states. (since states are what triggers renders, you can
separate unrelated ones into different groups.)
useCallback/useMemo
Either techniques I failed to implement for your case, but there may be a way to apply the above techniques. I will follow the thread to see if anyone else gets a solution.

React Stateless Components: Interacting with their output and appearance

I have looked around for an answer to this - the closest I found being this question - but there is I think a significant difference in my case (the fact that it starts to get into the parent holding the state of its children's... children) which has finally lead to me asking for some clarification.
A very simple example of what I mean is below (and will hopefully better illustrate what I'm asking):
Suppose we have a bunch of book documents like
bookList = [
{
title: "book 1",
author: "bob",
isbn: 1,
chapters: [
{ chapterNum: 1, chapterTitle: "intro", chapterDesc: "very first chapter", startPg: 2, endPg: 23 },
{ chapterNum: 2, chapterTitle: "getting started", chapterDesc: "the basics", startPg: 24, endPg: 45 }
]},
{
title: "book 2" ... }
]
So main point being these embedded objects within documents that could be very long and as such may be collapsed / expanded.
And then here is a rough sample of code showing the components
class App extends Component {
constructor(props) {
super(props);
this.state = {
books: bookList,
focusBook: null
}
this.updateDetailDiv = this.updateDetailDiv.bind(this);
}
updateDetailDiv(book) {
this.setState(
{ focusBook: book}
);
}
render() {
return(
<BookList
bookList = {this.state.books}
updateDetailDiv = { this.updateDetailDiv }
/>
<BookDetail
focusBook = { this.state.focusBook }
/>
);
}
}
const BookList = props => {
return (
props.bookList.map(item=>
<li onClick={()=> props.updateDetailDiv(item)}> {item.title} </li>
)
);
}
const BookDetail = props => {
return (
<div className="bookDetails">
{ props.focusBook != null
? <div>
{props.focusBook.title},
{props.focusBook.author},
{props.focusBook.isbn}
Chapters:
<div className="chapterList">
{ props.focusBook.chapters.map(item=>
<span onClick={()=>someFunction(item)}>{item.chapterNum} - {item.chapterName}</span>
)}
</div>
<div id="chapterDetails">
This text will be replaced with the last clicked chapter's expanded details
</div>
</div>
: <div>
Select A Book
</div>
})
}
someFunction(item) {
document.getElementById('chapterDetails').innerHTML = `<p>${item.chapterDesc}</p><p>${item.startPg}</p><p>${item.endPg}</p>`;
}
So my problem is that i'm not sure what the best approach is for handling simple cosmetic / visual changes to data in functional stateless components without passing it up to the parent component - which is fine and makes sense for the first child - but what happens when many children will have their own children (who may have their own children) --> all requiring their own rendering options?
For example - here the App component will re-render the DetailDiv component (since the state has changed) - but I don't want the App also handling the DetailDiv's detailed div. In my example here its all very simple but the actual application I'm working on has 2 or 3 layers of embedded items that - once rendered by App - could realisticially just be modified visually by normal JS.
SO in my example you'll see I have a someFunction() in each Chapter listing - I can make this work by writing a separate simple 'traditional JS DOM function' (ie: target.getElementById or closest() -- but i don't think i'm supposed to be using normal JS to manipulate the DOM while using React.
So again to summarize - what is the best way to handle simple DOM manipulation to the rendered output of stateless components? Making these into their own class seems like overkill - and having the 'parent' App handle its 'grandchildren' and 'great-grandchildren's state is going to be unwieldy as the Application grows. I must be missing an obvious example out there because I haven't seen much in the way of handling this without layers of stateful components.
EDIT for clarity:
BookDetail is a stateless component.
It is handed an object as a prop by a parent stateful component (App)
When App's state is changed, it will render again, reflecting the changes.
Assume BookDetail is responsible for displaying a lot of data.
I want it so each of the span in BookDetail, when clicked, will display its relevant item in the chapterDetail div.
If another span is clicked, then the chapterDetail div would fill with that item's details. (this is just a simple example - it can be any other pure appearance change to some stateless component - where it seems like overkill for a parent to have to keep track of it)
I don't know how to change the UI/appearance of the stateless component after it is rendered without giving it state OR making the parent keep track of what is essentially a 'substate' (since the only way to update the appearance of a component is to change its state, triggering a render).
Is there a way to do this without making BookDetail a stateful component?
You can add a little bit of simple state to functional components to track the selected index. In this case I would store a "selected chapter index" in state and then render in the div the "chapters[index].details", all without manipulating the DOM which is a React anti-pattern.
The use-case here is that the selected chapter is an internal detail that only BookDetail cares about, so don't lift this "state" to a parent component and since it is also only relevant during the lifetime of BookDetail it is rather unnecessary to store this selected index in an app-wide state management system, like redux.
const BookDetail = ({ focusBook }) => {
// use a state hook to store a selected chapter index
const [selectedChapter, setSelectedChapter] = useState();
useEffect(() => setSelectedChapter(-1), [focusBook]);
if (!focusBook) {
return <div>Select A Book</div>;
}
const { author, chapters, isbn, title } = focusBook;
return (
<div className="bookDetails">
<div>
<div>Title: {title},</div>
<div>Author: {author},</div>
<div>ISBN: {isbn}</div>
Chapters:
<div className="chapterList">
{chapters.map(({chapterName, chapterNum}, index) => (
<button
key={chapterName}
onClick={() => setSelectedChapter(selectedChapter >= 0 ? -1 : index)} // set the selected index
>
{chapterNum} - {chapterName}
</button>
))}
</div>
// if a valid index is selected then render details div with
// chapter details by index
{chapters[selectedChapter] && (
<div id="chapterDetails">
{chapters[selectedChapter].details}
</div>
)}
</div>
</div>
);
};
DEMO
There is some approaches you can do to solve this problem.
First, you don't need to create some class components for your functional components, instead, you can use react hooks, like useState so the component can control it's own content.
Now, if you don't want to use React Hooks, you can use React Redux store to manage all your states: you can only change the state values using the Redux actions.
Happy coding! :D

How can you generate predictable react keys for items without unique ids?

At times, I map over collections without unique ids. In that case, react logs this warning to the console: Each child in an array or iterator should have a unique "key" prop. To solve this issue, I created the following es6 module.
const key = {
count: 0,
getKey: () => {
key.count += 1;
return key.count;
},
};
export default key;
Now, I import the key module throughout my React application and call its getKey method when I need a unique key. But, I don't feel like the keys this generates are predictable like React advises. While the first render might map a collection with keys 1-10 predictably, any render afterwards will generate a new key for that element. Using a package like UUID will have the same effect will it not? What is the proper way to generate predictable React keys? Am I misunderstanding what they mean by predictable? Thanks!
Generating the keys before rendering the component is the best way to end up with predictable ones.
In an ideal world you will be working with lists of objects that already have their own unique id and in those cases it's best to use those ids as keys.
If you were rendering a list of numbers that might include duplicates, you could give them each a fixed key before passing them to a component to render.
let numbers = [1, 1, 2, 3];
let numbersWithKeys = numbers.map(x => {
return { value: x, key: key.getKey() };
});
function Numbers({ numbers }) {
return (
<ul>
{numbers.map(number => <li key={number.key}>{number.value}</li>)}
</ul>
)
}
render(
<Numbers numbers={numbersWithKeys} />
);
Then every single time your component renders it can safely use the same key for each number. You can imagine this component rendering the following virtual tree:
<ul>
<li key={1}>1</li>
<li key={2}>1</li>
<li key={3}>2</li>
<li key={4}>3</li>
</ul>
It would be possible for the <Numbers> component to reverse the list and you'd still end up with the same key for each item. That's what React means by predictable.
React can make some performance shortcuts when you change the order of a list by moving the rendered elements around, rather than re-rendering the entire thing, but if you're generating your keys inside your render method, then it sees a brand new list each time and has to re-render it all.
Here are a couple of things to consider:
Unique: Keys must be unique, but for the current array only.
Predictable: Keys must consistently map to the same element.
Keys do not have to be ridiculously convoluted identifiers.
What's predictable? If we start with (index:key): 1:1,2:2,3:3
and we remove the element on index 2, we should be left with: 1:1,2:3. The item at index 2 with key 2 has been removed, and the item on index 3 has moved to index 2, but retained its key 3.
The simplest way is of course to use a unique property in your data, like your database id (primary key), but that's not always possible. Let's say you want to create a dynamic form with input fields that can be added or removed. The input field elements may look exactly the same, except for a unique identifier which is dynamically assigned. The simplest way is of course using the map index, but that's not predictable, since it will change if you remove the second of three fields, for example.
What are our options?
One solution is to track your elements in state.
class MyComponent extends Component {
constructor() {
super();
this.state = {
myArr: []
};
}
_addHandler = el => {
const arr = this.state[el];
const length = arr.length;
const lastId = length > 0 ? arr[length - 1] : 0;
this.setState({ [el]: [...arr, lastId + 1] });
};
_removeHandler = (el, i) => {
const newItems = [
...this.state[el].slice(0, i),
...this.state[el].slice(i + 1)
];
this.setState({ [el]: newItems });
};
render() {
const myList = this.state.myArr;
return (
<div>
{myList.map((el, i) => (
<p>
{this.state.myArr[i]}{" "}
<span onClick={() => this._removeHandler("myArr", i)}>
remove
</span>
</p>
))}
<p onClick={() => this._addHandler("myArr")}>Add One</p>
</div>
);
}
}
You could write a simple wrapper component to track elements in an array using this concept, if it's something you're going to be doing a lot.
To assign the initial keys you could do the following in componentDidMount()
componentDidMount() {
const {someArr} = this.props;
// use the index to map the initial key values
// after which use state to track the key pairings
const newIds = someArr.map((el,i) => i+1);
this.setState({myArr:newIds});
}
you can use any data specifically related to the item... or (not recommended) for random id you can use new Date().getTime()
key is used for reusing components... it's important to give the same key for the same item
[].map(item, index) => <Item key={'this_list_specific_name' + item.id} />

React- onClick styling of currentTarget

I am trying to build a simple dynamically updated, interactive list that styles each <li></li> according to the css rules of a .clicked class, when you click on them.
The app is composed of two components, a parent and a child and the code in question is the following (taken from the child):
handleClick(e) {
document.getElementById(e.currentTarget.id).setAttribute("class","clicked");
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x,i)=>{ return (<li id={i} key={i} className={i%2==0 ? "white" : "grey"}
onClick={this.handleClick}>{x}</li>); })
return (
<div>
<ul id="ul">{ pro }</ul>
</div>
What is happening here is basically that the parent is passing to the child a sentences prop (an array of sentences that will form the basis for the formation of a dynamic list).
The controversial part is me using DOM manipulation in the form of document.getElementById(e.currentTarget.id).setAttribute("class","two");
in order to change the class of the dynamically created html from jsx.
The code above works, however it does not feel as best practice. The whole advantage in using react is to use virtual dom and optimize the way the DOM is updated.
My questions are the following:
1) Am I right to feel this way? (that my solution is not best practice?)
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
If you know this question to be a duplicate, please leave a comment and I ll remove it.
1) Am I right to feel this way? (that my solution is not best practice?)
It is correct to assume that this is not an ideal approach, manipulating the DOM via vanilla js in React has its place (Example Use Cases) but should not be done unless absolutely necessary. Also, it is not ideal to use the index from Array.prototype.map as the key on your components as if they change order it can cause confusion for React as the keys would map differently in that case.
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
You should make use of the component state. If you want each clicked element to maintain the clicked class then make a piece of state that caches the elements that have already recieved the clicked class. if only the most recently clicked element gets the clicked class then simply cache an identifier to the appropriate element in the state. You could also use refs for this purpose though the overusage of them is somewhat discouraged by facebook.
Here is a quick snipped that will toggle the click class on each <li>
class Test extends Component {
constructor() {
super();
this.state = {
clicked: {}
};
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x, i) => {
const color_class = i % 2 === 0 ? "white" : "grey";
const clicked_class = this.state.clicked[i] === true ? "clicked" : "";
let clicked = Object.assign({}, this.state.clicked); // Dont mutate state!!!
return (
<li
id={i}
key={i}
className={`${color_class} ${clicked_class}`}
onClick={e => {
if (clicked.hasOwnProperty(i)) {
delete clicked[i];
} else {
clicked[i] = true;
}
this.setState({ clicked });
}}
>
{x}
</li>
);
});
return (
<div>
<ul id="ul">
{pro}
</ul>
</div>
);
}
}

How do I manage UI state among subcomponents?

I want to display a selectable list of objects with react. I've got the list component, which owns several groups, which own several elements. Obviously, the list data itself should go into a store, but what about the UI state (e.g., which element is selected)? It's ostensively a property of the root list element, but to pass it down to the elements, each element along the chain (well, just 1 in this case- the group) would need to pass it along even if they don't care about it. And if I want to add another property I need to pass it down the chain rather verbosely.
Also, encapsulation. How do I make a component that might be instanced multiple times with different data?
In other languages I would just pass the list pointer down the chain, so that child elements could whatever they want from it, so everything stays encapsulated. Would the flux way be to pass the store down to child elements? Would you have a separate store for UI state vs the persistent data?
Let's design this from the bottom up. So, at the bottom we have a ListItem.
What does the list item need in order to render? Let's say it's a title, a body, and if it's selected or not.
Also what events make sense of the list item? Probably just onClick.
Does it have any state? No, unless we need to handle things like it being hovered, or other state specific to the ListItem, but not relevant to its parent.
var ListItem = React.createClass({
render(){
var classNames = ["list-item"];
if (this.props.selected) classNames.push("selected");
return (
<li className={classNames.join} onClick={this.props.onClick}>
<div className="title">{this.props.title}</div>
<div className="body">{this.props.body}</div>
</li>
);
}
});
The ListGroup is a bit less obvious.
For props we'll ask for a list of items, the selected index (if any), and an onSelectionChange callback, which is called with (nextIndex, lastIndex).
This component also has no state. For extra points, we'll allow specifying a custom renderer, making this more reusable. You can pass renderer={component} where component is something that implements the ListItem interface described above.
var ListGroup = React.createClass({
getDefaultProps: function(){
return {renderer: ListItem, onSelectionChange: function(){}}
},
render(){
return (
<div>{this.props.items.map(this.renderItem)}</div>
);
},
renderItem(data, index){
var selectedIndex = this.props.selectedIndex;
return (
<this.props.renderer
selected={selectedIndex === index}
key={i}
onClick={() => this.props.onSelectionChange(index, selectedIndex)}
{...data} />
}
});
And then we could render ListGroup like this:
<ListGroup
items={[{title: "foo", body: "bar"}, {title: "baz", body: "quux"]}
selectedIndex={this.state.i}
onSelectionChange={(index) => this.setState({i: index})} />
The data is passed down the tree, but only the essence of the data required to render each component. ListItem doesn't need the full list of items, nor the selected index. It doesn't care if multiple selections are allowed, or just one. All it knows is that it's selected when this.props.selected is truthy, and otherwise it's not.

Resources