Accessing state using a variable - javascript

I have started learning React, and decided to create my first app similar to online food ordering cart.
My problem started with accessing the state of the current number of the particular dish in the shopping cart ( if I want to order 2 pizzas it would show '2x Pizza' in the shopping cart ).
As I don't really want to use if method as it would make my code not really professional, I wanted to ask you if do you know how to sort out my problem.
What I've got for the moment is that part of the code:
this.state = {
values: {
pizza:0
}
}
<div className="item" onClick={(e) => this.addItem(e)} itemprice={1250} foodtype="values.pizza"><p className="itemDesc">Pizza</p></div>
addItem(e) {
let foodType = clickObj.getAttribute("foodtype"); // values.pizza
this.setState({
[foodType]: [foodType] + 1
});
}
I expected to increase this.state.value.pizza by 1 on each click of the button. I have to specify the variable with an attribute name because there would be quite a few of positions to order and I want to keep my code as tidy as I can.
Unfortunately, I can access the state but the value of that state is not being changed after click. (checked using console.log)

If you want to use previous state value to increment, you can use the following code.
this.setState(prevState => ({[foodType]: prevState[foodType] + 1}))
Also you can use https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en extention for chrome which will give you details about the state and other values.
Hope that answers your question.

Related

Conditional Rendering of Arrays in React: For vs Map

I'm new to React and building a calendar application. While playing around with state to try understand it better, I noticed that my 'remove booking' function required a state update for it to work, while my 'add booking' function worked perfectly without state.
Remove bookings: requires state to work
const [timeslots, setTimeslots] = useState(slots);
const removeBookings = (bookingid) => {
let newSlots = [...timeslots];
delete newSlots[bookingid].bookedWith;
setTimeslots(newSlots);
}
Add bookings: does not require state to work
const addBookings = (slotid, tutorName) => {
timeslots[slotid].bookedWith = tutorName;
}
I think that this is because of how my timeslot components are rendered. Each slot is rendered from an item of an array through .map(), as most tutorials online suggest is the best way to render components from an array.
timeslots.map(slot => {
if (!slot.bookedWith) {
return <EmptyTimeslot [...props / logic] />
} else {
return <BookedTimeslot [...props / logic]/>
}
})
So, with each EmptyTimeslot, the data for a BookedTimeslot is available as well. That's why state is not required for my add bookings function (emptyTimeslot -> bookedTimeslot). However, removing a booking (bookedTimeslot -> emptyTimeslot) requires a rerender of the slots, since the code cannot 'flow upwards'.
There are a lot of slots that have to be rendered each time. My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots? This I assume would require state to be used for both the add booking and remove booking function. Like this:
for (let i=0;i<timeslots.length;i++) {
if (!timeslot[i].bookedWith) {
return <EmptyTimeslot />
} else {
return <BookedTimeslot />
}
}
Hope that makes sense. Thank you for any help.
Your addBooking function is bad. Even if it seems to "work", you should not be mutating your state values. You should be using a state setter function to update them, which is what you are doing in removeBookings.
My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots?
Your map approach is not rendering both. For each slot, it uses an if statement to return one component or the other depending on whether the slot is booked. I'm not sure how the for loop you're proposing would even work here. It would just return before the first iteration completed.
This I assume would require state to be used for both the add booking and remove booking function.
You should be using setTimeslots for all timeslot state updates and you should not be mutating your state values. That is true no matter how you render them.

Appears that React is not re-rendering after state update in useeffect (Reask)

