React - changing values in server is not reflected in DOM immediately - javascript

From the client side, I have a modal in my HomeComponent where I can choose an element. Then, what I want is to render that element inside the same HomeComponent (in the productosEnVenta function). The element that I choose in the modal is then POST in the server through a fetch in my ActionCreators, and in the HomeComponent I show the elements that were posted before. My problem is that when I select the element from the modal, the program gets an error because it takes the element that I just selected as undefined, but when I reload the browser, the element is shown as normal. I believe that this has something to do with the life cycle of the component, but I don't know if I have to use a componentDidMount or componentDidUpdate to solve this. I hope somebody can help me.
Here is the code:
function productosEnVenta(postVenta, preparado, productos, restarCant, deleteCero) {
if (preparado.length > 0){
return(
<div>
{preparado.map((receta) => {
const producto = productos.filter((producto) => producto._id === receta.productoId._id)[0]
return(
<div key={receta._id}>
<Card>
<CardImg src={baseUrl + producto.image} /> //the error is shown here. It says it can't read .image of undefined
<CardTitle>{producto.name}</CardTitle>
</Card>
</div>
);
})}
</div>
);
}
else {
return(
<div><h4>No hay productos preparados</h4></div>
);
}
}
class Home extends Component {
render(){
const ModalElegirReceta = ({putIngrediente, productos, inventario}) => {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
function restarIngrediente(ingrediente){
for (var elem of ingrediente){
var restar = elem.gramos;
var enInventario = inventario.filter((inven) => inven.ingrediente === elem.ingrediente)[0];
console.log('en inventario: ', enInventario);
var restante = enInventario.disponible - restar;
putIngrediente(enInventario._id, enInventario.ingrediente, enInventario.costo, restante, enInventario.conversiones);
}
}
return(
<div>
<Button onClick={toggle}>Elegir Receta</Button>
<Modal isOpen={modal} toggle={toggle}>
<ModalHeader toggle={toggle}>Elegir Receta</ModalHeader>
<ModalBody>
<Form>
{productos.map((receta) => {
return(
<div className="col-12 col-md-3" key={receta._id}>
<Card onClick={() => {this.props.postPreparado(receta._id, receta.porciones, receta.precio); restarIngrediente(receta.ingredientes)}}>
<CardImg src={baseUrl + receta.image} />
<CardTitle>{receta.name}</CardTitle>
</Card>
</div>
);
})}
</Form>
</ModalBody>
</Modal>
</div>
);
}
return(
<div className="container">
<div className="row row-content">
<div className="col-12">
{productosEnVenta(this.props.postVenta, this.props.preparado.preparado, this.props.productos, this.props.putRestarPreparado, this.props.deleteCero)}
</div>
<div className="col-12 justify-content-center">
<ModalElegirReceta putIngrediente = {this.props.putIngrediente}
productos = {this.props.productos}
inventario = {this.props.inventario}/>
</div>
</div>
</div>
);
}
};
export default Home;

You can use conditional rendering to guard against errors.
Like with producto.image, if you expect producto might be undefined at some point, you can edit your function to return this instead:
return producto && (
<div key={receta._id}>
<Card>
<CardImg src={baseUrl + producto.image} /> //the error is shown here. It says it can't read .image of undefined
<CardTitle>{producto.name}</CardTitle>
</Card>
</div>
);
There may be more issues with your data flow but this will prevent your app from crashing.

Related

onClick function is not called after I have enabled the button in Reactjs

I have a textarea and a button. The button is disabled by default and when the user starts typing, I enable the button to be clicked. But the problem is that, the onClick function is not called while already disabled = false was set.
I've seen this: button onClick doesn't work when disabled=True is initialized (Reactjs)
Seems to be a good idea, but after I setState with the new value, my component is re-rendering, and I don't really want that.
const refText = useRef(null);
const refBtn = useRef(null);
function handleBtnStatus(e) {
let text = e.target.value;
if(text.replace(/\s/g, "").length > 0) {
refBtn.current.disabled = false;
}
else {
refBtn.current.disabled = true;
}
}
function postThis() {
console.log("You posted! Text:", refText.current.value);
// disable again
refBtn.current.disabled = true;
// delete previous text wrote
refText.current.value = "";
}
return (
<>
{isLogged && (
<div className="container">
<div className="content">
<div className="utool-item-text">
<textarea name="textArea" placeholder="Write something.." ref={refText} onChange={(e) => handleBtnStatus(e)}></textarea>
</div>
<div className="utool-item-post">
<button className="ust-btn-post" ref={refBtn} disabled={true} onClick={postThis}>Da Tweet</button>
</div>
</div>
<div className="posts-section">
<div className="list-posts">
{posts.map((p) => {
return (p.hidden === false ? (
<div className="post" key={p.id}>
<div className="post-text">
<span>{p.text}</span>
</div>
</div>
) : (''))
})}
</div>
</div>
</div>
)}
</>
)
Any help?
Use state instead of refs, re-rendering is ok for your case
Simplified example:
import React, { useState } from 'react';
const SimpleExample = () => {
const [textAreaValue, setTextAreaValue] = useState('');
return (
<>
<button disabled={!textAreaValue} onClick={() => console.log('onClick handler')}>
click me
</button>
<textarea value={textAreaValue} onChange={(e) => setTextAreaValue(e.target.value)} />
</>
);
};
And I would recommend checking this Use state or refs in React.js form components?

