In the Case One:
Im doing a simple server-side pagination in rails and using react as front-end and redux as my state management. I have done all the things and the last thing remaining is to just pass the new generated url and fetch the new data. This data will be fetched in a another component which will generate and products.
as Im using redux in my case, how am I able to pass this data to my data fetch action ?
In the Case Two:
I have tried passing a parameter named url and dispatching the fetch action again with the url data i give to it. but the return is that the dispatch is not a function. Am I even able to rerun actions in action.jsx ?
action.jsx
export const handlePage = (e, { activePage }) => {
let pageNum = activePage
let pageString = pageNum.toString();
let url = "/api/v1/products/index/?page=" + pageString; ------> Use This ...
}
export const fetchProducts = (url) => { ------> In Here
return (dispatch) => {
console.log(url);
dispatch(fetchProductsRequest());
axios
.get(url)
.then((response) => {
// response.data is the products
const products = response.data.products;
dispatch(fetchProductsSuccess(products));
})
.catch((error) => {
// error.message is the error message
dispatch(fetchProductsFailure(error.message));
});
};
};
export class Paginator extends React.Component {
state = {
page: [],
pages: [],
};
componentDidMount() {
axios
.get("/api/v1/products/index", { withCredentials: true })
.then((response) => {
this.setState({
page: response.data.page,
pages: response.data.pages,
});
})
.catch((error) => {
console.log("Check Login Error", error);
});
}
render() {
return (
<div>
<Pagination count={this.state.pages} page={this.state.page} onChange={handlePage} />
</div>
);
}
}
Product.jsx
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import "../../style/frequentlyasked.scss";
import ItemOne from "../../files/Item-One.png";
// Redux
import { connect } from "react-redux";
import { loadCurrentItem, addToCart, fetchProducts } from "./action";
const Product = ({
mapProducts,
fetchProducts,
product,
addToCart,
loadCurrentItem,
}) => {
useEffect(() => {
fetchProducts(); -----> Using it Here !
}, []);
return (
<div className="card-deck d-flex justify-content-center">
{mapProducts.map((product) => (
<div className="card item-card" key={product.id} product={product}>
{/* Admin Card */}
{/* Header Image */}
<img className="card-img-top" src={ItemOne} alt="Card image cap" />
{/* Card Body */}
<div className="card-body">
<h4 className="card-title">{product.title}</h4>
<h5 className="card-title">$ {product.price}</h5>
<p className="card-text">{product.description}</p>
<button
className="btn btn-primary"
onClick={() => addToCart(product.id)}
>
+ Add To Cart
</button>
<a href="#" className="btn btn-danger">
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-heart-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314z"
/>
</svg>
</a>
</div>
{/* Card Footer */}
<div className="card-footer">
<small className="text-muted">Last updated 3 mins ago</small>
</div>
</div>
))}
</div>
);
};
const mapStateToProps = (state) => {
return {
mapProducts: state.shop.products,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
loadCurrentItem: (item) => dispatch(loadCurrentItem(item)),
fetchProducts: () => dispatch(fetchProducts()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Product);
In your case:
useEffect(() => {
fetchProducts(page); -----> Using it Here !
}, [page]);
//and
fetchProducts: (page) => dispatch(fetchProducts(page)),
Note that your mapDispatchToProps could (and should) also be written in the map object notation:
const mapDispatchToProps = {
addToCart,
loadCurrentItem,
fetchProducts
}
Also note that the official recommendation is to use the react-redux hooks instead of connect and mapDispatchToProps.
So skip the whole connect stuff and in your component:
const Product = ({
product,
}) => {
const mapProducts = useSelector(state => state.shop.products)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProducts(page));
}, []);
Also, if you are still using connect, you have probable been following outdated tutorials. Redux has changed a lot over the last few years. Look into modern redux and the up-to-date official redux tutorials
By the way: there is a new API on the way for the official redux toolkit which can take care of all that data fetching for you. You can already try it out, at the moment as an extra package: https://rtk-query-docs.netlify.app/
Related
I'm learning react for the first time, I have an app where it fetches some data from a public API. I currently have it show 10 cards with random items from the API, and I have added a button to fetch a random item from the API and add it to the array, I managed to get the new item added to the array using push() but it does not show in the app itself. How can I make it that the new item is shown in the app as well?
Here is my code
Home.js
import { useState, useEffect} from "react";
import Card from './Card';
const Home = () => {
const [animals, setAnimals] = useState([]);
const handleDelete = (id) => {
const newAnimals = animals.filter(animal => animal.id !== id);
setAnimals(newAnimals);
}
useEffect(() => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/10')
.then(res => {return res.json()})
.then(data => {
setAnimals(data);
});
}, []);
const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
animals.push(data);
console.log(animals);
//what to do after this
})
}
return (
<div className="home">
<h2>Animals</h2>
<button onClick={handleAddAnimal}>Add Animal</button>
<Card animals={animals} handleDelete={handleDelete}/>
</div>
);
}
export default Home;
Card.js
const Card = ({animals, handleDelete}) => {
// const animals = props.animals;
return (
<div className="col-3">
{animals.map((animal) => (
<div className="card" key={animal.id}>
<img
src={animal.image_link}
alt={animal.latin_name}
className="card-img-top"
/>
<div className="card-body">
<h3 className="card-title">{animal.name}</h3>
<p>Habitat: {animal.habitat}</p>
<button onClick={() => handleDelete(animal.id)}>Delete Animal</button>
</div>
</div>
))}
</div>
);
}
export default Card;
App.js
import Navbar from './navbar';
import Home from './Home';
function App() {
return (
<section id="app">
<div className="container">
<Navbar />
<div className="row">
<Home />
</div>
</div>
</section>
);
}
export default App;
Screenshot of what I see now
screenshot
(I was also wondering how to fix the items going down instead of side by side but wanted to fix the add button first)
Let me know if there's anything else I should add, any help is appreciated, thank you!
Rather using array.push() method. You try using
setTheArray([...theArray, newElement]); e.g in your case it will be setAnimals([...animals,data]) in your onClick event.
Let me know doest it solve your issue or not.
const handleAddAnimal = () => {
fetch('https://zoo-animal-api.herokuapp.com/animals/rand/')
.then(res => {return res.json()})
.then(data => {
setAnimals([...animals,data])
console.log(animals);
//what to do after this
})
}
I'm trying to show data from an API call. The structure of the application looks like
MainComponent -> RefreshButton (this will fetch the data)
MainComponent -> ShowData (this will show the data that is being fetched)
MainComponent has a state userData that will store the response that was received from the API. Now the issue is, whenever I'm clicking the button, it is getting into an infinite loop of rendering and calls the API infinite times.
This is what the error shows:
Here is my MainComponent -
import React, { useEffect, useState } from "react";
import RefreshButton from "./RefreshButton";
import ShowData from "./ShowData";
const MainComponent = () => {
const [userData, setUserData] = useState();
useEffect(() => {
console.log(userData);
}, [userData]);
return (
<div>
<p style={{ textAlign: "center" }}>Main Component</p>
<RefreshButton setUserData={setUserData} />
{userData && <ShowData userData={userData} />}
</div>
);
};
export default MainComponent;
Here is my RefreshButton component -
import React from "react";
import axios from "axios";
const RefreshButton = ({ setUserData }) => {
const getData = () => {
axios
.get(`https://jsonplaceholder.typicode.com/todos`)
.then((response) => {
if (response.status === 200) setUserData(response.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="button-container">
<button className="fetch-data-button" onClick={() => getData()}>
Fetch new data
</button>
</div>
);
};
export default RefreshButton;
And here is my ShowData component -
import React from "react";
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info}
</div>
))}
</>
);
};
export default ShowData;
PS - I'm new to React and couldn't find a potential solution on this, there are several tutorials on how to fetch data from API calls and show it, but I wanted to know what I'm doing wrong here. Thanks in advance!
You might have misunderstood with the infinite loop error
It's actually a render error as being shown here:
To fix your render error, simply put an actual string variable in the {}
Because the response was an array of this object, so you can't simply render the whole object but need to pick an actual string variable inside:
[{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}],
Change to something like this:
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info.title} // <-- Put a title here.
</div>
))}
</>
);
};
Remove
useEffect(() => {
console.log(userData);
},[userData])
This will reevaluate component whenever user data changes, which Leeds to call showData infinitely
Hello guys I have a basic component that takes product info and render it. It has a method called onAaddToWishList which takes product id, fetches the data from API and adds it to my wishlist.
const Product = (props) => {
const [loading, setLoading] = useState(false);
const onAddToWishlist = (e, id) => {
e.preventDefault();
if (!loading) {
setLoading(true)
instance.get(`/products/product/${id}`, {
headers: {
'X-Auth-Token': findToken()
}
})
.then(response => {
if (response.data.food) {
alert('Product added')
props.addToWishlist(response.data.food);
console.log(response.data.food)
}
else {
alert('Product not found')
}
setLoading(false);
})
.catch(err => {
console.log(err)
setLoading(false);
alert('Error occured.try again')
})
}
}
return (
<div className="ProductCard">
<div className="ProductCard--Image">
<img src={props.image} alt="iclereu" />
</div>
<div className="ProductCard--Container">
<h1>{props.title}</h1>
<p>{props.description}</p>
<span>₼ {props.price}</span>
<div className="ProductCard--Nav">
<div className="ProductCard--Link">
<Link to="/">View more</Link>
</div>
<div className="ProductCard--Link">
<a
onClick={(e) => onAddToWishlist(e, props.id)}
href="#"
>
{!loading ? "+Wishlist" : "Loading..."}
</a>
</div>
</div>
</div>
</div>
)
}
const mapStateToProps = (state) => {
return {
wishlist: state.wishlist
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToWishlist: (product) => dispatch(wishlistActions.addToWishList(product))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Product)
This is my addToWishList action:
const addToWishList = (product) => {
return dispatch => {
let wishlist = [...store.getState().wishlist];
let exists = wishlist.find(prod => prod.product._id === product._id);
if (!exists) {
wishlist = [...wishlist, {
product: product,
count: 1
}]
}
else {
let index = wishlist.findIndex(prod => prod.product._id === product._id);
wishlist[index].count++;
}
dispatch({ type: "ADD_TO_WISHLIST", wishlist: wishlist })
}
}
props.addToWishlist is function for redux action(it saves data to redux store(wishlist)). Problem is whatever I do inside this action I get the following warning:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
But I don't use useEffect hook. It works actually(it adds a product to wishlist) but the warning is pretty annoying like I do something wrong. Even I console the argument of the redux action it gives the same error. I would be grateful if someone helps me
The reason is that props of your component are changing (wishlist) component is rerendering and in this time setLoading is trying to update the state of the unmounted component. For this example, useEffect is not really useful.
The quickest solution is to keep the loading state in redux store as well and remove const [loading, setLoading] = useState(false); from this component.
Also, it worth mentioning, that it's better to decompose presentation and API interaction. I can recommend you two simple approaches for redux: https://www.npmjs.com/package/redux-api-middleware and https://www.npmjs.com/package/redux-thunk
I've been trying to create a blog like website where user can add new post, save them and edit them later. I'm making this website in reactjs and redux. I've few confusions as to how to edit the post, like how will my website know that the user has clicked on this certain post to edit. To do I've used link from react router with the id at the end of the url but I'm not sure if it's the right way to do. Also, when I open the editor page of an existing post, it should load as it was last saved, i.e both the title input and the textarea should already be loaded with text when a user clicks on an already existing posts from the homepage.
I've created a codesandbox of the website. I'm not sure why all the lines in the switch statements in the reducer file is underlined with red.
this is my home.js file where the snippets will load
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { onLoad, setEdit } from "./actions/posts";
import { connect } from "react-redux";
class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
//Load all the snippets
onLoad();
}
render() {
const { snippets } = this.props;
return (
<div className="container">
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
<h1 className="text-center">Snippets</h1>
</div>
</div>
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
{snippets.map(snippet => {
return (
<div className="card my-3" key={snippet.snippetData.snippetId}>
<div className="card-header">{snippet.title}</div>
<div className="card-body">{snippet.snippetDescription}</div>
<div className="card-footer">
<div className="row">
<button
// onClick={() => this.handleEdit(snippet)}
className="btn btn-primary mx-3"
>
<Link to={`/editor/:${snippet.snippetData.snippetId}`}>
Edit
</Link>
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets,
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ onLoad, setEdit }
)(Home);
editor.js page
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
state = {
title: "",
enteredText: ""
};
componentDidMount() {
//Load the snippet
retrievePost(); // will it load the snippetId too?
}
handleChange = event => {
const { value } = event.target;
this.setState({
enteredText: value
});
};
// Save Snippet
performSave = snippetData => {
const { enteredText, title } = this.state;
savePost(snippetData.snippetId, enteredText, title); //is it the right way to send the parameters to save the post??
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
onChange={title => this.setState({ title })}
/>
<button
className="btn savebtn"
onClick={() => this.handlePost({ ...this.state })}
>
Save Snippet
<i className="fas fa-save" />
</button>
<div
contentEditable={true}
spellCheck="false"
name="enteredText"
placeholder="Enter your text here"
onChange={this.handleChange}
/>
</>
);
}
}
const mapStateToProps = state => ({
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
actions.js file
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post(
"/api/save",
JSON.stringify(snippetData),
config
);
snippetData.snippetId = res.data;
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
const res = await axios.post(
"/api/update",
JSON.stringify(snippetData),
config
);
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// Retrieve post
export const retrievePost = snippetId => async dispatch => {
try {
const res = await axios.post(`/api/snippetdata/${id}`);
dispatch({
type: RETRIEVE_POST,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
//Retrieve all the post
export const onLoad = () => async dispatch => {
try {
const res = await axios.post(`/api/mysnippets/`);
dispatch({
type: HOME_LOADED,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
// edit a post
First, I have fixed some problems for you:
https://codesandbox.io/s/amazing-bird-dd2mb
I did not fix the editor page, cuz I give up, it is meaningless to give you a working code while learning nothing.
I suggest you stop playing react now, you do not have enough experience to use a complex framework.
What problem your code has:
Wrongly import a commonJS module
Misuse combineReducers
Misuse html form element
Misuse js switch
Do not understand redux state correctly
Do not understand reducer fully
Do not have basic debuging skills
...
STOP WRITING CODE THAT YOU DO NOT UNDERSTAND
This project is too complex for a beginner.
I suggest you:
Implement a counter in vanilla js
Implement a todo list in vanilla js
RE-implement the counter with pure react, no redux, no react-router
RE-implement the counter with react + redux
RE-implement the counter with react + redux + thunk
RE-implement the counter with react + redux + saga
Repeat 3-6 but a todo list.
Then try to code a blog.
I am trying to fetch my data inside useEffect but every time i get an empty array when i try to send it as props to another component (Product)
ProducList.js
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { fetchProducts } from "../actions/products";
import { Product } from "./Product";
const ProductList = ({ getProducts, products, loading }) => {
useEffect(() => {
getProducts();
}, []);
return (
<div className="p-4">
<Product products={data} />
</div>
);
};
const mapStateToProps = state => ({
products: state.products,
loading: state.loading
});
const mapDispatchToProps = {
getProducts: fetchProducts
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
and here my Product.js
import React from "react";
export const Product = props => {
const products = props.products.map(product => {
return (
<div className="col-lg-4 mb-4" key={product.Id}>
<div className="card shadow-sm">
<img
className="card-img-top"
src={`/images/${product.Id}.jpg`}
alt={product.name}
/>
<div className="card-body">
<h5 className="card-title">
{product.name}{" "}
<span className="badge badge-warning">${product.price}</span>
</h5>
<a href="#" className="btn btn-secondary mx-auto">
Add to cart
</a>
</div>
</div>
</div>
);
});
return <div className="row">{products}</div>;
};
i need to fetch data and send to product component
It looks like your data is never passed into the ProductList component (there is no reference to a data prop in ProductList).
I believe you are trying to pass the products data into the Products component. Here's what you could do.
const [newProducts, setNewPropducts] = useState([]);
//add this useEffect and keep your other one
useEffect(() => {
//set state for products here
setProducts(products)
}, [products]);
<Product products={newProducts} />