I'm having an issue where I am unable to set the state using "setPhotos" without setting up a "setTimeout()". When I console.log I get the data, but when changing the console.log(newArray) to setPhotos(newArray) the state is not updated / rendered.
setTimeout(() => {
setPhotos(newArray);
}, 1000);
Any help would be so greatly appreciated. Maybe I am fundamentally using Promises wrong..?
import React, { useState, useEffect } from "react";
import { useParams, useLocation } from "react-router-dom";
import { Table } from "react-bootstrap";
import Firebase from "../../../config/Firebase";
import "./QuoteRequest.scss";
const QuoteRequest = () => {
const { request } = useLocation().state;
console.log(request);
const [photos, setPhotos] = useState([]);
useEffect(() => {
let newArray = [];
Firebase.storage()
.ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
data.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
newArray.push(url);
});
});
})
.then(() => {
setTimeout(() => {
setPhotos(newArray);
}, 1000);
});
}, []);
return (
<div className="perform mt-4">
<h1>Perform Quote</h1>
<Table bordered></Table>
<h1>Quote here v</h1>
{photos.length ? (
photos.map((photo) => (
// <span>img</span>
<img src={photo} className="photo" />
))
) : (
<span>no</span>
)}
</div>
);
};
export default QuoteRequest;
UPDATE:
I was able to get the result I needed with the following:
useEffect(() => {
const promises = [
Firebase.storage()
.ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
return data.items.map((imageRef) => {
return imageRef.getDownloadURL();
});
}),
];
Promise.all(promises).then((data) => {
// setPhotos(urls);
let newArray = [];
data[0].map((items) => {
items.then((url) => {
console.log(url);
newArray.push(url);
setPhotos(newArray);
});
});
});
}, []);
Try using Promise.all
const promises = Firebase.storage().ref("/images/requests/" + request.id)
.listAll()
.then((data) => {
return data.items.map((imageRef) => {
return imageRef.getDownloadURL()
});
})
Promise.all(promises).then((urls) => {
setPhotos(urls);
});
}, []);
Related
import './App.css';
import io from 'socket.io-client'
import { useEffect, useRef, useState } from 'react'
import React from 'react';
import ReactDOM from "react-dom/client";
const socket = io.connect("http://localhost:3001");
function App() {
const [message, setMessage] = useState("");
const [state, setState] = useState([]);
const [chat, setChat] = useState([]);
const socketRef = useRef();
const sendMessage = () => {
socket.emit("send_message", { message });
};
const renderChat = () => {
return (
chat.map(msg => {
console.log(msg.data)
return (
<h3>{msg.data["message"]}</h3>
)
})
)
}
useEffect(() => {
socketRef.current = io.connect("http://localhost:3001")
socketRef.current.on("receive_message", ({ message }) => {
setChat([ ...chat, { message } ])
})
return () => socketRef.current.disconnect()
},
[ chat ]
)
return (
<div className="App">
<input placeholder="Message..." onChange={(event) => {
setMessage(event.target.value);}}
/>
<button onClick={sendMessage}>Send Message</button>
<h1>Message:</h1>
{renderChat()}
</div>
);
}
export default App;
For some reason the useEffect that needs to store information doesn't work. I have tried a few solutions to store new values in an array useState but I always get this error:
When I do it like this:
useEffect(() => {
socket.on("receive_message", message => {
setChat([...chat, {message}]);
});
}, [socket])
it works but it doesn't save the information (it always has only 1 value which is the latest input text).
You can do it like in the second approach you mentioned, using the previous State:
useEffect(() => {
socket.on("receive_message", message => {
setChat(prevState => [...prevState, {message}]);
});
}, [socket])
You try
useEffect(() => {
socket.on("receive_message", ({ message }) => {
if(!!message){
setChat(prev => [ ...prev, { message } ])
}
})
return () => socket.disconnect()
},[ socket ])
I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})
I have a component that fetches the data properly but I want to encapsulate it in a helper. I've tried many things but I'm stuck.
This is the component that works:
export const Carousel = () => {
const [ lotteries, setLotteries ] = useState({});
const [ isLoading, setisLoading ] = useState(true);
useEffect(() => {
async function fetchAPI() {
const url = 'https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
response = await response.json()
setLotteries(response)
setisLoading(false)
}
fetchAPI()
}, [])
return (
<>
{
isLoading ? (
<span>loading</span>
) : (
<Slider >
{
Object.keys(lotteries).map((lottery, idx) => {
return (
<Slide
key={ idx }
title={ lottery }
prize={ lotteries[lottery].next.jackpot }
day={ lotteries[lottery].next.date.day }
/>
)
})
}
</Slider>
)}
</>
);}
And this is the last thing I've tried so far. This is the component without the fetch
export const Carousel = () => {
const [ lotteries, setLotteries ] = useState({});
const [ isLoading, setIsLoading ] = useState(true);
useEffect(() => {
getLotteries()
setLotteries(response)
setIsLoading(false)
}, [])
And this is where I tried to encapsulate the fetching.
export const getLotteries = async() => {
const url = 'https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
response = await response.json()
return response;
}
I'm a bit new to React, so any help would be much appreciated. Many thanks.
To get the fetched data from getLotteries helper you have to return a promise
export const getLotteries = async() => {
const url = 'https://protected-sea-
30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
return response.json()
}
and call it as async/await
useEffect(async() => {
let response= await getLotteries()
setLotteries(response)
setIsLoading(false)
}, [])
If you want to separate the logic for requesting a URL into another helper function, you can create a custom hook.
// customHook.js
import { useEffect, useState } from 'react';
export function useLotteries() {
const [lotteries, setLotteries] = useState(null);
useEffect(() => {
fetch('https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;')
.then(response => response.json())
.then(json => setLotteries(json));
}, []);
return lotteries;
}
// Carousel.js
import { useLotteries } from "./customHook.js";
export const Carousel = () => {
const lotteries = useLotteries();
if (lotteries) {
return; /* Your JSX here! (`lotteries` is now contains all the request responses) */
} else {
return <Loader />; // Or just null if you don't want to show a loading indicator when your data hasn't been received yet.
}
};
As you can see below in the dev tools screen shot, the child element does have props. My issue is I cannot get them to appear in the DOM when the component is first rendered. I have to click on the Link element again to re-render the component and only then does the map function work correctly (second screen shot). Another thing is I am using the same code in another component and it works fine. Help!
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import NewsLetterListChildComponent from './children/NewsLetterListChildComponent';
import LoadingComponent from '../Loading/LoadingComponent';
function PublicNewsLetterListComponent({ user }) {
const [ newsLetters, setNewsLetters ] = useState([]);
const [ loading, setLoading ] = useState(false);
const [ errors, setErrors ] = useState(false);
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
let newsLetterArray = [];
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
listRef
.getDownloadURL()
.then(url => {
newsLetterArray.push({ id: doc.id, data: doc.data(), image: url });
})
.catch(error => console.log(error))
});
});
setNewsLetters(newsLetterArray);
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);
const renderContent = () => {
if(loading) {
return <LoadingComponent />
} else {
return <NewsLetterListChildComponent newsLetters={newsLetters} />
}
}
return renderContent();
}
export default PublicNewsLetterListComponent
import React from 'react';
import { ListGroup, ListGroupItem, Row, Col } from 'reactstrap';
function NewsLetterListChildComponent({ newsLetters }) {
return (
<div>
<Row>
<Col md={{size: 6, offset: 3}}>
<ListGroup>
{newsLetters.map((item, index) => {
return (
<ListGroupItem key={index} className="m-1" ><h1>{item.data.title} </h1><img src={item.image} alt={item.data.title} className="thumb-size img-thumbnail float-right" /></ListGroupItem>
);
})}
</ListGroup>
</Col>
</Row>
</div>
)
}
export default NewsLetterListChildComponent;
Initial render and the list group is empty
after the re-render and now the list group is populated
You need to call setNewsLetters when the data is resolved:
const getNewsLetters = async () => {
setLoading(true);
try {
const newsLetters = await firebase
.firestore()
.collection("newsLetters")
.get();
const data = await Promise.all(
newsLetters.docs.map(async (doc) => {
const url = await firebase
.storage()
.ref()
.child("newsLetterImagesRef/" + doc.id)
.getDownloadURL();
return {
id: doc.id,
data: doc.data(),
image: url,
};
})
);
setNewsLetters(data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
The useEffect code contains an async request and you are trying to update an array of newsLetters in state even before it will be fetched. Make use of Promise.all and update the data when it is available
useEffect(() => {
let requestCancelled = false;
const getNewsLetters = () => {
setLoading(true);
firebase
.firestore()
.collection('newsLetters')
.get()
.then((querySnapshot) => {
const promises = querySnapshot.map((doc) => {
const listRef = firebase.storage().ref().child('newsLetterImagesRef/' + doc.id);
return listRef
.getDownloadURL()
.then(url => {
return { id: doc.id, data: doc.data(), image: url };
})
.catch(error => console.log(error))
Promise.all(promises).then(newsLetterArray => { setNewsLetters(newsLetterArray);})
});
});
setLoading(false);
};
getNewsLetters();
return () => {
requestCancelled = true;
};
}, []);
If you check newletters with if, your problem will most likely be resolved.
review for detail : https://www.debuggr.io/react-map-of-undefined/
if (newLetters){
newLetters.map(item=> ...)
}
I'm trying to change a class component into a functional component so that I can delete questions that are rendered onto the screen. My main focus is mainly deletedQuestion().
import React, { useEffect, useState } from "react"
import Axios from "axios"
import { deleteQuestion } from './InputQuestionsFunctions'
export default function () {
const [questions, setQuestions] = useState([])
useEffect(() => {
getQuestions()
}, [])
async function getQuestions() {
try {
const res = await Axios.get("http://localhost:5000/QuestionsRoute/me", {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
})
// if(res.status)
setQuestions(res.data)
}
catch (e) {
console.log("error while getting questions", e)
}
}
const deletedQuestion = (e) => {
e.preventDefault()
const removedQuestion = {
id:_id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
const renderQuestions = () => {
return questions.map(eachQuestion => {
return (
<div>
<button onClick={(e) => deletedQuestion(e)}
className="mdi mdi-delete mdi-24px lifeline-icon" />
<p> {eachQuestion.question}</p>
<p style={{ color: "blue" }}> {eachQuestion.answer}</p>
</div >
)
})
}
return (
<div>
{renderQuestions()}
</div>
)
}
right now the _id is undefined but ideally onClick={(e) => deletedQuestion(e)} would use the id to delete the specific question. Below is the imported () for reference!
export const deleteQuestion = removedQuestion => {
const body = {
id:removedQuestion._id
}
const options = {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
};
return axios
.delete(
process.env.REACT_APP_SERVER + '/QuestionsRoute/me/_id',
body,
options
).then(res => {
console.log('Deleted')
})
}
I'm new I would greatly appreciate the edited code but any help is appreciated!
You have access to the question in the .map so pass it through when you call deletedQuestion:
const deletedQuestion = (e, question) => {
e.preventDefault()
const removedQuestion = {
id: question._id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
...
<button onClick={(e) => { deletedQuestion(e, eachQuestion); }}
Inside deletedQuestion block, there is no _id variable, that's why it is undefined.
- You need to pass eachQuestion._id to deletedQuestion
import React, { useEffect, useState } from "react"
import Axios from "axios"
import { deleteQuestion } from './InputQuestionsFunctions'
export default function () {
const [questions, setQuestions] = useState([])
useEffect(() => {
getQuestions()
}, [])
async function getQuestions() {
try {
const res = await Axios.get("http://localhost:5000/QuestionsRoute/me", {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
})
// if(res.status)
setQuestions(res.data)
}
catch (e) {
console.log("error while getting questions", e)
}
}
// Now you have _id
const deletedQuestion = (e, _id) => {
e.preventDefault()
const removedQuestion = {
id:_id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
const renderQuestions = () => {
return questions.map(eachQuestion => {
return (
<div>
{/* pass eachQuestion._id into deletedQuestion*/}
<button onClick={(e) => deletedQuestion(e, eachQuestion._id)}
className="mdi mdi-delete mdi-24px lifeline-icon" />
<p> {eachQuestion.question}</p>
<p style={{ color: "blue" }}> {eachQuestion.answer}</p>
</div >
)
})
}
return (
<div>
{renderQuestions()}
</div>
)
}