React: implementing a router

I tried implementing browser router, but to no success. i'm having trouble with useParams hook, and just the router in general. Looked through multiple posts and i just wasn't able to get it working. I'll post the most barebones code below, hoping someone knows the solution. I removed the traces of the router, since it didn't work.
App.js is currently empty:
const App=()=> {
return (
<Main/>
);
}
Main.jsx is my main element, where components change. There isn't a page change per se, everything is in the main element. values get passed through props into main and written into state, so the useEffect can change visibility of components based on what you chose, first category, then recipe.:
const Main =()=> {
const [showElement, setShowElement] = useState("category");
const [selectedCategory, setSelectedCategory] = useState();
const [selectedRecipe, setSelectedRecipe] = useState();
useEffect(()=> {
if (selectedRecipe) {
setShowElement("recipe")
} else if (selectedCategory) {
setShowElement("recipeSelection")
}
window.scrollTo(0, 0)
}, [selectedCategory][selectedRecipe]);
return (
<>
<Header />
<main className="main">
<div>
<div>
{showElement === "category" &&
<CategoryWindow
passSelectedCategory={setSelectedCategory}
/>
}
</div>
<div>
{showElement === "recipeSelection" &&
<RecipeSelection
value={selectedCategory}
passSelectedRecipe={setSelectedRecipe}
/>
}
</div>
<div>
{showElement === "recipe" &&
<RecipeWindow
value={selectedRecipe}
/>
}
</div>
</div>
</main>
</>
)
}
This is the recipe picker component. For example when i click on curry, i'd like the url to show /food/curry. None od the names are hardcoded, everything comes from a javascript object:
const RecipeSelection =(props)=> {
const recipies = Recipies.filter(x=>x.type === props.value);
return (
<div className="selection-div">
<div className="selection-inner">
{recipies.map(selection =>
<>
<img src={require(`../images/${selection.id}.png`)}
className="selection-single"
key={selection.id}
alt={"picture of " + selection.id}
onClick={()=> props.passSelectedRecipe(selection.id)}
>
</img>
<div className="container-h3"
onClick={()=> props.passSelectedRecipe(selection.id)}
>
<h3 className="selection-h3">{selection.name}</h3>
</div>
</>
)}
</div>
</div>
)
}

Prop is an empty object in React child

