how unite two functions in to one in a functional component? - reactjs

I have this react functional component, I have a problem with the rest function inside of it I have to call this function twice to change every single state any way to unite them in to one ? in classic JavaScript I would use a conditional but since I am new to react I have no clue how to achieve this yet ?
import React, {useState} from 'react'
function ChangeableItem(props) {
setNumber([props.number])
setDescribe([props.dsecribe])
}
const restNumber = (e)=> {
setNumber('')
}
const restDescribe = (e)=> {
setDescribe('')
}
return (
<li className='day-item'>
<div className='number amount' onFocus={() =>{restNumber();}}} contentEditable="true">{number}</div>
<div className='number name' onFocus={() =>{restDescribe();}}} contentEditable="true">{describe}</div>
<div className="">
{icons}
</div>
</li>
)
}

actually, you can use useEffect on this, like you store your value in a useState and retrieve it in a useEffect
such as:
const [num, setNum] = React.useState('');
const [desc, setDesc] = React.useState('');
// these functions will retrieve values for you,
// when retrieving is done, it will set value to useState so you can use it.
React.useEffect(()=>{
restNumber([props.number])
// assuming this function return a Promise
.then((val)=>{
setNum(val);
})
},[props.number])
React.useEffect(()=>{
restDescription([props.description])
// assuming this function return a Promise
.then((val)=>{
setDesc(val);
})
},[props.description])
EDIT:
if you really want to retrieve value on focus, you can just do
<div onFocus={()=>restNumber([props.number]).then((val)=>setNum(val))}>
</div>

You can use useEffect to update new props:
useEffect(() => setNumber(props.number), [props.number])
useEffect(() => setDescribe(props.dsecribe), [props.dsecribe])

Related

React response get data

I wanted to add the data of a Axios response into my useState Array. The problem is that I don't know how to get the data out of the then() function. Basically what I want to do is save the Axios response so that I can use it in my Array. (I'm trying React for the first time)
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
console.log(res.data[0].title)
})
blogs.push({title: "TITLE HERE", text: "text", author: "author", date: "date"}); //I want to insert the title here
}
I already tried:
const [title, setTitle] = useState();
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
setTitle(res.data[0].title)
})
}
Heres the complete function:
import React, { useEffect, useState, express, memo } from "react";
import './network.css';
import Axios from 'axios';
function Content(props) {
const [blogs, setBlogs] = useState([]);
const [title, setTitle] = useState();
/**const [text, setText] = useState();
const [author, setAuthor] = useState();
const [date, setDate] = useState();*/
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
console.log(res.data[0].title)
})
blogs.push({title: "TITLE", text: "text", author: "author", date: "date"}); //I want to insert the title here
}
return (
<div>
{blogs.map((blog) => (
<div class="card">
<div class="card-body">
<h4>{blog.title}</h4>
<p>{blog.text}</p>
<div class="user">
<img alt="user" id="image"/>
<div class="user-info">
<h5>{blog.author}</h5>
<small>{blog.date}</small>
</div>
</div>
</div>
</div>
))}
</div>
);
}
export default Content;
Please add your fetch logic on useEffect hook. Otherwise, fetch logic will be executed in every re-render.
Your app may get frozen.
And you should not change state variable blogs by blogs.push....
use setBlogs function.
And please use className instead of class in DOM.
I see many problems in the code and strongly to read react help before writing any code.
Anyway updated code is here.
function Content(props) {
const [blogs, setBlogs] = useState([]);
const [title, setTitle] = useState();
/**const [text, setText] = useState();
const [author, setAuthor] = useState();
const [date, setDate] = useState();*/
useEffect(() => {
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
setBlogs(res.data);
})
}
}, []);
return (
<div>
{blogs.map((blog, key) => (
<div className="card" index={key}>
<div className="card-body">
<h4>{blog.title}</h4>
<p>{blog.text}</p>
<div className="user">
<img alt="user" id="image"/>
<div className="user-info">
<h5>{blog.author}</h5>
<small>{blog.date}</small>
</div>
</div>
</div>
</div>
))}
</div>
);
}
export default Content;
You should useEffect hook for fetching data and .then should set it to the state. UseEffect will fetch data when the component is rendered, and store it in the state. Then you can display it in jsx. I recommend reading this article (which shows how to do it with Axios).
https://www.freecodecamp.org/news/how-to-use-axios-with-react/
Since Axios is a promise-based HTTP client library you will have to deal with the data only after the response is resolved. This means you have two options . 1) use setBlog inside then function completely (which is mentioned by other answers already)
2) Use async-await syntax(which is just syntactic sugar to the Promise.then() and Promise.catch())
Another thing you need to keep in mind is to try and treat your react state arrays as immutable. So you need to use the spread operator instead of the traditional push method because the push method directly mutates your state array and can lead to some dangerous and error-prone code.
It is recommended that you make you react 'state' changes in the useEffect hook or inside a function in the functional component. triggering state changes in the manner you have done can lead to infinite rendering and cause a Runtime Error.
When creating a list in the UI from an array with JSX, you should add a key prop to each child and to any of its’ children. (the key is recommended 'NOT 'to be the index of the array) Below is the code sample to set your array into the state
useEffect(()=>{
(async()=>{
for(let i=0;i<=props.max;i++){
let response = await Axios.get("http://localhost:3001/content",{id:1});
setBlogs((blog)=>[...blog,response.data]);
}
})();
});

