Cannot map through array of files/images React - arrays

I am creating an image(s) upload component in React. Every time the file input field changes I am updating the images state. Below the input field I am trying to loop through the images and log their names in the console. However, as soon as I select an image, I get the error TypeError: images.map is not a function.
Here is my code:
import React, { useState } from 'react';
const ImagesInput = (props) => {
const [images, setImages] = useState([]);
const imageInputChange = (e) => {
setImages(e.target.files)
}
return (
<div>
<input {...props} type="file" onChange={imageInputChange} multiple />
{images.map((image) => {
console.log(image)
})}
</div>
)
}
export default ImagesInput;

The value of e.target.files is an array-like not an actual array. Just convert it to array before passing to setImages, like
setImages([...e.target.files])
Read here about array-like

Before
setImages(e.target.files)
Try to add a condition like this, make sure e.target.files is the image arr you want
if(e.target.files){
setImages(e.target.files)
}

Related

How to add searchinputs as array in localStorage with React TypeScript

I'm trying to make a search bar, where the search will occur once the button (or enter) is clicked. To this, I want to save the searched phrases in localStorage.
I have no problem with the first part. Things work fine when searching with a button or on enter-click. However, when I try to add the search as an array to localStorage I keep getting issues (like string doesn't work with an array, and many more. I've tried A LOT of different things). I've done this with JS and vanilla React, but never with TS.
In the provided code, the search works, and to put in at least something (as simple as I could) - the latest value is also stored in localStorage.
The code can be found here
Or here:
// index file
import { useState } from "react";
import { Searchbar } from "./searchbar";
import { SearchContainer } from "./style";
export const Search = () => {
const [search, setSearch] = useState<string>("");
return (
<SearchContainer>
<Searchbar setSearch={setSearch} />
<p>SEARCHED: {search}</p>
</SearchContainer>
);
};
// search bar file
import { useRef, KeyboardEvent, useEffect } from "react";
type SearchProps = {
setSearch: React.Dispatch<React.SetStateAction<string>>;
};
export const Searchbar = ({ setSearch }: SearchProps) => {
// with useRef we don't have to reload the page for every input
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
setSearch(String(inputRef.current?.value));
localStorage.setItem("form", JSON.stringify(inputRef.current?.value)); // Issue here
};
// Enable search with the enter-key
const log = (e: KeyboardEvent): void => {
e.key === "Enter" ? handleClick() : null;
};
useEffect(() => {
const value = localStorage.getItem("form");
value ? JSON.parse(value) : "";
}, [setSearch]);
return (
<div>
<input
type="text"
autoComplete="off"
ref={inputRef}
placeholder="search game..."
onKeyDown={log}
/>
<button onClick={handleClick}>SEARCH</button>
</div>
);
};
I've been on this for a while now so any help is appreciated. (I am also trying to learn TS from scratch, but until I get to here...).

React File Upload

I just want to create simple file upload component on react. It will be something simple that after select the file and submit the upload button, it will give a massage with filename like "You uploaded fileName.txt". I have tried search so many sources but it's mostly more than I need, that would be great if anyone could help me about it. Thank you!
You can detect file selection using the onChange event listener, and you can access the name of the selected file via e.target.files which returns all of the files which have been selected, we'll take the first selected file and store its name value inside a state called selectedFileName.
during render we'll check if the selectedFileName state has value, we'll display it inside a p tag.
import React, { useState } from 'react';
export const App = () => {
const [selectedFileName, setSelectedFileName] = useState('');
const [displayFileName, setDisplayFileName] = useState(false);
const submitFile = () => setDisplayFileName(true);
const handleFileSelect = e => {
const selectedFile = e.target.files[0];
setDisplayFileName(false);
setSelectedFileName(selectedFile?.name);
};
return (
<div>
<input
type='file'
accept='*'
name='fileSelect'
onChange={handleFileSelect}
/>
{displayFileName ? (
<p>{ selectedFileName?`You Uploaded '${selectedFileName}'`:"Please select a file!"}</p>
) : (
<button value='Submit' onClick={submitFile}>
Submit File
</button>
)}
</div>
);
};

Why is my component failing to run when I call it?

