UI does not render after state is changed - javascript

I have an app (project in Udacity) in React which display books on my shelves according to categories: Currently Reading, Want to Read and Read. Every time I change the category say from Want to Read to Currently Reading the book will move to the right category and in this case it would be Currently Reading. My code works on this one with no problem. However, you can also search from the vast library of books wherein you could move to your shelf, by default the category is None, although you could include the existing books in your shelf as part of being search (aside from the main library of books). Now, my problem is this, if I move from None to Want To Read category for example my UI does not change after I click the back button that brought me back to the main page (i.e. App.js). When I do however, change of category in the main, I have no problem. Also my function for updating the Book Shelf in App.js when called does not show any error in the console.
I have the following components:
App
|
|--BooksSearch
|--BooksList
| |--BookShelf
|--BooksSearchPage
|--BookShelf
The BooksList and BooksSearch displays the books and the search button respectively in the main page (i.e. App.js). The BooksSearchPage allows user to search books from the library to move into the shelves. The BookShelf displays the list of books whether they are in the shelves or in the library.
This is my App.js
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
toCamelShelf(Shelf) {
if (Shelf==="currentlyreading") return "currentlyReading"
if (Shelf==="wanttoread") return "wantToRead"
return Shelf
}
updateBookShelf = (mybook, shelf) => {
shelf=this.toCamelShelf(shelf)
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
render() {
return (
<div className="app">
{this.state.showSearchPage ? (
<Route path='/search' render={({history})=>(
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
/>
)} />
) : (
<Route exact path='/' render={()=>(
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
</div>
)} />
)}
</div>
)
}
}
export default App
And since the code is too long, I included my repo in Github. I am very new to ReactJS and have been debugging this problem for the last 3 days but to no avail.

I'm having a hard time understanding the app enough to know why exactly, but it sounds like its a state issue.
If you navigate away and come back, or click something and it doesn't update properly, the state isn't being updated at that moment (that event) or the state wasn't saved correctly right before that event.
As soon as you reproduce the problem event, ask yourself "what was the state right before I did this?" and "why is the state how it is now?"
Did you forget to update the state?
Is it getting the wrong state from somewhere?
Did you call this.setState({ something })?
Did you overwrite the state instead of adding to it?
Is there a missing state update?
On both pages, right before and right after, add in the render method: console.log(this.state) and if needed, console.log(this.props). I think you will see the problem if you look there. The question is how exactly did it get like that? Re-visit all your state updates.
If you navigate away and come back, where does it get that state from? Why is that data in there?
Remember, React is a state machine. State is an object that has a snapshot of data every time you look at it. It's like looking at a piece of paper with all your data on it. If you leave the room and come back and the data isn't there, what updated your state and made it go away? or why didn't it get added to your state? That mechanism there is causing your problem.
I see a few spots in your code to focus on:
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
and
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
and
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
also right up here:
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
One of them is acting too strongly or one of them isn't updating at the right time, or data is getting overwritten, I suspect.
The console.log() should be most helpful. If your data is missing. Make it show up there at that time and the problem will go away :) (P.S. that setState on componentDidMount looks a little suspect).

Related

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 Router: Component is not rendering but the URL is updated

This kind of "component not rendering in React Router" type of questions seem to be a very frequently asked question. I have looked through everything but I could not find a solution for my problem.
Here is how my code look like:
render(){
return(
<div>
<SearchBar searchBody={this.state.body}/>
<Route path = "/ranked/" component ={Ranked}></Route>
</div>
);
}
Above, the component Ranked is created which, depending on the subpath renders different things.
(For example, localhost:3000/ranked/NBA)
function SearchDropDown(props){
return(
<div className = "searchDropDownItem">
<Link to={"/ranked/"+props.item.url}>{props.item.name}</Link>
</div>
)
}
Above is a different component with the Link tag, which, depending on the url, links to different subpath of /ranked/.
The problem is that let say I am on localhost:3000/ranked/NBA.
If I get redirected to localhost:3000/ranked/WNBA through the linked tag, the url is updated correctly but the component is refreshed to itself.
From the solutions from previous related posts, I have tried
<Route exact path = "/ranked" ...
But it didn't work.
What could be the problem here? How do I solve it?
I would recommend your route look something like this instead /route/:org if you expect to receive props at the end of that specified route. Then inside your Ranked component you would use this.props.match.params.org to get the organization you want ie. (NBA, WNBA). After you have received these props in your Ranked component then you can render what ever you need for that specified organization. Hopefully this makes some sense.

