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([]);
Related
I'm making movie app (using react.js)
I want to show a list of new movies whenever user scrolls down.
but when i write these codes, it doesn't work.
I used react-intersection-observer and made the second useEffect for adding new list.
can you see what is the problem...?
**import { useInView } from "react-intersection-observer";**
import { useEffect, useRef, useState } from "react";
import Movie from "../components/Movie";
import HeaderComponent from "../components/HomeButton";
import GlobalStyle from "../GlobalStyle";
import { Route, useParams } from "react-router-dom";
import { LoadingStyle, ListContainer } from "../components/styles";
function Home() {
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieSearch, setMovieSearch] = useState("");
const [movieName, setMovieName] = useState("");
const [pageNumber, setPageNumber] = useState(1);
** const { ref, inView } = useInView({
threshold: 0,
});**
const param = useParams();
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=1&page=${pageNumber}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
const onChange = event => {
setMovieSearch(event.target.value);
};
const onSubmit = event => {
event.preventDefault();
if (typeof param === Object) {
setMovieName(movieSearch);
getMovies();
} else {
Route(`/main`);
}
};
// When User Searching...
useEffect(() => {
getMovies();
}, [movieName]);
// When User Scroll, Keep Adding Movies at the bottom...
** useEffect(() => {
setPageNumber(prev => prev + 1);
setMovies(prev => {
return [...movies, ...prev];
});
getMovies();
}, [inView]);
**
return (
<>
<GlobalStyle />
<HeaderComponent
onSubmit={onSubmit}
onChange={onChange}
movieSearch={movieSearch}
/>
{loading ? (
<LoadingStyle>Loading...</LoadingStyle>
) : (
<>
<ListContainer>
{movies.map(item => {
return (
<Movie
key={item.title}
id={item.id}
title={item.title}
year={item.year}
medium_cover_image={item.medium_cover_image}
rating={item.rating}
runtime={item.runtime}
genres={item.genres}
summary={item.summary}
/>
);
})}
</ListContainer>
**{inView ? <>๐ญ</> : <>๐งถ</>}**
<div ref={ref} style={{ width: "100%", height: "20px" }}></div>
</>
)}
</>
);
}
export default Home;
and, when i debug this code, this errors comes out.
react_devtools_backend.js:4026 Warning: Encountered two children with the same key, `Headless Horseman`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted โ the behavior is unsupported and could change in a future version.
at div
at O (http://localhost:3000/static/js/bundle.js:47415:6)
at Home (http://localhost:3000/static/js/bundle.js:908:80)
at Routes (http://localhost:3000/static/js/bundle.js:41908:5)
at Router (http://localhost:3000/static/js/bundle.js:41841:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:40650:5)
at App
I have this code that controls the behavior of what to map from an array onClick. The useState is set to a string const [activeFilter, setActiveFilter] = useState('All'); that is supposed to automatically filter all products containing the string as tag but it doesn't do this automatically and I can't figure out why. Please help with code below.
index.js
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { Product, FooterBanner, HeroBanner } from '../components'
const Home = ({products, bannerData}) => {
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
useEffect(() => {
setProductItems(products)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setTimeout(() => {
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.filter((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below is what it looks like on load and the only time All products containing 'All' tags are visible is when the All button is clicked on again, regardless of it being active initially
No products are being displayed initially when the component renders because the displayed products are loaded from the filterWork state that is only set once an onClick event is triggered. To fix this you can simply set the initial products in the useEffect because you are starting with all the products being displayed.
useEffect(() => {
setProductItems(products);
setFilterWork(products);
}, [])
Code sandbox link: https://codesandbox.io/s/useinterval-customhook-iucj8q?file=/src/components/Displaytimer.js
I have created a custom hook for clock countdown while I am passing minutes input field values and seconds input fields as a prop to the child component it is taking the values too but when I click the start button it is still showing the 0. I think this is taking initial values I have used promises too and console logging each and every value but no use.
Image for output:
APP.js
import "./styles.css";
import Timer from "./components/Timer";
export default function App() {
return (
<div className="App">
<Timer />
</div>
);
}
Timer.js
import { useState, useRef } from "react";
import DisplayTimer from "./Displaytimer";
export default function Timer() {
const [min, setMins] = useState(0);
const [sec, setSecs] = useState(0);
const refValueMinutes = useRef();
const refValueSeconds = useRef();
const onchangeMinutes = (e) => {
// refValueMinutes.current = Number(e.target.value);
// const currVal = refValueMinutes.current;
setMins(Number(e.target.value));
};
const onchangeSeconds = (e) => {
// refValueSeconds.current = Number(e.target.value);
// const currVal = refValueSeconds.current;
setSecs(Number(e.target.value));
};
return (
<div>
<h2>Minutes: </h2> <br />
<input onChange={onchangeMinutes} />
<h2>Seconds: </h2> <br />
<input onChange={onchangeSeconds} />
<br />
<br />
<DisplayTimer min={min} sec={sec} />
</div>
);
}
enter image description here
UseInterval.js(custom hook)
import { useRef, useEffect } from "react";
export default function UseInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => {
clearInterval(id);
};
}
}, [delay]);
}
I thought this is due to DOM painting to the web page before the values get initiated to the state so I have tried using promises but no result and suggest me a good way to render this design to the webpage.
I have used use effect too:
useEffect(() => {
startTime();
stopTime();
resetTime();
}, []);
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)
Why am I not having access to the updated recipes (useState) value from inside the component that defines it?
In this example you can see how not being able to access to this value causes an error in the app once the reference to a function that I use to update the state is deleted
=> Codebox and code below
*Click two times the <h1> to see the error
https://codesandbox.io/s/sparkling-sea-5iqgo?fontsize=14&hidenavigation=1&theme=dark
import React, { useEffect, useState } from "react";
export default function App() {
const [userRecipes, setUserRecipes] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
userRecipes.setBookmarks("onetwothree");
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
console.log(recipes); // is undefined !? and deletes setBookmarks
setRecipes({
bookmarked_recipes: newRecipes,
setBookmarks: recipes.setBookmarks
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
setBookmarks: setBookmarks
});
}, []);
return [recipes, setRecipes];
};
What I don't understand is why if I return [recipes, setRecipes] where recipes.setBookmarks stores a reference to a function, it doesn't work
But if I return the function itself (which is a reference as well) [recipes, setBookmarks] then it works
See this other codebox where it does work
https://codesandbox.io/s/red-violet-gju99?fontsize=14&hidenavigation=1&theme=dark
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [userRecipes, setUserRecipes] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
setUserRecipes("onetwothree" + Math.random());
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
console.log(recipes); // is defined this time
setRecipes({
bookmarked_recipes: newRecipes,
setBookmarks: recipes.setBookmarks
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
setBookmarks: setBookmarks
});
}, []);
return [recipes, setBookmarks];
};
It's all about context.
If you'll put console.log(receipes) in useEffect and the render function itself, you can see what the flow of events are:
First render recipe is empty.
UseEffect is called and puts setBookmark in recipe (but the recipe for setBookmark is empty)
Second render is called, and now recipe has "testesttest" and recipe.setBookmark is a function where the recipe object that is bound to it is the recipe value from event 1
setBookmark is called, recipe is now set to "onetwothree" but the recipe object is empty so we set the setBookmark to undefined.
instead of keeping the function inside the state, you need to just call it directly (I.E. return setBookmark and not setRecipes, like this:
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [userRecipes, setBookmarks] = useRecipesData();
return (
<div className="App">
<h1
onClick={() => {
setBookmarks("onetwothree" + Math.random());
}}
>
Hello CodeSandbox
</h1>
<h2>{userRecipes.bookmarked_recipes}</h2>
</div>
);
}
const useRecipesData = () => {
const [recipes, setRecipes] = useState({});
const setBookmarks = newRecipes => {
setRecipes({
bookmarked_recipes: newRecipes,
});
};
useEffect(() => {
setRecipes({
bookmarked_recipes: "testtesttest",
});
}, []);
return [recipes, setBookmarks];
};