I am struggling to find why my component is not responding to being called by its parent. I am trying to integrate Cloud Firestore with code that previously ran using Redux. My first goal is to populate my List with data from Firestore.
Here are my (simplified) components in question:
// List.js
import React, { useEffect, useState } from "react";
import db from "../../db";
import { onSnapshot, query, collection, orderBy } from "firebase/firestore";
import TaskItem from "./TaskItem";
const List = () => {
const [taskList, setTaskList] = useState([]); // Currently assumes DB never empty, populates on initial render
const [isInitialRender, setIsInitialRender] = useState(true);
// Firestore
const ref = collection(db, "Tasks");
const q = query(ref, orderBy("listIndex"));
useEffect(() => {
// Execute only on initial render
if (isInitialRender) {
// Populate task list
onSnapshot(q, (querySnapshot) => {
setTaskList(() => querySnapshot.docs)
}, (error) => {
console.log(error)
})
};
setIsInitialRender(() => false);
}, []);
return (
<>
<h2>List</h2>
{taskList.forEach((task) => ( // console-logging `task` here will output correct data
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
))
}
</>
);
};
export default List;
// TaskItem.js
import React from "react";
const TaskItem = (props) => {
console.log('This will not print')
const submitHandler = () => console.log('Submitted');
return (
<form onSubmit={submitHandler}>
<input
autoFocus
type="text"
/>
</form>
);
};
export default TaskItem;
I have tried:
Populating the state with the data from each document (rather than assigning it directly), then passing the contents as props. This led to (I believe) an infinite loop, and ideally I would like to pass the actual DocumentReference to the TaskItem anyways. So this was a bust for me.
Returning [...querySnapshot.docs], or even (prev) => prev = [...querySnapshot.docs] in the state setter. No response from TaskItem().
Decomposing the taskList state into a new dummy array, and using that array to populate the props for TaskItem.
I know that the task data is being fetched successfully because I can satisfactorily log taskList's contents from the map function in List's return statement. But it seems like TaskItem() never runs.
Does anyone see my error here?
edit: sorry I assumed you were using map. I'm not sure why your forEach isn't working but map would work, from my example
edit 2: you probably are looking to use map because you want to transform every element in the array: JavaScript: Difference between .forEach() and .map()
you forgot to return something from the map, and maybe need {} instead.
try
{taskList.forEach((task) => {
return (
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
)
})

state not changing on onChange event in react?

This is my code:
function AddPost() {
const [file, setFile] = useState({})
const handleChange = (event) => {
setFile(event.target.files[0]);
console.log(file);
}
return (
<div>
<TextField type='file' onChange={handleChange} label='image' variant='outlined' />
</div>
)
}
I am not getting file info. on console, while i am selecting a file . Instead of that I am getting empty object why ?
You need to use a useEffect to besure you are doing your action after a state is updated. Here is an example :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
const [num, setNum] = React.useState(0);
const handleClick = () => {
setNum(1);
console.log('num =', num);
}
// Note the dependency array below
React.useEffect(() => console.log('num (useEffect) = ', num), [num]);
return (
<div>
<button onClick={handleClick}>Click</button>
</div>
);
};
render(<App />, document.getElementById("root"));
and here is repro on Stackblitz.
Here, on click, num will be 0 in the function, but it will be set to 1 in the useEffect.
Just the file is not updated yet (whitin onChange) , so you see the initial state
The state update is performed asynchronously and only visible in the next render call.
In the next render call, file will be updated to the new value and then your (also new) function handleChange will use that value.
This means in your current code the log will always be off by one (empty object, file 1, file 2 etc.) when uploading one file after each other.
The behavior is described in the documentation of the useState hook.
To fix the log, simply write
const newFile = event.target.files[0];
setFile(newFile);
console.log(newFile);

Updating array using react hooks

I am making an application using the Upsplash API.
Upon rendering I want to display 30 images, witch works correctly.
import React, { useState, useEffect } from "react"
const ContextProvider =({ children }) =>{
const [allPhotos, setAllPhotos] = useState([])
const [cartItems, setCartItems] = useState([])
const [imageQuery, setImageQuery] = useState('')
useEffect(() => {
const url = `https://api.unsplash.com/photos?page=5&per_page=30&client_id=${process.env.REACT_APP_UNSPLASH_KEY}`
async function getPhotos() {
const photosPromise = await fetch(url)
const photos = await photosPromise.json()
setAllPhotos(photos)
}
getPhotos()
},[])
I then pass AllPhotos to my Photos.js using my context, and map over allPhotos, passing the photo to my Image component to display information about the image.
import React, {useContext} from "react"
import {Context} from "../Context"
function Photos(){
const {allPhotos} = useContext(Context)
const imageElements = allPhotos.map((photo,index) =>(
<Image key={photo.id} photo={photo}/>
))
return(
<>
<main>
{imageElements}
</main>
</>
)
}
export default Photos
const Image = ({ photo }) => {
return (
<div
<img src={photo.urls.thumb} className="image-grid" alt="" />
</div>
)
}
From here the images from the API display and everything is working correctly.
What I want to do now is add a search query, where the users can search for certain images.
I made a component for the input value
import React, { useContext } from "react"
import {Context} from "../../Context"
const QueryInput = () =>{
const {imageQuery, setImageQuery, SearchImage} = useContext(Context)
return(
<form onSubmit={SearchImage} >
<label>
Search Photos
<input
type="text"
className="query-input"
placeholder="Search Images"
value={imageQuery}
onChange={(e) => setImageQuery(e.target.value) }
/>
</label>
<button type="submit">Search Image</button>
</form>
)
}
export default QueryInput
I made a searchQuery function in my context
const SearchImage = async (e) =>{
e.preventDefault()
const queryUrl = `https://api.unsplash.com/search/photos?
age=5&per_page=30&query=${imageQuery}&client_id=${APP_KEY}`
const response = await fetch(queryUrl)
const queryPhotos = await response.json();
setAllPhotos(prevState => [...prevState, ...queryPhotos])
}
Everything works so far, I can console.log(queryPhotos) and get the users images of the query they searched for. If I search for "stars" I will get a bunch of images with stars.
What im having trouble doing is mapping through allPhotos again and displaying the query search images.
The error im having is
TypeError: queryPhotos is not iterable
I have been at this for awhile. Any information/advice would be greatly appreciated. Any questions about the code or need additional information I can provide it. THANK YOU.
In short.
queryPhotos is not an array.
unsplash api response for api /photos and /search/photos is a bit different. One return an array, while the other is an object, you need to access photos in results
So, change this line from
setAllPhotos(prevState => [...prevState, ...queryPhotos])
to
setAllPhotos(prevState => [...prevState, ...queryPhotos.results])
Should fix your problem.

Resources