map not a function using react hooks

i'm trying to populate a select bar with a name from an API call. I Have created my hook, also useEffect for its side effects, and passed the data down the return. its giving me map is not a function error. my variable is an empty array but the setter of the variable is not assigning the value to my variable. How can i clear the map not a function error ? i have attached my snippet. Thanks.
import React, { useEffect, useState } from "react";
import axios from "axios";
const Sidebar = () => {
const [ingredients, setIngredients] = useState([]);
useEffect(() => {
const fetchIngredients = async (url) => {
try {
let res = await axios.get(url);
setIngredients(res.data);
} catch (error) {
setIngredients([]);
console.log(error);
}
};
fetchIngredients(
"https://www.thecocktaildb.com/api/json/v2/1/search.php?i=vodka"
);
}, []);
const displayIngredients = ingredients.map((ingredient) => {
setIngredients(ingredient.name);
return <option key={ingredient.name}>{ingredients}</option>;
});
return (
<div className="sidebar">
<label>
By ingredient:
<select>{displayIngredients}</select>
</label>
</div>
);
};
export default Sidebar
First, here
setIngredients(res.data);
change res.data to res.ingredients (the response object doesn't have data property). Then you'll face another bug,
const displayIngredients = ingredients.map((ingredient) => {
setIngredients(ingredient.name);
//...
First, ingredient.name is undefined, and second, it probably would be a string if it existed. Just ditch the setIngredients call here.
You are declaring displayIngredients as a variable typeof array (By directly affecting the array.map() result). You need it to be a function that return an array as follow :
const displayIngredients = () => ingredients.map((ingredient) => {
// Do not erase your previous values here
setIngredients(previousState => [...previousState, ingredient.name]);
// Changed it here as well, seems more logic to me
return <option key={ingredient.name}>{ingredient.name}</option>;
});
You should also wait for the API call to end before to display your select to prevent a blank result while your data load (If there is a lot). The easiest way to do that is returning a loader while the API call is running :
if(!ingredients.length) {
return <Loader />; // Or whatever you want
}
return (
<div className="sidebar">
<label>
By ingredient:
<select>{displayIngredients}</select>
</label>
</div>
);

useCallback with dependency vs using a ref to call the last version of the function

While doing a code review, I came across this custom hook:
import { useRef, useEffect, useCallback } from 'react'
export default function useLastVersion (func) {
const ref = useRef()
useEffect(() => {
ref.current = func
}, [func])
return useCallback((...args) => {
return ref.current(...args)
}, [])
}
This hook is used like this:
const f = useLastVersion(() => { // do stuff and depends on props })
Basically, compared to const f = useCallBack(() => { // do stuff }, [dep1, dep2]) this avoids to declare the list of dependencies and f never changes, even if one of the dependency changes.
I don't know what to think about this code. I don't understand what are the disadvantages of using useLastVersion compared to useCallback.
That question is actually already more or less answered in the documentation: https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
The interesting part is:
Also note that this pattern might cause problems in the concurrent mode. We plan to provide more ergonomic alternatives in the future, but the safest solution right now is to always invalidate the callback if some value it depends on changes.
Also interesting read: https://github.com/facebook/react/issues/14099 and https://github.com/reactjs/rfcs/issues/83
The current recommendation is to use a provider to avoid to pass callbacks in props if we're worried that could engender too many rerenders.
My point of view as stated in the comments, that this hook is redundant in terms of "how many renders you get", when there are too frequent dependencies changes (in useEffect/useCallback dep arrays), using a normal function is the best option (no overhead).
This hook hiding the render of the component using it, but the render comes from the useEffect in its parent.
If we summarize the render count we get:
Ref + useCallback (the hook): Render in Component (due to value) + Render in hook (useEffect), total of 2.
useCallback only: Render in Component (due to value) + render in Counter (change in function reference duo to value change), total of 2.
normal function: Render in Component + render in Counter : new function every render, total of 2.
But you get additional overhead for shallow comparison in useEffect or useCallback.
Practical example:
function App() {
const [value, setValue] = useState("");
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<Component value={value} />
</div>
);
}
function useLastVersion(func) {
const ref = useRef();
useEffect(() => {
ref.current = func;
console.log("useEffect called in ref+callback");
}, [func]);
return useCallback((...args) => {
return ref.current(...args);
}, []);
}
function Component({ value }) {
const f1 = useLastVersion(() => {
alert(value.length);
});
const f2 = useCallback(() => {
alert(value.length);
}, [value]);
const f3 = () => {
alert(value.length);
};
return (
<div>
Ref and useCallback:{" "}
<MemoCounter callBack={f1} msg="ref and useCallback" />
Callback only: <MemoCounter callBack={f2} msg="callback only" />
Normal: <MemoCounter callBack={f3} msg="normal" />
</div>
);
}
function Counter({ callBack, msg }) {
console.log(msg);
return <button onClick={callBack}>Click Me</button>;
}
const MemoCounter = React.memo(Counter);
As a side note, if the purpose is only finding the length of input with minimum renders, reading inputRef.current.value would be the solution.

Incorrect use of useEffect() when filtering an array

I have this React app that's is getting data from a file showing in cards. I have an input to filter the cards to show. The problem I have is that after I filter once, then it doesn't go back to all the cards. I guess that I'm using useEffect wrong. How can I fix this?
import { data } from './data';
const SearchBox = ({ onSearchChange }) => {
return (
<div>
<input
type='search'
placeholder='search'
onChange={(e) => {
onSearchChange(e.target.value);
}}
/>
</div>
);
};
function App() {
const [cards, setCards] = useState(data);
const [searchField, setSearchField] = useState('');
useEffect(() => {
const filteredCards = cards.filter((card) => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
});
setCards(filteredCards);
}, [searchField]);
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={cards} />
</div>
);
}
you should Include both of your state "Card", "searchedField" as dependincies to useEffect method.once any change happens of anyone of them, your component will re-render to keep your data up to date,
useEffect(() => { // your code }, [searchField, cards]);
cards original state will be forever lost unless you filter over original data like const filteredCards = data.filter().
though, in a real project it's not interesting to modify your cards state based on your filter. instead you can remove useEffect and create a filter function wrapped at useCallback:
const filteredCards = useCallback(() => cards.filter(card => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
}), [JSON.stringify(cards), searchField])
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={filteredCards()} />
</div>
);
working example
about array as dependency (cards)
adding an object, or array as dependency at useEffect may crash your app (it will throw Maximum update depth exceeded). it will rerun useEffect forever since its object reference will change everytime. one approach to avoid that is to pass your dependency stringified [JSON.stringify(cards)]

React Hook useEffect() run continuously although I pass the second params

I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,

Resources