This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 16 days ago.
I make an ecommerce site
I am trying to display the page with the details of a product:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setLoading(false);
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">
Price: {product.price}
</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
The values are not displayed so I put some console.log
When I put a console.log in the "try" product returns an empty object, and when I put the console.log after the useffect the product returns an object with my values
You need to set the "isLoading" state in false whene the "product" state when the state has finished setting. In order to do that you need to add 1 more useEffect to listen for changes in the product state, for that you must add the "product" state as a dependency of the useEffect.
Try the next code:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
useEffect(() => {
setLoading(false);
}, [product]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">Price: {product.price}</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
useState hook does not update the state value instantly. There are a lot of things happening when you change the state, like batching state updates, recomputing DOM manipulations, re-rendering components, etc.
If you want to know what is going to be set as your state, log the argument that you are passing to the setProduct function. You may also use React dev tools.
Related
I am having a problem while getting data from supabase .
Could any one help me
`
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const { data, error} = await supabase.from("Employees").select("*")
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
`
I tried using map, other function, and looked it up yet nothing works
The problem is how you are fetching the data. Try fetching your data inside an useEffect hook:
import Link from "next/link";
import { supabase } from "../../supabase";
import { useState, useEffect } from "react";
function Index() {
// const { data, error } = await supabase.from("Employees").select("*")
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
let cancelFetch = false; // to avoid race conditions on React18
supabase
.from("Employees")
.select("*")
.then((res) => {
if (!cancelFetch) {
setData(res.data);
setError(res.error);
}
});
return () => {
cancelFetch = true;
};
}, []);
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return <h1>{index.name}</h1>;
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
);
}
export default Index;
More info on fetching and useEffect here: https://beta.reactjs.org/apis/react/useEffect#fetching-data-with-effects
Your source code is invalid. React components should always be a function (or class) that returns a react object. it does not accept a promise that returns a react object.
You will probably want to use react's useEffect to solve this problem:
import { useState, useEffect } from "react";
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
supabase.from("Employees").select("*")
.then(data => setData(data))
.catch(err => setError(err))
}, [])
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
Your component cannot be async, because it returns a Promise and React doesn't like that.
There is a cool function on Next.js that allows you to fetch data asynchronously, try that:
function Index({ data }) {
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
export async function getServerSideProps() {
const { data, error} = await supabase.from("Employees").select("*")
return {
props: {
data: data
}
}
}
More here: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
Based on the way you are fetching data, I believe you are using next13 and you are in app directory. When you rendered jsx
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
index refers to each element inside the data array. Most likely index.name is an object. that is why it is throwing that error.
console.log("index name",index.name)
If you are using async functional component, you should be using Suspense api. Create a separeate component maybe async Users, fetch the data inside this component, and when you want to display the users inside the Index
import {Suspense} from "react"
function Index(){
return (
<>
....
<Suspense fallback={ <h1>Users are loading...</h1>} >
<Users/>
</Suspense>
....
</>
)
}
You only use async component inside app folder and server component.
Hello I am making an application to practice React, my notes app has a pagination which works perfectly, the problem is in the search engine, which only looks for notes from the page I'm on, for example, if I'm on page 2 and I look for a note on page 2, it shows it, however if the note is on a different page, it doesn't show it, it doesn't find it.
I know where the problem is but I'm not sure how to solve it, I'm a bit new to React and I was asking for your help.
I was able to do my pagination with the package react-paginate here is the documentation https://www.npmjs.com/package/react-paginate
My code:
Component App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination
data={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
searchText={searchText}
/>
</div>
</div>
)
}
export default App;
Component Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
import NoteList from './NoteList';
import { MdSkipPrevious, MdSkipNext } from 'react-icons/md';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 9;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<div className="pagination-wrapper">
<ReactPaginate
breakLabel="..."
nextLabel={<MdSkipNext
className='icons'
/>}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<MdSkipPrevious
className='icons'
/>}
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</div>
</>
);
}
export default Pagination;
Component NoteList.js
import React from 'react'
import Note from './Note'
import '../styles/NoteList.css'
import AddNote from './AddNote'
const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => {
return (
<>
<div className="add-notes-wrapper">
<AddNote
handleAddNote={handleAddNote}
/>
</div>
<div className='notes-list'>
{notes.map((note =>
<Note
key={note.id}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</>
)
}
export default NoteList;
Component Search.js
//import React, { useState } from 'react'
import {MdSearch, MdAdd} from 'react-icons/md'
import '../styles/Search.css'
const Search = ({ handleSearchNote, setShowNote }) => {
const handleShowAddNote = () => {
if (setShowNote){
let addNote = document.querySelector('.new');
addNote.classList.add('wobble-horizontal-top')
addNote.style.display='flex';
document.querySelector('.notes-list').style.display='none';
document.querySelector('.pagination').style.display='none';
}
}
return (
<div className='search'>
<div className="input-wrapper">
<MdSearch
className='icon search-icon'
/>
<input
type="text"
placeholder='What note are you looking for?'
onChange={(event) => handleSearchNote(event.target.value) }
/>
</div>
<div className="btn-wrapper-search">
<button
className='btn-addNote'
onClick={handleShowAddNote}>
Nueva Nota
</button>
<MdAdd
className='icon add-icon'
/>
</div>
</div>
)
}
export default Search
The problem is in the component Pagination.js because I'm filtering the notes on each page with the currentItems variable, if I did it with the data variable it would work, but then it would show all the notes, and I don't want that, I currently want to show 9 notes per page.
greetings and thanks in advance.
Edit:
#Newbie I'm doing what you said, but I don't know if you mean this, in my Pagination.js component I did:
useEffect(() => {
const filterNotes=data.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)
setItemOffset(0);
}, [data, searchText])
It doesn't work, do I have to pass a prop to my components additionally?
greetings.
As I suggested to you, search all the notes with searchText in your App.js and pass the results into the Pagination component and it will solve your problem.
Codesandbox: https://codesandbox.io/s/youthful-thompson-xugs0c
Edit
All changes are as per what we talked about in the email.
Codesandbox: https://codesandbox.io/s/green-fast-3k76wx
Search and pagination do not play well together, one of the common solutions is to jump to page 1 each time the filter term changes.
So use an useEffect on searchText to filter data and reset itemOffset to 0, then redo pagination as if the data changed.
The user will jump to page 1 at each keystroke of the search, then he can navigate pages (if there are more than one). This will lead to a less confusing UX.
I made a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on the run button(inside Editor.js), it sets the query to the App component using the setter function, and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call the useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
Any way to get the runtime without calling useData() on every change?
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;
import { useState , useEffect} from "react";
import 'semantic-ui-css/semantic.min.css'
import { Header, Button , Container, Image} from 'semantic-ui-react'
import dotalLogo from './dota2.png'
import React from "react";
import Loading from './loading'
const dataUrl = 'https://api.opendota.com/api/heroStats'
function App() {
const [loading, setLoading] = useState(false)
const [data, setData] = useState([])
const [index, setIndex] = useState(0)
const fecthApi = async () => {
setLoading(true)
try {
const fetched = await fetch(dataUrl)
const parsed = await fetched.json()
setData(parsed)
setLoading(false)
} catch (error) {
console.log('error')
setLoading(false)
}
}
useEffect(()=>fecthApi(),[])
if (loading){
return <Loading />
}
function nextBtn(e) {
e.preventDefault()
setIndex((prev)=>prev+1)
}
function backBtn(e) {
e.preventDefault()
setIndex((prev)=>prev-1)
}
return (
<>
<main id="main-content">
<Container>
<Header className='headings' as='h1' size='medium' style={{fontSize: 40}}>
<img className="dota-logo" src={dotalLogo} alt="" />
<Header.Content className="dota-header">Dota 2</Header.Content>
</Header>
<br />
<Container className='middle-layer'>
<Button
onClick={(e)=> nextBtn(e)}
className='change-btn-one'
content='Back'
icon='arrow left'
labelPosition='left' />
<Image
className='dota-img'
src={"https://api.opendota.com" + data[index].img}
rounded
alt='err'
bordered
centered/>
<Button
onClick={(e)=> backBtn(e)}
className='change-btn-two'
content=' Next '
icon='arrow right'
labelPosition='right' />
</Container>
</Container>
<Container>
<p>{data[index].localized_name}</p>
</Container>
<div className="contain"></div>
</main>
</>
);
}
export default App;
I get an error after compiling it but I have defined it and fetch the data using async await but get a
TypeError: data[index] is undefined
I have stuck for several hours and still come out with no solution. Furthermore, I have tried to destructed it but still get the same error. Using data.map() works, but I only want to display one hero data at a time, and using map would load 120 hero data.
I understand this is kinda a dumb question, but I just can't figure it out :)
data[index] is undefined before fetching ended.
So, data[index].localized_name will gives you error.
you can write it like this.
data[index] && data[index].localized_name
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} />