How optimize little code React "RandomImage" from array with onclick - reactjs

I make a little code for take array images and show one and change it with click, but I think that code can be more optimized, especialy the second useState but I dont know how.
import React, { useEffect, useState } from 'react';
const apiURL = 'https://picsum.photos/v2/list?page=2&limit=100';
export default function Image() {
function random(mn, mx) {
return Math.random() * (mx - mn) + mn;
}
const [gifs, setgifs] = useState([]);
useEffect(function () {
console.log('test');
fetch(apiURL)
.then((res) => res.json())
.then((response) => {
const gifs = response.map((image) => image.download_url);
setgifs(gifs);
});
}, []);
let imagenaleatoria = gifs[Math.floor(random(1, gifs.length))];
const [imagenactual, nuevaimagen] = useState();
return (
<div className='App'>
<section className='App-header'>
<div className='caja'>
<img
onClick={() => {
nuevaimagen(gifs[Math.floor(random(1, gifs.length))]);
}}
src={imagenaleatoria}
></img>
</div>
</section>
</div>
);
}
Thanks so much.

I thought I just rewrite your code snippet and add comments so you understand what and why
import { useState, useEffect } from "react";
const apiURL = "https://picsum.photos/v2/list?page=2&limit=100";
// has no acces to state so there is no need to create this funtion isde the RandomImage component
export const getRandomIndex = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
export const RandomImage = () => {
const [gifs, setGifs] = useState([]);
const [randomIndex, setRandomIndex] = useState(null);
// runs only after the first render
useEffect(function () {
console.log("test");
fetch(apiURL)
.then((res) => res.json())
.then((response) => {
const gifs = response.map((image) => image.download_url);
setGifs(gifs);
setRandomIndex(getRandomIndex(0, gifs.length));
});
}, []);
// just sets an random index
const handleClick = (event) => {
setRandomIndex(getRandomIndex(0, gifs.length));
};
return (
<div className="App">
<section className="App-header">
<div className="caja">
{!gifs.length ? (
'Loading...'
) : (
<img onClick={handleClick} src={gifs[randomIndex]} />
)}
</div>
</section>
</div>
);
};

Related

Not Rendering Card - React