Summarize the Problem
Goal
I am attempting to create a cart feature for my Shop Inventory project. This cart will basically contain a list of product ids and eventually allow a user to see all of the items in this 'cart'. Once the user feels like they have added all of the products they'd like to purchase to their cart, they will have a button within the cart page to purchase all products in the cart.
Ideally, I will store a list of product ids in local storage, and once the user navigates to a certain route, I will grab the list from local storage using JSON.parse/JSON.stringify and implement the feature accordingly (Putting the products ids into a state variable called products). It seems to be working fine except for a bit of debugging code I put in place.
Expected VS Actual
I have put this debugging code inside of my component to show me the list of products that have been grabbed from local storage.
{products.map((product) => {
<h2>{product}</h2>;
})
}
However, whenever I load the page the only content I see is more debugging content like "Hello World" I put above it. What I expected to see when I loaded the page was a list of ids that corresponded to the products put into the cart. I know that it had at least one product inside of the 'products' state variable by looking into the react dev tools.
Error Messages
There are no error messages.
Describe what you've tried
Debugging attempts
React Dev Tools
I was able to confirm that even though the state variable list isn't being displayed, the state variable itself is getting updated. I was able to verify this with the React Dev Tools.
VS Debugger
I also attempted to look through the debugger before and after the state had been set. The array I set it to seems to be correct:
I noticed that after going through quite a bit of the internal workings of React it came back to my code inside of useEffect. And when it did the products state variable was updated correctly. However, in the next few steps I took with the debugger, it didn't seem to go back to my visual part to re-render it.
Stack Overflow
I have searched through some Stack Overflow questions that popped up while asking this question. I believe the closest question is this one which hasn't been answered yet. Some of them are troubles with working with child components and directly accessing the state variable instead of using set both of which I am not doing. For the child issues, the major one I looked at was this one. For the directly accessing this is the major one.
Show some code
Cart.js
Using a functional react component, I have a 'products' state variable that I attempt to populate in the useEffect hook with a certain key in local storage. Below is my Cart component. You may notice there is also another state variable called 'addingResource' which is used to indicate if the Cart component has been created to add a resource to the cart or to simply display it. As you can see as well the addingResource state variable gets updated in the useEffect hook. I'm not entirely sure if that is possibly what is affecting the products state variable, but I figured I would note it here in case it is. Once again the main problem is that the products map function inside of the return statement does not show any items, even after the products state variable appears to be updated.
function Cart(props) {
const [products, setProducts] = React.useState([]);
const [addingResource, setAddingResource] = React.useState(false);
const REACT_APP_CART_TOKEN_NAME = process.env.REACT_APP_CART_TOKEN_NAME;
// Ensures that addingResource is updated to true if needed
React.useEffect(() => {
if (props.match && addingResource === false) {
setAddingResource(true);
}
const stringVersion = window.localStorage.getItem(
REACT_APP_CART_TOKEN_NAME
);
let productIdsList = [];
if (stringVersion) {
productIdsList = JSON.parse(stringVersion);
console.log("test");
}
if (props.match) {
productIdsList.push(props.match.params.id);
window.localStorage.setItem(
REACT_APP_CART_TOKEN_NAME,
JSON.stringify(productIdsList)
);
}
alert("Updating Products");
if (!products) {
setProducts(productIdsList);
}
}, []);
// TODO: Read the products key from the local storage
return (
<>
<h1>Match: {`${addingResource}`}</h1>
{products &&
products.map((product) => {
<h2>{product}</h2>;
})}
</>
);
}
Local Storage 'Cart' Token
Additionally, the 'cart' local storage token that is being accessed in this component has this structure:
[
'5fccb14ed0822f25ec5cee63'
]
Finishing Remarks
This is a recreated question from my originally closed question you can find here. I believe the reason it was closed is that I was having problems submitting my question. There appeared to be a section of code that stack overflow said was invalid. I had looked through all of the code segments I had put in and none of them seemed off. I even tried formatting the spacing in a text editor and pasting it over a few times to make sure it had the right spacing. I even removed all of the code snippets and tried to submit it that way. However, it still told me there was an error somewhere in my post. I then tried to use the process of elimination to resolve this issue by slowly removing sections of my post and trying to submit it. The problem this caused was that my initial posting had only a slight portion of the original post. I was trying to quickly find the error and fill in the rest of the information. I was able to eventually able to find the section of text near my "React Dev Tools" paragraph that was causing this error. After resolving this error, I was able to submit my post in its complete form. Unfortunately, it seems that my question was closed and even after adding some additional details to my original lengthy post and putting a section is asking the person who closed the question to review it again and message me if anything was wrong, it remained closed for nearly a week. Because of this, I decided to make a new post ensuring all of my original post's content was available from the first version.
I greatly appreciate any help and hope to resolve this issue for me and those in the future that come across this. Thank you!
Your useEffect looks ok. But you need to return a value inside your map function, else it won't display anything :
return (
<>
{products.map(product => <h2>{product}</h2>)}
</>
);
When you remove the braces around the content of an arrow function, it is considered as the returned value. Else, you can simply write () => { return <>blabla</> });