Saving state of a Switch component in a stack navigator

I am new to programming. I'm setting up some boolean switches to allow the user to select preferences. The code I have got so far seems to work as had many issues with state being stuck or not toggling at all. Now my issue is that when I back out of the screen I'm working on and go back in, it always goes back to "true" state, so switches are always on when I go onto the page.
Is there something wrong with my code that the state doesn't persist, or should I move the state to redux store? If so how would I do this?
Multiple web articles and some youtube tutorials, they all seem to work on a barebones single page "Hello World" app. Can't find any real world examples to work with.
class SettingsMarketingPreferences extends Component {
constructor(props) {
super(props);
this.state = {
switchone: true,
switchtwo: true,
switchthree: true
};
}
<Switch
onValueChange={value => this.setState({ switchone: value })}
value={this.state.switchone}/>
Ideally it would save the state of the switches, there are three I just decided to share how one was built to avoid repetition. Long term goal is to attach it to a ID token so we would be able to see who has opted in or out for our marketing information.
<ToggleSwitch
isOn={this.state.toggle1}
onColor="green"
offColor="gray"
size='medium'
onToggle={()=>this.togglechange1()}
/>

Resetting redux state without breaking react components

Been working with redux in a complex react application.
I put some data in redux state and use this data to render a component.
Then, you want to change the page and I do need to reset some fields in the redux state. Those fields were used to render the previous component.
So, if I reset the state before going to the next page, the previous page rerenders and throws errors because data is missing (because of the reset). But I can't reset in the next page, because that page is reached in many flows so it can be difficult to manage when to reset and when not.
How problems like this are managed in react applications?
All the example are simplified to show the problem. in the actual application, there are many fields to reset.
so, page 1
redux state
{field: someValue}
component uses the field value
function myComponent(props) => {
return (props.field.someMappingOperation());
}
Now, when going to page 2, field should be null, so we reset it
redux state
{field: null}
the component above rerenders, throwing an error because of
props.field.someMappingOperation()
and field is null.
What can be done is to load the next page, so this component is not in the page and then reset the state. yet, this becomes very hard to manage, because you the are in page B (suppose clients list with the list saved in redux) and you want to see the details of a client you go to page C, when you press back you don't want to reset again the state. So you add conditions on the reset. But because there are many flows in the application, that page can be reached in many ways. Have conditions for each way is not the best solution I suppose.
Edit:
I would like to add that state reset was not required initially and components aren't designed for that. As the application grew and became enough complex it became necessary. I'm looking for a solution that does not require me to add props value checking in every and each component, like
{this.props.field && <ComponentThatUsesField />}
This is really a lot of work and it's bug-prone as I may miss some fields.
As the application has a lot of pages and manages a lot of different data, the store results to be big enough to not want to have clones of it.
You have to design your components in a more resilient way so they don't crash if their inputs are empty.
For example:
render() {
return (
{ this.props.field && <ComponentUsingField field={this.props.field} />}
)
}
That's a dummy example, but you get the point.
you can change your code like below:
function myComponent(props) => {
const {fields} = props;
return (fields && fields.someMappingOperation());
}
This will not throw any error and is a safe check
You don't need to use conditions or reset your state, your reducer can update it and serve it to your component. You can call an action in componentDidMount() and override the previous values so they will be available in your component. I assume this should be implied for each of your component since you are using a different field values for each.

React: update component's props from outside of render method without using state

Here is what I'm trying to achieve. I have two React components Product and ProductInfoPanel, shown inside a ProductList component. Product displays selected information about a product, such as product name, and price. When a product is clicked, more details will be shown in the ProductInfoPanel. So I need to pass wah twas clicked to the ProductInfoPanel.
Here is how I currently wire them up together. Each Product gets a click handler passed in, which passes back the product object when invoked, then that is passed into the ProductInfoPanel's props. The ProductList uses state to keep track of what was clicked, so when it changes, it triggers the re-rendering of the info panel.
class ProductList extends React.Component {
render() {
return (
<div>
<div className='content'>
<ul>
{ this.props.products.map((product, index) => {
return (
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked.bind(this)}/>
</li>
);
})}
</ul>
</div>
<div className='side-panel'>
<ProductInfoPanel product={this.state.selectedProduct} />
</div>
</div>
);
}
onProductClicked(clickedProduct) {
// Use the product object that was clicked, and updates the state.
// This updates the info panel content.
this.setState({ selectedProduct: clickedProduct });
}
}
Here is roughly how the two components are constructed.
class Product extends React.Component {
render() {
// Even though it needs only name and price, it gets the whole product
// object passed in so that it can pass it to the info panel in the
// click handler.
return (
<div onClick={this.onClicked.bind(this)}>
<span>{this.props.product.name}</span>
<span>{this.props.product.price}</span>
</div>
);
}
onClicked(e) {
this.props.clickHandler(this.props.product);
}
}
class ProductInfoPanel extends React.Component {
render() {
// Info panel displays more information about a product.
return (
<ul>
<li>{this.props.product.name}</li>
<li>{this.props.product.price}</li>
<li>{this.props.product.description}</li>
<li>{this.props.product.rating}</li>
<li>{this.props.product.review}</li>
</ul>
);
}
}
This is the best I could come up with, but using state to keep track of what product was clicked still sounds wrong to me. I mean, it's not really a state of a component, is it?
If I could update props of a referenced React component from outside of the render method, then I'd try to pass a reference to a ProductInfoPanel to each Product, so they could do update it in their click handler.
Is there a way to achieve what I want and avoid using state to keep track of what was clicked?
You could use a flux-like library like redux, or an alternative like mobx to remove state management from your component, but my personal feeling is to keep it as simple as possible until you really feel like there will be significant benefit in adding another layer of abstraction into your project.
I used to start off projects using redux by default but then one time I kicked myself as it turned out that the added complexity of introducing a redux implementation turned out to be overkill for what was actually a fairly small and simple project. I don't know if there is a hard line to know when you should shy away from using standard state and introduce another library to manage it for you, but I have learned that it's probably safest to do it the easiest and simplest way first until you genuinely feel there is actual benefit in bring in another dependency.
A few bits of advice on your current code...
You are binding your functions in the properties like so:
<Product product={product} clickHandler={this.onProductClicked.bind(this)}/>
When you call function bind it actually returns a new function instance, therefore React's reconciler will see it as a new prop coming into your component and will therefore always re-render the subcomponent tree. Something to be aware of. As an alternative approach you can do early binding in your constructor like so:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.onProductClicked = this.onProductClicked.bind(this);
}
render() {
...
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
...
}
}
Additionally, where you are providing index as they unique key prop above - you should consider using a unique identifier from your product model (if it's available). That way if you add or remove items from the list React will have more information to know whether or not it should re-render all of the Product component instances.
For example:
render() {
...
{
this.props.products.map((product) =>
<li key={product.id}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
)
}
...
}
Read more about these concepts here:
https://facebook.github.io/react/docs/advanced-performance.html
I think it's fine. If there were more components that responded to changes in SelectedProduct, then the value of having the parent component control the state would be more apparent. In your case, it might not seem necessary, since only a single component changes.
However, if your Product also responded by highlighting the SelectedProduct, and a RecentlyViewedProducts list responded in some way to the SelectedProduct, then it would become evident that the SelectedProduct isn't the state of the ProductInfoPanel, but state of a higher level part of the application that it's an observer of.

Categories

Resources