React Useeffect returning null array in first - javascript

I making a react project and while data loading i need take my datas from json with fetch-get. But while i doing it its sending empty array in first then 1-2second later sending me datas. So while its empty data my render is starting so my application is crashing. How can i make it without get empty data ? My codes :
const { id } = useParams();
const [articles, setArticles] = useState([]);
const [collection, setCollection] = useState([]);
useEffect(() => {
fetch("http://localhost:3001/api/v1/article/bycollection/" + `${id}`)
.then((x) => x.json())
.then((z) => {
setArticles(z);
}, []);
fetch("http://localhost:3001/api/v1/collection/" + `${id}`)
.then((x) => x.json())
.then((z) => {
setCollection(z);
}, []);
}, [id]);
Empty error line :
return (
<div className="tomiddle ">
<div className="mb-3">
<BreadCrumbComp data={breadCrumbData} />
</div>
<div className="collectionBg">
<div className="collectionHeader flex">
<div className="w-32 h-32 p-4 mt-2 text-5xl">
<i className={collection.icon}></i>
</div>
<div>
<h1 className="collectionTitle ">{collection.name}</h1>
<WriterGroupInfo groupData={collection} /> // Here i getting error.
Its sending empty collection
</div>
</div>)
WriterGroupInfo :
const WriterGroupInfo = (props) => {
const prop = props.groupData;
console.log("prop=>", props); // Showing empty array.
const articles = prop.articles.slice(0, 3);
const last = articles[articles.length - 1];
const lastAuthor = articles.length > 1 ? " ve " + last.author.name : "";
const authorNames =
articles
.slice(0, 2)
.map((a) => a.author.name)
.join(", ") + lastAuthor;
return (
<div className="flex pt-4 ">
<Avatar.Group>
{articles.map((x) => (
<Avatar key={x.id} src={x.author.avatar} />
))}
</Avatar.Group>
<div className="text-collection-small-500 truncate text-xs pl-2 pt-1">
<span>Bu koleksiyonda {prop.articles.length} makale mevcut</span>
<br />
{articles.length > 0 ? (
<span className="truncate mr-2">
<span className="text-gray-400 ">Yazarlar: </span>
{authorNames}
</span>
) : (
<></>
)}
</div>
</div>
);
};

This is happening because you are setting the first value of collecion as an empty array:
const [collection, setCollection] = useState([]);
You should handle this situations like this:
return (
<div className="tomiddle ">
<div className="mb-3">
<BreadCrumbComp data={breadCrumbData} />
</div>
{collection.length === 0 ?
<div> No data for collection </div> :
<div className="collectionBg">
<div className="collectionHeader flex">
<div className="w-32 h-32 p-4 mt-2 text-5xl">
<i className={collection.icon}></i>
</div>
<div>
<h1 className="collectionTitle ">{collection.name}</h1>
<WriterGroupInfo groupData={collection} /> // Here i getting error.
Its sending empty collection
</div>
}
</div>)

Your callback inside useEffect has 2 async calls and you are not waiting for them to finish. They take their time. Before that the next render has already taken place and for that reason you have your data empty for some time until your fetch(and subsequent setState) is completed.
Since both fetch are independent you can wait for them using Promise.all and await.
useEffect( () => {
async yourFunction() {
let promise1 = fetch("http://localhost:3001/api/v1/article/bycollection/" + `${id}`).then((x) => x.json()).then((z) => {
setArticles(z);
});
let promise2 = fetch("http://localhost:3001/api/v1/collection/" + `${id}`).then((x) => x.json())
.then((z) => {
setCollection(z);
});
let results = await Promise.all([promise1,promise2]);
}
yourFunction();
}, [id]);
Also, [] as an extra parameter for the second then() is not required in my opinion.

The issue is UseEffect works asynchronously and it triggers after the initial render. So at the initial render, it tries to populate using null/Empty array values. You can initiate the passing array values using useState like const[value,setValue]=useState([]) .

Related

React spread operator inside a table data