I'm trying to add a search bar to a parent component.
All the logic is working fine in the console. With every character that is typed in the search field I get fewer results.
I try to pass it to a child component to render the card(s) result, but I get a blank card: I can not see data passed.
Parent Component <AllAssets>
class AllAssets extends Component {
state = {
cards: [],
searchField: '',
}
async componentDidMount() {
const { data } = await cardService.getAllCards();
if (data.length > 0) this.setState({ cards: data });
}
addToFavorites = (cardId, userId) => {
saveToFavorites(cardId, userId)
toast.error("The asset was added to your favorites.")
}
render() {
const { cards, searchField } = this.state;
const user = getCurrentUser();
const filteredAssets = cards.filter(card => (
card.assetName.toLowerCase().includes(searchField.toLowerCase())));
console.log(filteredAssets);
return (
<div className="container">
<SearchBox placeholder={"Enter asset name..."}
handleChange={(e) => this.setState({ searchField: e.target.value })}
/>
<PageHeader>Assets available for rent</PageHeader>
<div className="row">
<div className="col-12 mt-4">
{cards.length > 0 && <p>you can also add specific assets to your favorites and get back to them later...</p>}
</div>
</div>
<div className="row">
{!!filteredAssets.length ? filteredAssets.map(filteredAsset => <SearchResult addToFavorites={this.addToFavorites} filteredAsset={filteredAsset} user={user} key={filteredAsset._id} />) :
cards.map(card => <CardPublic addToFavorites={this.addToFavorites} card={card} user={user} key={card._id} />)
}
</div>
</div >
);
}
}
export default AllAssets;
Child Component <SearchResult>
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
return (
<div className="col-lg-4 mb-3 d-flex align-items-stretch">
<div className="card ">
<img
className="card-img-top "
src={filteredAsset.assetImage}
width=""
alt={filteredAsset.assetName}
/>
<div className="card-body d-flex flex-column">
<h5 className="card-title">{filteredAsset.assetName}</h5>
<p className="card-text">{filteredAsset.assetDescription}</p>
<p className="card-text border-top pt-2">
<b>Tel: </b>
{filteredAsset.assetPhone}
<br />
<b>Address: </b>
{filteredAsset.assetAddress}
</p>
<p>
<i className="far fa-heart text-danger me-2"></i>
<Link to="#" className="text-danger" onClick={() => addToFavorites(card._id, user._id)}>Add to favorites</Link>
</p>
</div>
</div>
</div>
);
}
export default SearchResult;
When I console.log(filteredAsset) in <SearchResult> I get an empty object. What am I doing wrong?
This line is incorrect:
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
You are passing in positional arguments, not named props. Do this instead:
const SearchResult = ({addToFavorites, filteredAsset, card, user}) => {
In your original code, React attaches all of your props as fields on the first argument. So they would be accessible in the child, but not in the way you're trying to access them. Try logging out the values of each of the arguments in the child, if you're curious to see what happens.
The corrected version passes in a single object with field names that match the names of your props. It's shorthand that's equivalent to:
const SearchResult = (
{
addToFavorites: addToFavorites,
filteredAsset: filteredAsset,
card: card,
user: user,
}
) => {

react-responsive-carousel messed up

I've read the documentation, I don't know why it's working but it's messed up. Here's my code :
function CarouselItem(props) {
const { post } = props
return (
<React.Fragment>
<div>
<img src={`http://localhost:5000/image/${post.foto}`} />
<p className="legend">{post.judul}</p>
</div>
</React.Fragment>
)
}
function NewsItem(props) {
const { posts } = props.post
let content = posts.map(item => <CarouselItem key={item._id} post={item} />)
return (
<div>
<Carousel showThumbs={false}>{content}</Carousel>
</div>
)
}
It turns out like this :
Use this in the first line of your .js file:
import 'react-responsive-carousel/lib/styles/carousel.min.css';

Passing data between components in React

Ultimately I'm trying to pass mapped elements in an array to a child component. I made a WordPress API call to get back posts for a preview page, and now that I'm trying to have that data render in their own pages, I keep getting that the data is undefined. The dynamic links are rendering as expected, but none of the other data is being passed.
Articles.js
// cut for brevity
render() {
let articles = this.state.newsData.map((article, index) => {
if(this.state.requestFailed) return <p>Failed!</p>
if(!this.state.newsData) return <p>Loading...</p>
return(
<div key={index} className="article-container">
<div className="article-preview">
<span className="article-date">{article.date}</span>
<h5>{article.title.rendered}</h5>
<div dangerouslySetInnerHTML={{ __html: article.excerpt.rendered }} />
<Link to={`/news/${article.slug}`}>Read More...</Link>
</div>
<Route path={`/news/:articleSlug`}
render={ props => <Article data={article} {...props} />}
/>
</div>
)
});
return (
<div>
<h3>All Articles from Blog</h3>
{articles}
</div>
)
}
Article.js
import React from 'react';
const Article = ({match, data}) => {
let articleData;
{ console.log(this.data) }
if(data)
articleData = <div>
<h3> {data.title.rendered}</h3>
<div dangerouslySetInnerHTML={{ __html: data.content.rendered }} />
<hr />
</div>
else
articleData = <h2> Sorry. That article doesn't exist. </h2>;
return (
<div>
<div>
{articleData}
</div>
</div>
)
}
export default Article;
How do I get the data from the array into the Article component?
Your problem is with asynchronous requests.
You have a route that will call the render method when the user clicks on a link. At that point in time, javascript has no reference to the article anymore, you need to persist it.
Here's an example of what you are experiencing
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 1);
}
The code above will always log 10
A solution to this problem is using bind.
for (var i = 0; i < 10; i++) {
setTimeout(function(i) { console.log(i); }.bind(null, i), 1);
}
So, in your code, you need to persist the article variable.
You can do that by calling a method that takes the data.
renderArticle(data) {
return props => <Article data={data} {...props} />
}
render() {
let articles = this.state.newsData.map((article, index) => {
if(this.state.requestFailed) return <p>Failed!</p>
if(!this.state.newsData) return <p>Loading...</p>
return(
<div key={index} className="article-container">
<div className="article-preview">
<span className="article-date">{article.date}</span>
<h5>{article.title.rendered}</h5>
<div dangerouslySetInnerHTML={{ __html: article.excerpt.rendered }} />
<Link to={`/news/${article.slug}`}>Read More...</Link>
</div>
<Route path={`/news/:articleSlug`}
render={this.renderArticle(article)}
/>
</div>
)
});
return (
<div>
<h3>All Articles from Blog</h3>
{articles}
</div>
)
}
Hope this points you in the right direction.

Categories

Resources