I am learning about states in React, and below is an exercise to create a to-do list.
I capture the state of each inputted "item" in the to-do list and add them to an "items" array.
Then, each element in the "items" array is rendered using HTML.
I don't understand why I can't just push each item into a normal JavaScript array? After submitting the form, I log the "items" array. However, the "items" array only has 1 element, no matter how many times I submit the form. It seems like previous values are disappearing every time.
I solved the problem by using a React state that is an array. But I still don't understand why I can't use a JS array. Any explanations/resources would be greatly appreciated!
function App() {
const items = [];
//const [items, setItems] = React.useState([]);
const [inputText, setInputText] = React.useState("");
function handleSubmit(event) {
items.push(inputText);
console.log(items);
//setItems((prevValue) => {
// return [...prevValue, inputText];
//});
event.preventDefault();
}
function handleChange(event) {
setInputText(event.target.value);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<form className="form" onSubmit={handleSubmit}>
<input type="text" value={inputText} onChange={handleChange} />
<button type="submit">
<span>Add</span>
</button>
</form>
<div>
<ul>
{items.map((item) => {
return <li>{item}</li>;
})}
</ul>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
You have to define the let items = []; outside the function. Because in react each component reloaded on state update. so it will re-assigned to empty array. Make sure you define it using let not const
You can use the react hook useCallback to prevent items re-rendering every time state changes const items = useCallback([], [])
Related
While trying to get a DOM element's position on render using this code:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
<div ref={callbackRef}> </div>
</ToastBody>
</Toast>
</>
}
I noticed that it always prints a DOMRect object with zero for every property :
If I add state dependence and then state changes causing rerender, the correct position will be printed. Something like this:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
const [show, setShow] = useState(true) // added state
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
{show ? <div ref={callbackRef}> </div> : null} // div inside Toast can be toggled
</ToastBody>
</Toast>
<Button onClick={() => setShow(!show)} >toggle </Button> // added toggle button
</>
}
After double click on the button:
What confuses me the most is the fact that if I replace this Toast imported from Reactstrap with pure html with bootstrap classes the problem disappears. And this is exactly what React renders because I copied it from source code in the browser:
<div class="toast fade show" role="alert">
<div class="toast-header">
<strong class="me-auto"></strong>
</div>
<div class="toast-body">
<div ref={callbackRef}> </div>
</div>
</div>
And it seems to be a problem that exists just for this Toast component. For Reactrstrap's Card for example it is not the case. So how can using a component which at the end of the day gets rendered into a certain html code be different from using the same html code and why this particular component turns out to be a special case regarding obtaining its DOMRect?
I didn't get data for the first time.
Here is my screen shot when I search for the first time it return undefined and when I search for second time it return proper data.
How to I fix this problem. And please also explain what does it happens. I search this behavior from 2 days but I didn't find any solution even from stack overflow.
Here is my code.
import logo from './logo.svg';
import './App.css';
import Navbar from './components/Navbar';
import { useEffect, useMemo, useState } from 'react'
function App() {
const [searchWord, setSearchWord] = useState('');
const [responseWord, setResponseWord] = useState();
const [isLoad, setIsLoad] = useState(false)
const [urlLink, setUrlLink] = useState('')
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
console.log(responseWord)
console.log(isLoad)
}
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div className="column bg-success text-light text-center col-3" style={{ height: "100vh" }}>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{
isLoad &&
<>
<h3 className='word'>{responseWord.word}</h3>
</>
}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={(e) => e.preventDefault()}>
<input className="form-control mr-sm-2" placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button className="btn btn-outline-success my-2 my-sm-0" type="submit" onClick={() => fetchWord(searchWord)} >Search</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
When responseWord was printed the first time, responseWord's value was not updated to new value. Because setState operates asynchronously.
Use useEffect hook instead.
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
}
useEffect(() => {
console.log(responseWord);
}, [responseWord]);
can you try this one please i think will it help you out
import React, { useEffect, useMemo, useState } from "react";
const App=()=> {
const [searchWord, setSearchWord] = useState("");
const [responseWord, setResponseWord] = useState([]);
const [isLoad, setIsLoad] = useState(false);
const [urlLink, setUrlLink] = useState("");
const fetchWord = async (word) => {
console.log(word);
try {
setIsLoad(false);
const res = await fetch(
`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`
);
const data = await res.json();
setIsLoad(true);
setResponseWord(data);
} catch (er) {
setIsLoad(false);
}
};
const HandleSubmit = (e) => {
e.preventDefault();
fetchWord(searchWord);
};
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div
className="column bg-success text-light text-center col-3"
style={{ height: "100vh" }}
>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{isLoad && responseWord.length !== 0 && (
<>
{/* <h3 className="word">{responseWord.word}</h3> */}
{responseWord.map((eg, i) => (
<h3 key={i || eg}>{eg.word}</h3>
))}
</>
)}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={HandleSubmit}>
<input
className="form-control mr-sm-2"
placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button
className="btn btn-outline-success my-2 my-sm-0"
type="submit"
>
Search
</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
don't console inside the Asynce function cause async function will await until responce came so your results will be previous state
and assign your useState intially with empty array that will work properly if in case empty data
Muhammad, you're off to a great start here. First, let's take a look at your code as it is at the moment. Then, I'll make a couple of recommendations on how to refactor your code.
Congrats! You're actually getting data the first time you click the button and trigger fetchWord function.
You're just calling console.log(responseWord) and console.log(isLoad) too early. You're trying to log responseWord and isLoad right after updating their state within the same function. This happens because "calls to setState are asynchronous inside event handlers" and changes to state variables do NOT reflect the new value immediately after calling setState.
"When state changes, the component responds by re-rendering." And it is in the new re-render that the new state value will be reflected.
Why doesn’t React update state synchronously?
According to React documentation, React intentionally “waits” until all components call setState() in their event handlers before starting to re-render. This boosts performance by avoiding unnecessary re-renders.
When you call setResponseWord("new value") and setIsLoad("new value"), and then try to log the new state values to the console before React re-denders your component, you get false and undefined.
Try console.log(data) instead of console.log(responseWord).
Since you have access to const data = await res.json(); inside your function and before the component re-render happens, you should be able to see your data right away.
The images below ilustrate this example (focus on line 16):
Next, recommendations:
1 . It is recommended to make your AJAX call to an API using useEffect Hook.
This way, you can add the serachWord to the dependency array, and useEffect will execute every time the value of seachWord changes.
In your case, you make your fetch call on button click, but if, for example, you created a web app in which you needed the data to be populated right away without the user having to click a button, useEffect Hook will shine at its best because useEffect automatically runs the side-effect right after initial rendering, and on later renderings only if the value of the variables you passed in the dependency array change.
See the image below from the React documentation to get an idea of how you could refactor your code:
Another recommendation is to get rid of the onClick property in the button, and just let the handleSubmit function call fetchWord (see lines 38 and 21-24).
This information should help you move your app forward. And you're doing great. I see that you're successfully updating state variables, using async/await, making AJAX calls using fetch() and learning React.
Please take a look at the reference list below:
https://reactjs.org/docs/faq-state.html
https://reactjs.org/docs/faq-ajax.html
https://reactjs.org/docs/forms.html
I am working on e-commerce project (MERN).So i got Molla e-commerce React template for better UX/UI.
In my root function am getting all products and store them using redux like this :
const updateStore = () => {
store.dispatch( getAllProducts() );
}
Everything was working fine until i found out that if i try to access product page for the first time (with empty localstorage as in incognito mode) i get nothing and the product object was undefined , if i refresh the page then it works fine.
The problem is when i try to access the products page with empty redux store , it doesn't wait or rerender when the data are stored.
I tryed to use useEffect() to wait for product change to rerender but it's not working .
This is my product page code :
function SingleProduct( props ) {
let productLink= props.match.params.link;
const {product} = props;
const [productLoaded,setProductLoaded] = useState(false);
useEffect( () => {
if(productLoaded){
productGallery();
document.querySelector( '.skel-pro-single' ).classList.remove( 'loaded' );
let imgLoad = imagesLoaded( ".product-main-image", { background: true } );
imgLoad.on( 'done', function ( instance, image ) {
document.querySelector( '.skel-pro-single' ).classList.add( 'loaded' );
} );
}
}, [ productLink ] )
useEffect(()=>{
if(product){
setProductLoaded(true);
}
},[product])
return (
productLoaded ?
<>
<Helmet>
<title></title>
</Helmet>
<h1 className="d-none"></h1>
<div className="main">
<div className="page-content">
<div className="container">
<div className="product-details-top skeleton-body">
<div className="row skel-pro-single">
<div className="col-md-6">
<div className="skel-product-gallery">
</div>
<MediaOne link={ productLink } />
</div>
<div className="col-md-6">
<div className="entry-summary row">
<div className="col-md-12">
<div className="entry-summary1"></div>
</div>
<div className="col-md-12">
<div className="entry-summary2"></div>
</div>
</div>
<ProductDetailOne link={ productLink } />
</div>
</div>
</div>
<DescOne link={ productLink } />
<h2 className="title text-center mb-4">You May Also Like</h2>
<RelatedProducts />
</div>
</div>
<StickyBar link={ productLink } />
<QuickView />
</div>
</>
:
<></>
)
}
function mapStateToProps( state, props ) {
return {
product: state.data.products.filter( product => product.link == props.link )[ 0 ]
}
}
export default connect(mapStateToProps)(SingleProduct);
I tried to wait for product change using the useEffect and productLoaded state since it's undefined on first page render but it still showing undefined.
'product' variable, I think, is an object, be aware about what could happen if you use useEffect with that.
const a = {a:123};
const b = {a:123};
(a === b) === false
if it is possible see if the id or a string/number changes
The first useEffect will run before the second one so when this will run it will have productLoaded on false.
Try to gain productLoaded from props (you will not have to manage the asyncronicity of useEffect).
assuming that product is undefined before loading
const productLoaded = !!product;
useEffect( () => {
if(productLoaded){
..."do here what you need"
}
}, [ productLink, productLoaded ] )
maybe you can achieve a change in the css classes
<div className={`row skel-pro-single ${productLoaded?"loaded":""}`}>
as a suggestion try to use react as a templating with variables in the returning jsx, please don't mix js dom interaction and react interactions, they could disturb each other (react will try to report the part of the dom that he manages to the state he calculated, so could overwrite your changes made by
document.querySelector( '.skel-pro-single' ).classList.remove
i would like to know how can i add a CSS class to an element which has no any CSS classes.I am looking React Functional component solution with Hooks. Here i want to add class to tag and i don't need to add ${myclass} in advance. That means tag should be without any attributes before we execute the addclass functionality. I tried the following method and needs to get a best practice on it. Thanks in advance!
function Trial(){
const [myclass, changeclass] = useState("");
const addclass=()=>{
changeclass(`active`)
}
return(
<div>
<h1 className={` ${myclass}`}>Hi</h1>
<button onClick={addclass}>Click it</button>
</div>
)
}
You can add an id and then manipulate the element in the DOM by queryselector
in the element you want to change its className
<h1 className={`${myclass}`} id = "change-class">Hi</h1>
and then in the function
const addclass=()=>{
document.querySelector("#change-class").classList.add('new-class-name');
}
so the whole code would look like this
function Trial(){
const addclass=()=>{
document.querySelector("#change-class").classList.add('new-class-name');
}
return(
<div>
<h1 className={`${myclass}`} id = "change-class" >Hi</h1>
<button onClick={addclass}>Click it</button>
</div>
)
}
For more info check here and here
You can use useRef() hook for this:
function Trial(){
const ref = useRef(null);
const addclass=()=>{
const h1 = ref.current; // corresponding DOM node
h1.className = "active";
}
return(
<div>
<h1 ref={ref} className="">Hi</h1>
<button onClick={addclass}>Click it</button>
</div>
)
}
I am mapping through an array of objects and displaying their values in a card, each object has a title, comment, and datePosted field. The datePosted field is put throught a function I made to display it on the card as 'Posted 2 hours ago' or 'Posted '2 minutes ago'. This is updating though everytime the state changes, which isn't a problem when it was posted days ago, or even hours, as nothing changes, but when a new post is first uploaded and saved, the date being displayed updates practically everytime you type a key, as it goes from 'Posted 1 second ago' to 'Posted 2 seconds ago', to 'Posted 3 seconds ago' and so on... . Is there anything I can do to get around this, I've considered saving the component in a different state and displaying that, but I don't think that will work as the state needs to be updated everytime the user changes a title or comment so it can be displayed in real time.
Here's an example of the JSX that is displaying the card in case it helps. timeCalc is the function that calculates how long ago the post was created
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{timeCalc(item.datePosted)}</h3>
</div>
</div>
))}
I would extract the code to its own component, and then that component can use one of several possible techniques to remember the value from its first render. Here's one with useMemo:
{posts.map(item => <Post item={item}/>)}
//... elsewhere:
const Post = ({ item }) => {
const time = useMemo(() => {
return timeCalc(item.datePosted);
}, []);
return (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{time}</h3>
</div>
</div>
);
}
Other possible ways to remember the initial value are with state:
const Post = ({ item }) => {
// Note that i have no need for `setTime`, as this value will never be changed
const [time] = useState(timeCalc(item.datePosted));
Or with a ref:
const Post = ({ item }) => {
const timeRef = useRef(timeCalc(item.datePosted));
// ...
<h3>{timeRef.current}</h3>
You can create a component and memoize it using React.memo and areEqual function to avoid re-rendering when props i.e. date are same.
Demo:
const inputDate = new Date().toISOString();
function timeCalc(date) {
return moment(date).fromNow()
}
function TimeCalc({date}) {
return <h4>{timeCalc(date)}</h4>
}
function areEqual(prevProps, nextProps) {
return prevProps.date === nextProps.date
}
const TimeCalcMemo = React.memo(TimeCalc, areEqual);
function App() {
const [v, sv] = React.useState('')
return (<div>
<div>{v}</div>
<div>Enter something after a minute</div>
<input type="text" value={v} onChange={e => sv(e.target.value)} />
<TimeCalcMemo date={inputDate}/> memoized
<TimeCalc date={inputDate}/> not memoized
</div>)
}
ReactDOM.render(<App />, document.getElementById('mydiv'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<body>
<div id="mydiv"></div>
</body>
You can also try conditional rendering.
const lastUpdated = timeCalc(item.datePosted)
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
{
(compare timestamp here using moment.js or anyway you prefer) &&
<h3>{timeCalc(item.datePosted)}</h3>
}
</div>
</div>
))}