I am creating a data table, fetching data from Redux store, and setting up this Redux's raw data into a useState hook named rowsData, setRowsData for data table rows,
The data from redux is array of objects.
I then set the rowsData into a new [data, setData] useState because of some additional data, like meta information for pagination.
useEffect(() => {
const rawRows =
users.allUsers &&
users.allUsers.data.map((user) => {
return {
name: (
<Link
to={`/users/profile/view/${user.secondaryId}`}
className="d-flex justify-content-start align-items-center"
>
<div className="me-3">
<AvatarWord initial={user.name[0]} />
</div>
<h5 className="text-primary mt-2"> {user.name}</h5>
</Link>
),
primaryRole: primaryRoleBackground(
user.primary_role
? user.primary_role.name[0].toUpperCase() +
user.primary_role.name.slice(1)
: ""
),
// primary_role: primaryRoleBackground(user.primary_role),
id: user.id,
email: user.email,
status: <div>{userStatus(user.status, user.id)}</div>,
Here I am adjusting it according to data table. The last line ````Status: ``` has a function to discriminate the active and inactive users with a button to activate or deactivate them.
const userStatus = (status, id) => {
switch (status) {
case "0":
return (
<>
<span className="legend-indicator bg-danger text-dark"></span>
Inactive
<Link
to="#!"
type="button"
className="btn badge bg-success ms-2"
onClick={() => userActivator(id)}
>
Activate
</Link>
</>
);
case "1":
return (
<>
<span className="legend-indicator bg-success text-dark"></span>
Active
<Link
to="#!"
type="button"
className="btn badge bg-danger ms-2"
onClick={() => userDeactivator(id)}
>
Deactivate
</Link>
</>
);
default:
return;
}
};
Here is the output of the codes above.
now when I click on deactivate I use the following code to update the data.
const userDeactivator = (id) => {
// deactivateUser(id);
console.log(rowsData.length);
for (let i = 0; i === dataLength; i++) {
const res = rowsData[i].id === id;
setRowsData([
...rowsData,
{
...res,
status: (
<div>
<>
<span className="legend-indicator bg-danger text-dark"></span>
Inactive
<Link
to="#!"
type="button"
className="btn badge bg-success ms-2"
onClick={() => userActivator(res.id)}
>
Activate
</Link>
</>
</div>
),
},
]);
}
};
I sure sends the API call to deactivate the user, but I have to update the data status in runtime. What I am missing I cannot figure out.
In useEffect hook you have to pass the argument of status like this:
useEffect(() => {
return () => {
// Your Code
}
}, [status])
This would tell the useEffect that whenever the status changes you have to re-render the state for it.

How to add and remove multiple checkbox values and update nested array in React state hook

I am using React Context to create a multistep form, the form must keep the selection when the user clicks the next or previous step. I am using the below code and it's working fine until I reach the stage to add multiple features using the checkbox, once items are checked, user can go to the previous step to edit and press next to go to the next stage where checked checkboxes must remain checked. I cannot figure out how to push each checkbox value to the features array and remove the item from array when the user uncheck. The important part is to retain the selected despite user go to previous or next step.
Context Provider
import React, { useEffect, useState, createContext } from 'react'
const carSpecs = {
make: '', features: [],model: '',serviceHistory: false, warranty: false, trim: '', bodyType: '', transmission:''
}
export const UsedCarListingContext = createContext({})
export function GlobalUsedCarListingProvider (props) {
const [usedCar, setUsedCar] = useState(carSpecs)
useEffect(() => {}, [usedCar])
return (
<UsedCarListingContext.Provider
value={[usedCar, setUsedCar]}
>
{props.children}
</UsedCarListingContext.Provider>
)
}
Car Details Component
export const UsedCarAdDetails = () => {
const [usedCar, setUsedCar] = useContext(UsedCarListingContext)
const changeHandler = (e) => {
const {name, value} = e.target
setUsedCar({
...usedCar,
[name]: value
})
}
const handleChange = ({target: {name, checked}}) => {
setUsedCar({
...usedCar,
[name]: checked
})
}
return (
<div className="container-1100 bg-white shadow-nav-bar w-full h-52 pt-12">
<NavLink to={'/'} className='landing-nav-logo'>
<img
className='mr-20 mt-4 w-66 ottobay-logo-center'
src={OttobayGray}
alt={'logo'}
/>
</NavLink>
</div>
<div className="container-1050 mg-0auto flex justify-between mt-48">
<div className='container-700 p-20'>
<form>
<div className='ad-listing-input-wrapper mb-16-mobile mb-16 w-full-mobile flex items-center'>
<div className='ad-label-container'>
<label className="listing-input-label font-semibold mr-40"
htmlFor="videoLink">History: </label>
</div>
<div className="ad-input-group-container">
<div className='checkbox-group-container'>
<CheckboxWithImage
onChange={handleChange}
name={'serviceHistory'}
checked={usedCar.serviceHistory}
label={'Service History'}
icon={<GiAutoRepair/>}
checkboxTitleClass={'historyCB'}
/>
<CheckboxWithImage
onChange={handleChange}
name={'warranty'}
checked={usedCar.warranty}
label={'Warranty'}
icon={<AiOutlineFileProtect/>}
checkboxTitleClass={'historyCB'}
/>
</div>
</div>
</div>
<div>
<div className='checkbox-group-wrapper'>
{carFeatures.map(item => (
<div className='feature-item'>
<Checkbox
label={item.name}
onChange={handleFeaturesChange}
checked={usedCar && usedCar.features.some(val => val === item.id)}
value={item.id}
/>
</div>
))}
</div>
</div>
<div className="error-container"></div>
</div>
<div className="car-basic-submission-container">
<div> </div>
<button type='submit' className='search-button bg-button-primary text-white font-semibold rounded-4'> Next Step</button>
</div>
</form>
</div>
)
}
You seem to be calling a non existent function handleFeaturesChange in you feature-item checkbox.
Anyway, something like this should work:
const handleFeaturesChange = ({target: {value, checked}}) => {
setUsedCar({
...usedCar,
features: checked ? [
...usedCar.features,
value, // add the value to previously selected features
] : usedCar.features.filter(val => val !== value) // remove the value
})
}
You could potentially replace the value with name string but then you'd need to update the condition in the checked param of the Checkbox to compare it with the name instead.

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,
}
) => {

State not updating with useState set method

I'm trying to learn hooks and try to update the state using onMouseEnter and Leave events, but the state in isFlip.flipStat doesn't change, it used for flag to flipping the card using ReactCardFlip components. The only issues here is my state doesn't change when handleMouse function trigger, maybe anyone can help. Thanks in advance.
Here's my code :
function OurServices() {
const [isFlip, setFlip] = useState([])
const listServices = [
{
"title": "title",
"img": "img.jpg",
"desc": "lorem ipsum"
}
]
function handleMouse(key) {
let newArr = [...isFlip]
newArr[key].flipStat = !newArr[key].flipStat
setFlip(newArr)
}
useEffect(() => {
listServices.map((x) => (
x.flipStat = false
))
setFlip(listServices)
})
return (
{isFlip.map((x, key) => (
<div key={key} onMouseEnter={() => handleMouse(key)} onMouseLeave={() => handleMouse(key)}>
<div className={styles.card} >
<div className={styles.card_body+" p-xl-0 p-lg-0 p-md-1 p-sm-1 p-0"}>
<ReactCardFlip isFlipped={x.flipStat} flipDirection="horizontal">
<div className="row">
<div className={"col-xl-12 text-center "+styles.services_ic}>
<img className="img-fluid" src={x.img} width="72" height="72" alt="data-science" />
</div>
<div className={"col-xl-11 mx-auto text-center mt-4 "+styles.services_desc}>{x.title}</div>
</div>
<div className="row">
<div className={"col-xl-12 text-center "+styles.services_ic}>
{parse(x.desc)}
</div>
</div>
</ReactCardFlip>
</div>
</div>
</div>
))}
)```
The first problem is in your useEffect,
useEffect(() => {
listServices.map((x) => (
x.flipStat = false
))
setFlip(listServices)
})
you are setting listServices as the isFlip array. But Array.map() method doesn't update the source array. You need to write like this,
useEffect(() => {
const updatedArr = listServices.map((x) => (
x.flipStat = false
return x;
))
setFlip(updatedArr)
})
And can you log newArr after this line let newArr = [...isFlip] and see if that array has all the items? It should help you debug the issue.
Update:
Try creating new array while setting the state,
function handleMouse(key) {
let newArr = [...isFlip]
newArr[key].flipStat = !isFlip[key].flipStat
setFlip([...newArr])
}
The main problem is apparently from my code to adding flipsStat key to the listServices variable, to solving this I change the mapping way to this :
listServices.map((x) => ({
...x, flipStat: false
}))
Thanks for #sabbir.alam to reminds me for this.

Pokemon Api: TypeError: Cannot read property 'type' of undefined

I'm working with the pokemon API and I'm running into trouble. Here's the original code where I first encountered the problem.
import React, {useState} from 'react';
const api = {
base: "https://pokeapi.co/api/v2/pokemon/"
}
function App() {
const [query, setQuery] = useState('');
const [pokemon, setPokemon] = useState({});
const search = evt => {
if(evt.key === "Enter") {
fetch (`${api.base}${query}`)
.then(res => res.json())
.then(result => {
setPokemon(result);
setQuery('');
console.log(result);
})
}
}
const weightConverter = weight => {
return Math.floor(weight / 10) + ".0 kg.";
}
const heightConverter = height => {
return (height / 10) + " m."
}
return (
<div className="wrapper">
<main>
<div className="pokedex-search-box">
<input
type="text"
className="pokedex-search-bar"
placeholder="Search for a Pokémon..."
onChange={e => setQuery(e.target.value)}
value={query}
onKeyPress={search}
></input>
</div>
<div>
<div className="pokedex">
<div className="pokedex-left">
{pokemon.name}
<br></br>
{pokemon.id}
</div>
<div>
<img src={pokemon.sprites.front_default}></img>
<br></br>
{pokemon.types[0].type.name}
<br></br>
{pokemon.types[1].type.name}
<br></br>
{weightConverter(pokemon.weight)}
<br></br>
{heightConverter(pokemon.height)}
</div>
</div>
</div>
</main>
</div>
);
}
export default App;
Originally this was my code, but after looking up the problem on google I realized that the react dom was loading faster than my api call since you have to input a search that will then fetch the object. Which it can't do that since you have to wait for a search input. After that, I added this little bit to check to see if the object was already called and if not set it to an empty string so that you can use the search.
{pokemon.sprites ? (
<div className="pokedex">
<div className="pokedex-left">
{pokemon.name}
<br></br>
{pokemon.id}
</div>
<div>
<img src={pokemon.sprites.front_default}></img>
<br></br>
{pokemon.types[0].type.name}
<br></br>
{pokemon.types[1].type.name}
<br></br>
{weightConverter(pokemon.weight)}
<br></br>
{heightConverter(pokemon.height)}
</div>
</div>
) : ('')}
And that worked fine, but after a few searches I come back to the same problem of "TypeError: Cannot read property 'type' of undefined." I'm new to react so I'm a little lost. I have a temporary solution that works for the first three pokemon and then it breaks after that.

Categories

Resources