I am using firebase with react to store images so that the user can upload them there and then have them be displayed and I manage to display the uploaded image but when I refresh the page. The image gets removed, my function userProfileImage() does not seem to update the image, how can I keep the image?
const [image, setImage] = useState(null);
const [url, setUrl] = useState("");
const [progress, setProgress] = useState(0);
const handleChange = e => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
const imagesListRef = ref(storage, "images/");
useEffect(() => {
list(imagesListRef).then((response) => {
response.items.forEach((item) => {
getDownloadURL(item).then((url) => {
setUrl(url);
console.log(url);
});
});
});
}, []);
const handleUpload = () => {
const uploadTask =
storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
"state_changed",
snapshot => {
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
error => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then(url => {
setUrl(url);
});
}
);
userProfileImage();
};
return (
<div className='App'>
<Header/>
<br />
<br />
{progress > 0 && progress < 100 ?
<progress value={progress} max="100" /> : <div></div>
}
<div class="proPic">
<Avatar src={url} sx={{ width: 250, height: 250 }} />
<input type="file" onChange={handleChange} />
<button onClick={handleUpload}>Submit</button>
</div>
</div>
)
}
When you are refreshing, all the components will also be refreshed. As a result all the states will be reinitialized. So when you refresh, url will be null again. So you should retrieve the image when the component is refreshed. You can retrive the uploaded image inside a useEffect hook.
useEffect(()=>{
//retrieve image
},[]}
Related
How can I use Upload component in Ant Design to upload avatar to Firebase?
Here is what I used and tried in React:
// Modal hook
const [modal1Open, setModal1Open] = useState(false);
// Upload new avatar
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState();
const handleAvatarChange = (info) => {
if (info.file.status === 'uploading') {
setLoading(true);
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, (url) => {
setLoading(false);
setImageUrl(url);
});
}
};
const uploadAvatar2fb = async () => {
console.log('new avatar url: ', imageUrl)
try {
await addAvatarUrl2UserDb(location.state.userEmail, imageUrl)
} catch (error) {
console.log(error.message)
}
}
const uploadButton = (
<div>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div
style={{
marginTop: 8,
}}
>
Upload
</div>
</div>
);
return (
{/* change avatar */}
<Button type="primary" onClick={() => setModal1Open(true)}>Change Avatar</Button>
<Modal
title="Upload your new avatar here"
centered
open={modal1Open}
onOk={() => setModal1Open(false)}
onCancel={() => setModal1Open(false)} >
<p>Click below to upload...</p>
<Upload
name="avatar"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
// upload address
action='./images/'
// check format jpg/png check size < 2mb
beforeUpload={beforeUpload}
// set new upload url
onChange={handleAvatarChange} >
{imageUrl ? (
<img
src={imageUrl}
alt="avatar"
style={{
width: '100%',
}}
/>
) : (
uploadButton
)}
</Upload>
</Modal>
)
How do I handle with the action='' in that <Upload ..> tag?
I wrote this trying to make the uploading to firebase
const uploadAvatar2fb = async () => {
console.log('new avatar url: ', imageUrl)
try {
await addAvatarUrl2UserDb(location.state.userEmail, imageUrl)
} catch (error) {
console.log(error.message)
}
}
which called the function in firebase.js
// save avart to users
export const addAvatarUrl2UserDb = async (email, url) => {
const userDocRef = doc(db, 'users', email)
try {
await setDoc(userDocRef, {
avatar: url
})
} catch (error) {
console.log("avatar url upload error:", error.message)
}
}
And when I tried to upload after start npm, I got the error like POST http:localhost:3000/{url of the action} 404 not found. It shows that the address I wrote in <Upload action='...' /> is not found.
How could I solve this and make the uploading works?
I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());
I am trying to organize my code order to handle feed as feed.* based on my endpoint API, but however react doesn't allow me to directly send functions into component, but I want something similar to feed.results, feed. count
const [initialized, setIntialized] = useState(false);
const [feed, setFeed] = useState([]);
const browserFeed = async () => {
const response = await browse();
setFeed(response.results);
setIntialized(true);
};
useEffect(() => {
if (!initialized) {
browserFeed();
}
});
export const browse = () => {
return api.get('xxxxxxxx')
.then(function(response){
return response.data // returns .count , .next, .previous, and .results
})
.catch(function(error){
console.log(error);
});
}
<div className="searched-jobs">
<div className="searched-bar">
<div className="searched-show">Showing {feed.count}</div>
<div className="searched-sort">Sort by: <span className="post-time">Newest Post </span><span className="menu-icon">▼</span></div>
</div>
<div className="job-overview">
<div className="job-overview-cards">
<FeedsList feeds={feed} />
<div class="job-card-buttons">
<button class="search-buttons card-buttons-msg">Back</button>
<button class="search-buttons card-buttons">Next</button>
</div>
</div>
</div>
</div>
If it is pagination you are trying to handle here is one solution:
async function fetchFeed(page) {
return api.get(`https://example.com/feed?page=${page}`);
}
const MyComponent = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
// Fetch on first render
useEffect(() => {
fetchFeed(1).then((data) => setFeed(data));
}, []);
// Update feed if the user changes the page
useEffect(() => {
fetchFeed(currentPage).then((data) => setFeed(data));
}, [currentPage]);
const isFirstPage = currentPage === 1;
return (
<>
<FeedsList feeds={feed} />
{isFirstPage && (
<button onClick={() => setCurrentPage(currentPage - 1)}>Back</button>
)}
<button Click={() => setCurrentPage(currentPage + 1)}>Next</button>
</>
);
};
I have to create component which fetch data with pagination and filters.
Filters are passed by props and if they changed, component should reset data and fetch it from page 0.
I have this:
const PaginationComponent = ({minPrice, maxPrice}) => {
const[page, setPage] = useState(null);
const[items, setItems] = useState([]);
const fetchMore = useCallback(() => {
setPage(prevState => prevState + 1);
}, []);
useEffect(() => {
if (page === null) {
setPage(0);
setItems([]);
} else {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
}, [page, minPrice, maxPrice]);
useEffect(() => {
setPage(null);
},[minPrice, maxPrice]);
};
.. and there is a problem, because first useEffect depends on props, because I use them to filtering data and in second one I want to reset component. And as a result after changing props both useEffects run.
I don't have more ideas how to do it correctly.
In general the key here is to move page state up to the parent component and change the page to 0 whenever you change your filters. You can do it either with useState, or with useReducer.
The reason why it works with useState (i.e. there's only one rerender) is because React batches state changes in event handlers, if it didn't, you'd still end up with two API calls. CodeSandbox
const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [page, minPrice, maxPrice]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const App = () => {
const [page, setPage] = useState(0);
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
</div>
);
};
export default App;
The other solution is to use useReducer, which is more transparent, but also, as usual with reducers, a bit heavy on the boilerplate. This example behaves a bit differently, because there is a "set filters" button that makes the change to the state that is passed to the pagination component, a bit more "real life" scenario IMO. CodeSandbox
const PaginationComponent = ({ tableConfig, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
const { page, minPrice, maxPrice } = tableConfig;
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [tableConfig]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {tableConfig.page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const tableStateReducer = (state, action) => {
if (action.type === "setPage") {
return { ...state, page: action.page };
}
if (action.type === "setFilters") {
return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
}
return state;
};
const App = () => {
const [tableState, dispatch] = useReducer(tableStateReducer, {
page: 0,
minPrice: 25,
maxPrice: 50
});
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
const setPage = useCallback(
page => {
if (typeof page === "function") {
dispatch({ type: "setPage", page: page(tableState.page) });
} else {
dispatch({ type: "setPage", page });
}
},
[tableState]
);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
}}
/>
</div>
<button
onClick={() => {
dispatch({ type: "setFilters", minPrice, maxPrice });
}}
>
Set filters
</button>
<PaginationComponent tableConfig={tableState} setPage={setPage} />
</div>
);
};
export default App;
You can use following
const fetchData = () => {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
// Whenever page updated fetch new data
useEffect(() => {
fetchData();
}, [page]);
// Whenever filter updated reseting page
useEffect(() => {
const prevPage = page;
setPage(0);
if(prevPage === 0 ) {
fetchData();
}
},[minPrice, maxPrice]);
I'm trying to display a image selected from my computer in my web app. I referred the following question which addresses the question i'm trying to fix.
How to display selected image without sending data to server?
I have my html part like this
<div className="add_grp_image_div margin_bottom">
<img src={img_upload} className="add_grp_image"/>
<input type="file" className="filetype" id="group_image"/>
<span className="small_font to_middle">Add group image</span>
<img id="target"/>
</div>
I want to display the selected image in
<img id="target"/>
How can i do this?
I referred FileReader docs as well.
https://developer.mozilla.org/en-US/docs/Web/API/FileReader
Try this
<input type="file" onChange={this.onImageChange} className="filetype" id="group_image"/>
Add method to class
onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
let reader = new FileReader();
reader.onload = (e) => {
this.setState({image: e.target.result});
};
reader.readAsDataURL(event.target.files[0]);
}
}
or you can use URL.createObjectURL
onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
this.setState({
image: URL.createObjectURL(event.target.files[0])
});
}
}
And display image
<img id="target" src={this.state.image}/>
For the hook version:
const [image, setImage] = useState(null)
const onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
setImage(URL.createObjectURL(event.target.files[0]));
}
}
return (
<div>
<input type="file" onChange={onImageChange} className="filetype" />
<img src={image} alt="preview image" />
</div>
)
Recently I came across needing a similar feature. Here is my implementation using hooks.
export default function App() {
const [image, setImage] = React.useState("");
const imageRef = React.useRef(null);
function useDisplayImage() {
const [result, setResult] = React.useState("");
function uploader(e) {
const imageFile = e.target.files[0];
const reader = new FileReader();
reader.addEventListener("load", (e) => {
setResult(e.target.result);
});
reader.readAsDataURL(imageFile);
}
return { result, uploader };
}
const { result, uploader } = useDisplayImage();
return (
<div className="App">
<input
type="file"
onChange={(e) => {
setImage(e.target.files[0]);
uploader(e);
}}
/>
{result && <img ref={imageRef} src={result} alt="" />}
</div>
);
}
I created a custom hook so that it can be reused anywhere in the app. The hook returns the image src and the uploader function.
The image src can then be linked to the <img src={..} /> and then on input change you can just pass in the e to the uploader function.
hope it works for you
<form onSubmit={form => submitForm(form)}>
<input
accept="image/*"
onChange={onImageChange}
className={classes.inputImage}
id="contained-button-file"
multiple
name="image"
type="file"
/>
<label htmlFor="contained-button-file">
<IconButton component="span">
<Avatar
src={mydata.imagePreview}
style={{
margin: "10px",
width: "200px",
height: "200px"
}}
/>
</IconButton>
</label>
<Button
type="submit"
variant="outlined"
className={classes.button}
>
Default
</Button>
</form>
onImageChange
const onImageChange = event => {
if (event.target.files && event.target.files[0]) {
let reader = new FileReader();
let file = event.target.files[0];
reader.onloadend = () => {
setData({
...mydata,
imagePreview: reader.result,
file: file
});
};
reader.readAsDataURL(file);
}
};
submitForm
const submitForm = form => {
form.preventDefault();
const formData = new FormData();
formData.append("image", mydata.file);
// for (var pair of formData.entries()) {
// console.log(pair[1]);
// }
const config = {
headers: {
"content-type": "multipart/form-data"
}
};
axios
.post("api/profile/upload", formData, config)
.then(response => {
alert("The file is successfully uploaded");
})
.catch(error => {});
};