I'm new to React, and I would like to know if someone can help me?
I'm trying to use useEffect and State to manipulate the API.
But the cards are not rendering.
Sometimes all the cards are rendering, other times not.. and they always come on a different order even after sorting them :( Can you help me?
App.js
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from "react";
import PlayerList from "./PlayerList";
import axios from "axios";
function App() {
const Team = [
...
];
const Team2 = [
...
];
const Team3 = [
...
];
const teamForLoop = [Team, Team2, Team3];
const [allPlayers, setAllPlayers] = useState([]);
const [team, setTeam] = useState([]);
const [allTeams] = useState(teamForLoop);
const [loading, setLoading] = useState(true);
useEffect(() => {
const playerInfo = async () => {
setLoading(true);
allTeams.map(async (teamArray) => {
setTeam([]);
teamArray.map(async (player) => {
let playerName = player.split(" ");
const result = await axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${playerName[0]}%20${playerName[1]}`
);
if (result.data.player === null) {
setTeam((state) => {
return [...state];
});
} else {
setTeam((state) => {
return [...state, result.data.player[0]];
});
}
});
setAllPlayers(team);
});
setLoading(false);
};
playerInfo();
}, [allTeams]);
if (loading) return "...Loading...";
return (
<>
<PlayerList allPlayers={allPlayers} />
</>
);
}
export default App;
PlayerList.js
import React from "react";
export default function PlayerList({ allPlayers }) {
const myData = []
.concat(allPlayers)
.sort((a, b) => (a.strNumber > b.strNumber ? 1 : -1))
.sort((a, b) => (a.idTeam !== b.idTeam ? 1 : -1));
return (
<div>
{myData.map((player, index) => (
<div key={index}>
<div className="playerCard">
<img
className="playerImage"
src={player.strCutout}
alt={`${player.strPlayer}`}
/>
<h1 className="playerName">{player.strPlayer}</h1>
<h2 className="playerNumber">{player.strNumber}</h2>
</div>
</div>
))}
</div>
);
}
Codesandbox link:
"https://codesandbox.io/s/busy-orla-v872kt?file=/src/App.js"

useEffect fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

useEffect dosn't save data in localstorage

I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)

reset new array reactjs infinite scroll

I have tried infinite scroll for reactjs from this link https://www.youtube.com/watch?v=NZKUirTtxcg&t=303 and work perfectly. But I want to improve with my condition.
I have make infite scroll for case products, the product has sub_category and sub_category has one category. For example I have one page showing all products by category (it's showing all sub_category).
The user can choose the product base sub_category (the page showing just what user choose for sub_category).
And my problem is I don't know to reset product variable as new array to fullfill products from sub_category.
I have two component ListInfiteTwo.jsx and UseProductSearch.jsx
ListInfiteTwo.jsx
import React, { useEffect, useState, useRef, useCallback } from 'react';
import axios from 'axios';
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import '../styleProduct.css';
import { NavbarPageListProduct, NotFoundPage, COBottomNav } from '../../../components';
import configAPI from '../../../api/configAPI';
import productAPI from '../../../api/productAPI';
import Kcard from '../../Card/Kcard';
import UseProductSearch from './UseProductSearch';
export default function ListInfiniteTwo(props) {
const classes = useStyles();
const [totQtyItem, setTotQtyItem] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const [category, setCategory] = useState(props.match.params.id);
const [subCategory, setSubCategory] = useState(null);
const [subCategories, setSubCategories] = useState([]);
const [amount, setAmount] = useState(0);
const [limit, setLimit] = useState(6);
const [selectedSubCategory, setSelectedSubCategory] = useState('selectedSubCategory');
const {
loading,
error,
products,
hasMore
} = UseProductSearch(pageNumber, category, limit, subCategory)
const observer = useRef()
const lastProductElementRef = useCallback(node => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
setPageNumber(prevPageNumber => prevPageNumber + 1)
}
})
if (node) observer.current.observe(node)
}, [loading, hasMore])
useEffect(() => {
let getSubCategoriesAct = configAPI.getSubCategory(kategori);
getSubCategoriesAct.then((response) => {
setSubCategories(response.data)
}).catch(error => {
console.log(error)
});
},[])
const callBackAddItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackDeleteItemTotal = (data) => {
setTotQtyItem(data)
}
const callBackCalculateAmount = (data) => {
setAmount(data);
}
const selectSubCategory = (id) => {
setSubKategori(id)
setPageNumber(1)
}
return (
<>
<NavbarPageListProduct
titleView="List Product"
viewPrev="detailOrder"
totalQtyItem={totQtyItem}
cHistoryId={props.match.params.id}
/>
<div className={classes.root}>
<div className="css-ovr-auto">
<div className="css-ovr-auto">
<div className="css-c-1hj8">
<div className="css-c-2k3l">
{
<>
<div className={ selectedSubCategory === 'selectedSubCategory' ? 'css-sb-sl-top-active' : 'css-sb-sl-top'} >
<div className="css-sb-sl-label">
<span className="css-sb-sl-val"> All on Category </span>
</div>
</div>
{subCategories.map((x, z) =>
<div className="css-sb-sl-top" onClick={() => selectSubCategory(x._id) }>
<div className="css-sb-sl-label">
<span className="css-sb-sl-val" >{x.name}</span>
</div>
</div>
)}
</>
}
</div>
</div>
</div>
</div>
<Grid container spacing={1}>
<Grid container item xs={12} spacing={1}>
{
products.length >= 1 ?
products.map((pr, index) =>
<React.Fragment>
<div ref={lastProductElementRef}></div>
<Kcard
ref={lastProductElementRef}
product={pr}
callBackAddItemTotal={callBackAddItemTotal}
callBackDeleteItemTotal={callBackDeleteItemTotal}
callBackCalculateAmount={callBackCalculateAmount}
/>
</React.Fragment>
)
:
<NotFoundPage
content="No Products"
/>
}
</Grid>
</Grid>
</div>
<div>{loading && 'Loading...'}</div>
<div>{error && 'Error'}</div>
{
amount > 0 ?
<COBottomNav
titleBottom="Total Pay"
amount={amount}
titleBtnBottom="Process"
action='proces_list'
/>
:
""
}
</>
)
}
UseProductSearch.jsx
import { useEffect, useState } from 'react';
import axios from 'axios';
export default function UseProductSearch(pageNumber, category, limit, subCategory) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const [products, setProducts] = useState([])
const [hasMore, setHasMore] = useState(false)
const [lastPage, setLastPage] = useState(0)
useEffect(() => {
setProducts([])
}, [])
useEffect(() => {
setLoading(true)
setError(false)
let cancel
if (subCategory) {
setProducts([])
}
axios({
method: 'GET',
url: process.env.REACT_APP_API_URL + `data-product-pagination`,
params: {
orderby: 'newest',
type: 'verify',
page: pageNumber,
limit: limit,
xkategori: category,
subkategori: subCategory,
},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(res => {
if (res.data.data) {
if (res.data.data.data.length > 0) {
setProducts(prevProducts => {
return [...new Set([...prevProducts, ...res.data.data.data])]
})
}
}
setHasMore(res.data.data.data.length > 0)
setLoading(false)
setLastPage(res.data.data.last_page)
}).catch(e => {
if (axios.isCancel(e)) return
setError(true)
})
return () => cancel()
}, [pageNumber, category, limit, subCategory])
return { loading, error, products, hasMore }
}
what I have tried to add code on UseProductSearch.jsx
if (subCategory) {
setProducts([])
}
it's work when user choose sub category the page showing new products base on sub_category, but when I scroll down it's reseting the product to empty array.
Thx, for your help...
Try including subCategory as a dependency in your first useEffect hook from useProductSearch instead. This would reset your array whenever the subCategory state changes.
useEffect(() => {
setProducts([])
}, [subCategory])

function Component receive props but doesn't render it

Situation:
first, I fetch imgs list from database:
{imgs:
[
{_id: '...',img:'***.png'},
...,
]
}
then, signature img.src using ali-oss-hook, results like:
{imgs:
[
{_id:'...', img: '***.png', src: 'signatured-http-address'}
...,
]
}
then, pass the imgs to PictureList component :
<PictureList imgs={images}
PictureList receive the new props,but didn't render it
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
Code
Picture.js
import React, {useEffect, useState, useRef } from 'react'
import { useAlioss } from '../../hooks/oss-hook'
import PictureList from '../../components/PictureList'
import './style.less'
const Pictures = () => {
const [loading, setLoading] = useState(true)
const [signatured, setSignatured] = useState(false)
const [results, setResults] = useState()
const [images, setImages] = useState([])
const { allowUrl } = useAlioss()
const resultsDoSetRef = useRef(false)
async function getImages() {
try {
const dbResponse = await fetch(
`${process.env.REACT_APP_BACKEND_URL}/upload/images`
);
const resu = await dbResponse.json();
setResults(resu)
resultsDoSetRef.current = true
} catch (e) {
console.log("get images failed")
} finally {
setLoading(false)
console.log("get images done!")
}
}
useEffect(() => {
getImages();
}, [])
async function signatureUrl(raw) {
setSignatured(false)
try {
let tempImgs = []
raw.imgs.forEach((r) => {
allowUrl(r.img).then((res) => {
r.img = res;
tempImgs.push(r)
});
});
setImages(tempImgs);
} catch (e) {
console.log("signature failed",e)
} finally {
setSignatured(true)
console.log("signature done!")
}
}
useEffect(() => {
if (resultsDoSetRef.current) {
resultsDoSetRef.current = false
signatureUrl(results);
}
},[results])
return (
<div className="picture">
{loading ? <h1>Loading</h1> : <PictureList imgs={images} />}
</div>
);
};
export default Pictures
PictureList.js
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
chrome react devTool component shows props
chrome devTool element shows empty PictureList
In chrome devTool react component shows right props, but the PictureList component still empty <ul></ul>.
Which part is wrong?
Look at the PictureList.js, you are receving "imgs" as the arugument of the function, this is not the same as property you passed in
<PictureList imgs={images}
This "imgs" is actually an object that has a property imgs, so your code will become:
const PictureList = ({ imgs }) => {
return (
<ul>
{imgs.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
P.S : Just a suggestion, generally props(or something similarly descriptiove) is used as the parameter argument, so your code will be something like this:
const PictureList = ({ props }) => {
return (
<ul>
{props.imgs.map((i) => (
<img key={i._id} src={i.src} alt="pic" />
))}
</ul>
);
}
export default PictureList
In Picture --signatureUrl() method, raw.imgs.forEach() returns a bounch of promises, these promises can't resolve all at once.
When setImages(tempImgs) ,the images in useState hook receive a empty array first, then push new image to images array when raw.imgs.forEach() returned promise resolve a new image item.
So, we must wait all allowUrl(r.img) promises resolved, then setImages(tempImgs).
function signatureUrl(raw) {
const tasks = raw.imgs.map(i => allowUrl(i.img))
Promise.all(tasks).then(values => {
let resultImgs = raw.imgs.map((t, index) => ({ ...t, src: values[index] }));
setImages(resultImgs)
})
}
PS: Solution do works, but all analizes may be wrong, for reference only.

Resources