React component is re-rendering items removed from state

This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed

Correct way to fetch data via API in React [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am learning javascript and React and the challenge (on Hackerrank practise) is to fetch data from an API which has multiple pages. There should be as many buttons as the number of pages and on clicking each button, the data from that particular page should show up below like:
Here is the component that I wrote:
import React from 'react';
const url = "https://jsonmock.hackerrank.com/api/articles?page=";
const default_page = 1;
class Articles extends React.Component {
state = {
pageCount: 1,
body: {},
currentPage: 1,
error: null,
isLoading: false,
data: []
};
componentDidMount(){
this.setState({isLoading: true});
fetch(url+default_page)
.then(response => response.json())
.then(body => this.setState({
pageCount: body.total_pages,
data: body.data,
body: body,
}))
.catch(err => this.setState({error: err}));
}
pageButtonGenerator(){
if(this.state.body){
let pageButtons=[];
for(var i=1; i<=this.state.pageCount; i++){
const id = i; //need this to use in event handler, BUT WHY DOES i NOT WORK (the value is always i=this.state.pageCount)
pageButtons.push(<button data-testid="page-button" key={"page-button-"+i} onClick={(e) => this.buttonClickHandler(id)}>{i}</button>);
}
return pageButtons;
}
else{
return <button data-testid="page-button" key="page-button-1">1</button>
}
}
buttonClickHandler = (pageNum) => {
// console.log(pageNum);
this.setState({isLoading: true});
fetch(url+pageNum)
.then(response => response.json())
.then(body => this.setState({
pageCount: body.total_pages,
data: body.data,
body: body,
currentPage: pageNum
}))
.catch(err => this.setState({error: err}));
// this.titlesGenerator();
}
titlesGenerator = () => {
if(this.state.data){
return this.state.data.map((element,index) => {
if(element.title){ return <li key={"title-"+index+1} data-testid="result-row">{element.title}</li> }
else{ return null }
})
// console.log(this.state.data);
}
}
render() {
return (
<React.Fragment>
<div className="pagination">
{this.pageButtonGenerator()}
</div>
<ul className="results">
{this.titlesGenerator()}
</ul>
</React.Fragment>
);
}
}
export default Articles;
Although my code passed the test cases, I am not very confident if I did it the right way. I have my doubts like:
Should I be fetching all the pages in one go to avoid multiple network calls or should a call be made every time a page button is clicked?
Am I generating buttons the right way (see the pageButtonGenerator)?
Inside the for-loop in pageButtonGenerator, am I calling the onClick handler the right way? I was trying to directly pass the variable "i" but it was always = 6 (the exit value of the loop). I am struggling to understand why the variable i was also 6. I thought the closure would ensure that the value is always correct..
How should I go about what to store in state and what should be derived and not stored?
Open to constructive criticism. Thanks
You fetch a page per request for the simple reason that it is overkill to fetch all pages, if all the user visits is page 1.
&3: Typically you would rather create a widget React component that wraps the paginator in a separate component and pass number of pages to it.
This widget is merely a list that you style to be a pagination widget.
So indeed, the logic inside pageButtonGenerator is sort of unnecessary.
4: When dealing with ajax, usually I would have three properties:
{ isFetching, result, error }
result is a nested object containing all properties that are a result of the ajax call.
Properties belonging to pagination will be passed to the pagination widget.
Properties that can be derived are usually not stored; if you forget to update them, it results in bugs. Imo, it is better to derive at runtime.
More notes:
This is 2020, use functional components if you can.
I don't like it very much to do all state management in React components.
Check out Redux, and Redux observable if you want cleaner separation of concerns.
This will also answer many of your questions of what goes where.
I try to explain in easy term:
Should I be fetching all the pages in one go to avoid multiple network calls or should a call be made every time a page button is clicked?
A: No think as practical scenario if you are making web app and you have 500 pages. Does it make sense to fetch all 500 pages on the go even you cant say user can go each and every page. Good rule of thumb is you get to know about total data available and do pagination. For example user click on 5 page. Then fetch 5 page data. Before that when even you are not sure user going to click or not why you adding overhead in user browser. Hope that answer this question.
Am I generating buttons the right way (see the pageButtonGenerator)?
A: Yes. Better way to learn about pagination. But you are handling dynamic button generation right
Inside the for-loop in pageButtonGenerator, am I calling the onClick handler the right way? I was trying to directly pass the variable "i" but it was always = 6 (the exit value of the loop). I am struggling to understand why the variable i was also 6. I thought the closure would ensure that the value is always correct..
A: Yes you are right its closure issue. The reason why its happening because you are using
for(var i=1; i<=this.state.pageCount; i++){
//var keyword
var in javascript is function scope it means var is sitting in function scope so when buttonClickHandler get the value it after the loop already run i.e its already set 5 time when loop closes its 6 one. That's why its sending 6 as id.
Easiest solution is use : let for(let i=1; i<=this.state.pageCount; i++){
How should I go about what to store in state and what should be derived and not stored?
Generally good rule of thumb is error,loading and data state. Based on its combination you can show result. Critical data like pagecount, current data should be stored in your state without that you will not get what will be next and previous state

React 1000 checkboxes - clicking/re-render takes several seconds

So I am trying to learn React, and have a quite simple application I am trying to build.
So I have a backend API returning a list of say 1000 items, each item has a name and a code.
I want to put out all the names of this list into check boxes.
And then do X with all selected items - say print the name, with a selectable font to a pdf document.
With this I also want some easy features like "select all" and "deselect all".
So since I am trying to learn react I am investigating a few different options how to do this.
None seems good.
So I tried making a child component for each checkbox, felt clean and "react".
This seems to be really bad performance, like take 2-3 seconds for each onChange callback so I skipped that.
I tried making a Set in the class with excluded ones. This too seems to be really slow, and a bad solution since things like "select all" and "deselect all" will be really ugly to implement. Like looping through the original list and adding all of them to the excluded set.
Another solution I didn't get working is modifying the original data array. Like making the data model include a checked boolean to get a default value, and then modify that. But then the data needs to be a map instead of an array. But I have a feeling this solution will be really slow too whenever clicking the checkbox. I dont quite understand why it is so slow to just do a simple checkbox.
So please tell me how to approach this problem in react.
A few direction questions:
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
async componentDidMount() {
const url = "randombackendapi";
const response = await fetch(url);
const data = await response.json();
this.setState({ data: data.data, loading: false });
}
Why is this so extremely slow? (Like take 3 seconds each click and give me a [Violation] 'click' handler took 3547ms) warning. And my version of each item being a sub function with callback being equally slow. How can I make this faster? - Edit this is the only question that remains.
{this.state.data.map((item, key) => (
<FormControlLabel
key={item.code}
label={item.name}
control={
<Checkbox
onChange={this.handleChange.bind(this, item.code)}
checked={!this.state.excludedSets.has(item.code)}
code={item.code}
/>
}
/>
))}
handleChange = (code, event) => {
this.setState({
excludedSets: event.target.checked
? this.state.excludedSets.delete(code)
: this.state.excludedSets.add(code)
});
};
I guess I don't understand how to design my react components in a good way.
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
Once you have the array you can use a map to add a checked key, which will just make the process much easier by utilizing map on the main array to check and an easier implementation for the select-deselect all feature
let data = [{code: 1},{code: 2},{code: 3}]
let modifiedData = data.map(item => {
return {...item, checked: false}
})
//modifiedData = [ { code: 1, checked: false }, { code: 2, checked: false }, { code: 3, checked: false } ]
I would recommend to save the modified data inside the state instead of the data you fetched since you can always modify that array to send it back to the api in the desired format
now that you have the modified array with the new checked key you can use map to select and deselect like so
const handleChange = (code) => {
modifiedData = modifiedData.map(item => item.code === code ? {...item, checked: !item.checked}: item)
}
And as of the deselect all | select all you can use another map method to do this like so
const selectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: true}})
}
and vice-versa
const deselectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: false}})
}
It's a common rendering issue React will have, you can use virtualize technique to reduce the amount of DOM elements to boost the re-rendering time.
There're several packages you can choose, like react-virtuoso, react-window etc.
The main concept is to only render the elements inside your viewport and display others when you're scrolling.
So I was unable to get the React checkbox component performant (each click taking 2-3 seconds), so I decided to just go with html checkboxes and pure javascript selectors for my needs, works great.

Categories

Resources