I'm working on a small react project, and I'm trying to map over an array, and display the names (in react).
I've been trying to find a solution to this error (.map is not a function), and the only suggestion I found was adding the index as a key. It doesn't seem to be solving it, any help would be greatly appreciated!
Your groups is not an array hence the .map is not a part of the groups. I tried your code sandbox and the groups is having following value.
3 people should be one group!
Check how is your groups being set.
for the workaround until you fix your group value you can do something like this.
import React, { useState } from "react";
import { shuffleArray, createGroups } from "./utils";
import "./styles.css";
//TODO groups should be at least 3 people
//MAYBE also add option people per group
const App = () => {
const [names, setNames] = useState("");
const [numGroup, setNumGroup] = useState(2);
const handleNames = (e) => {
e.preventDefault();
};
const shuffledArr = shuffleArray(names.split(" "));
const groups = createGroups(numGroup, shuffledArr);
return (
<div className="App">
<h1>Coffee Groups</h1>
<form className="form" onSubmit={handleNames}>
<textarea
name="listOfNames"
type="text"
value={names}
onChange={(e) => setNames(e.target.value)}
/>
<div className="button">
<label>groups</label>
<input
min="2"
className="numInput"
type="number"
value={numGroup}
onChange={(e) => setNumGroup(e.target.value)}
/>
<button type="submit" onClick={() => console.log(groups)}>
Run
</button>
</div>
</form>
<div>
{Array.isArray(groups) && groups.map((group, idx) => {
return (
<ul key={idx}>
<li>Group {idx + 1}</li>
{group.map((person, idx) => {
return <li key={idx}>{person}</li>;
})}
</ul>
);
})}
</div>
</div>
);
};
export default App;
On codesandbox, if you hover over line 16 const groups = createGroups(numGroup, shuffledArr); you'll see that groups is any[] | "3 people should be one group". Since in some cases groups is not an array, line 43 JS complains that .map() does not exist on a string.
You can fix this by making createGroups only returning an array, and then instead of groups? try groups.length >= 3?
Related
I have three number inputs:
<input type='number'>number1</input>
<input type='number'>number2</input>
<input type='number'>number3</input>
I wish to store the state as an array of objects so it looks like the following after a user has entered three numbers:
[{ par: number1 }, { par: number2 }, { par: number3 }]
I also want the user to be able to edit a number so if the user changes his/her mind, they can simply edit an existing number which will edit the corresponding value in the state.
My understanding is that when you want to add elements to the state you use the spread operator and when you want to edit elements in the state you use the map function. I know how to implement each way of updating state but I am not sure how to determine on the change of an input, if the state should be added to or edited. I guess I need a way of determining that on each input change? but I am not sure how to do that.
If you are just asking how to implement an onChange handler for three inputs that update one of three elements in an array then I suggest passing an index as well as the value you want to update.
An example:
const [state, setState] = React.useState([
{ par: 0 },
{ par: 0 },
{ par: 0 }
]);
// Curried function to close over index in callback scope
const changeHandler = index => e => {
const { value } = e.target;
setState(state => state.map(
(current, i) => i === index ? { par: Number(value) } : current
);
};
return (
<>
{state.map(({ par }, i) => (
<label key={i}>
number {i + 1}
<input
type="number"
value={par}
onChange={changeHandler(i)}
/>
</label>
))}
</>
);
Here is the sample code of the above problem. This code will help you to solve your issue.
import React, {useState} from "react";
import "./App.css";
function App() {
const [data, setData] = useState([
{par: 0}, {par: 0}, {par: 0}
])
const onChange = (elem, val) => {
const tempData = [...data]
tempData[elem] = {
par: Number.parseInt(val)
}
setData(tempData)
}
return (
<div className="App">
<div>Number 1
<input type='number'
onChange={(e) => onChange(0, e.target.value)}/>
</div>
<div>Number 2
<input type='number'
onChange={(e) => onChange(1, e.target.value)}/>
</div>
<div>Number 3
<input type='number'
onChange={(e) => onChange(2, e.target.value)}/>
</div>
</div>
);
}
export default App;
I was thinking about how to code TailwindCSS cleaner in React. Since Tailwind is utility-first, it makes us inevitably end up with components (ex: className="w-full bg-red-500"). So, I tried to create a utility like this:
utils/tailwind.ts
const tw = (...classes: string[]) => classes.join(' ')
and call it inside:
components/Example.tsx
import { useState } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={tw(
'w-full',
'h-full',
'bg-red-500'
)}
>
hello
</div>
</div>
)
}
But, it will cause tw() to be re-called as always as text state is updated.
So, I decided to wrap tw() function using useMemo to prevent re-call since the tw() always returns the same value. But the code is like this:
import { useState, useMemo } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}
Is it correct or good practice if I put useMemo like that? Thank you 🙏 .
Is it correct or good practice if I put useMemo like that?
Short answer - yes.
Long answer - it depends. It depends on how heavy the operation is. In your particular case, joining a couple of strings may not be such heavy calculation to make the useMemo worth to be used - it's good to remember that useMemo memoizes stuff and it takes memory.
Consider example below. In the first case, without useMemo, the tw function will be called with every App re-render, to calculate new className. However, if useMemo is used (with empty dependency array), tw will not be called and new className will not be calculated even if the App re-renders, due to the basic memoization. It will be called only once, on component mount.
Conclusion - it's a good practice to use useMemo, but rather for heavy operations, like mapping or reducing huge arrays.
export default function App() {
const [_, s] = useState(0);
return (
<div className="App">
<div className={tw(false, 'w-full', 'h-full', 'bg-red-500')}>div1</div>
<div
className={useMemo(
() => tw(true, 'w-full', 'h-full', 'bg-red-500'),
[],
)}
>
div2
</div>
<button onClick={() => s(Math.random())}>re-render</button>
</div>
);
}
Playground: https://codesandbox.io/s/distracted-liskov-tfm72c?file=/src/App.tsx
The issue here is that React will re-render the component every time it's state changes. (each time you setText).
If you want to prevent that from happening, then see if you really need this re-render hence what do you really need the input text for?
you do not HAVE to use state here to use the input value.
you could call another function on change which will not update the state, and use the input value there for whatever you need.
for example:
const Example = () => {
const onInputChange = (e) => {
const text = e.target.value
// do something with text
}
return (
<div>
<input onChange={(e: any) => onInputChange(e)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}
Please review my code first.
const test = () => {
const [files, setFiles] = useState ([]);
//I think I have to edit following statement.
const handleFile = (e) => {
const newFiles = []
for (let i=0; i < e.target.files.length; i++) {
newFiles.push(e.target.files[i])
}
setFiles(newFiles)
};
return (
{files.map((file, index) => (
<div>
<div key={index}>
<p>
{file.name}
</p>
<Button size='small' onClick={() => {deleteSelectedFile(file.name)}}>
Delete
</Button>
</div>
</div>
))}
<div>
<label onChange={handleFile}>
<input type='file' multiple />+ Attach File
</label>
</div>
)
}
with handleFile statement, I can appropriately get the files.
However, when I upload one file at a time and then upload one file again,
the file is not added. the file replaces file.
There is not problem when I upload multiple time at once.
For example, I upload 'hello.jpg', and it renders well in the screen. Then, I upload 'goodbye.jpg', it renders well, but 'goodbye.jpg' replaces 'hello.jpg'.
Therefore, what I see is just 'goodbye.jpg' [button], not
'hello.jpg' [button]
'goodbye.jpg' [button]
.
I want my files to stacked up, without replacing.
I need some wisdom!
In my opinion, you only need to spread the prev state values and the files during the change event.
import { useState } from "react";
export default function App() {
const [files, setFiles] = useState([]);
const handleChange = (e) => {
// This is what you need
setFiles((prev) => [...prev, ...Object.values(e.target.files)]);
};
return (
<div>
<label onChange={handleChange}>
<input type="file" multiple />
</label>
{files.map((file) => {
return <p>{file.name}</p>;
})}
</div>
);
}
how about you don't create a new variable for new files, you just set the state for the files since it's an array
setFiles(oldFile => [...oldFile,e.target.files[i]]);
If it's possible you can drop a codepen link
recently in my work we are using react with typescript.. And seriously I'm so newbie and I feel really dummy to make any functions.. with it.. But in my free time, I try to learn typescript more.. I'm a little bit confused with hooks in typescript.. Somebody could let me know how to map an object in an object array? I've tried many ways and even checked on stackoverflow similar issues, but I couldn't solve mine..
E.g. usually if I'm using ES6+ I feel really comfortable so in this case, I'd rather show you what I want to get. If I get this easy example I think I will understand typescript more.
const App = () => {
const [data, setData] = data([]);
const [query, setQuery] = query("");
const handleSubmit = (e) => {
e.preventDefault();
if (query) {
const newItem = {id: new Date().getTime().toString(), query};
setData([...data, newItem]);
}
}
return (
<section className="section">
<form onSubmit={handleSubmit}>
<div className="form-control">
<label forHTML="input">Add new</label>
<input type="text" id="input"/>
<button type="submit">Add</button>
</div>
</form>
<article className="someData">
{data.map(item => {
return <div className="box">
<h2>{item.title}</h2>
</div>
})}
</article>
</section>
)
}
I know this example has not much sense, but If I'd know how to write it in typescript I could get know-how typescript works like. I've read the documentation and All the time I want to map my object I always type it as Array or object or even as an "any" and nothing does work :( I'm just curious how this function written in ES6 could look like in typescript..
import React, { useState } from 'react';
interface Data {
id: string;
query: string;
}
const App: React.FC<{}> = () => {
const [data, setData] = useState<Data[]>([]);
const [query, setQuery] = useState<string>('');
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
if (query) {
const newItem: Data = { id: new Date().getTime().toString(), query };
setData([...data, newItem]);
}
};
return (
<section className="section">
<form onSubmit={handleSubmit}>
<div className="form-control">
<label forHTML="input">Add new</label> {//TS Error, invalid prop forHtml}
<input type="text" id="input" />
<button type="submit">Add</button>
</div>
</form>
<article className="someData">
{data.map((item: Data, i: number) => {
return (
<div key={i} className="box">
<h2>{item.title}</h2>{ // TS Error no title in data}
</div>
);
})}
</article>
</section>
);
};
export default App;
Code is in app component (I know that's bad) help would be greatly appreciated. I don't know what I'm doing. Everything works at least except for delete button, trying to remove an item from the list after its been added and can't figure it out. They're asking me to add more details but I don't really know what else to say.
import React, {useState, useEffect} from 'react';
import './App.css';
function App() {
const [memories, setMemories] = useState([]);
const [newItem, setNewItem] = useState('');
const dateAndTime = new Date();
const dateString = dateAndTime.toLocaleDateString();
const [allowSubmit, setAllowSubmit] = useState(false);
const handleSubmit = (evt) => {
evt.preventDefault();
if (allowSubmit === true) {
setMemories([...memories,{
id : memories.length,
value : newItem,
date : dateString
}]);
setNewItem('');
setAllowSubmit(false);
}
}
const clearAllMemories = () => {
setMemories([]);
localStorage.setItem('memories',JSON.stringify(memories));
}
useEffect(() => {
const memoriesData = localStorage.getItem('memories');
if (memoriesData) {
setMemories(JSON.parse(memoriesData));
}
},[]);
useEffect(() => {
localStorage.setItem('memories',JSON.stringify(memories));
});
return (
<div className='App'>
<h2>Favorite moments of the day</h2>
<form onSubmit={handleSubmit}>
<textarea className='text-area'
type='text'
value={newItem}
onChange={e => {
setAllowSubmit(true);
setNewItem(e.target.value)
}}
/>
<input className='input-button'type="submit" value="Add Memory"/>
</form>
<h3 className='memories-title'>Memories</h3>
<div className='memories'>
<ul className='ul'>
{memories.map(memories => (
<li key = {memories.id}>{memories.date}{memories.value}
<button onClick={() => {
setMemories(memories.value.split(memories.value, 1));
}}>delete</button>
</li>
))}
</ul>
</div> <br/>
<button className='clear-button'
onClick={clearAllMemories}
>
Clear All Memories
</button>
</div>
);
}
export default App;
I'm seeing something that could be the problem.
<ul className='ul'>
{memories.map(memories => (
<li key = {memories.id}>{memories.date}{memories.value}
<button onClick={() => {
setMemories(memories.value.split(memories.value, 1));
}}>delete</button>
</li>
))}
</ul>
When using the map function with the memories array, you're assigning the array's name to the array element. And then you access memories.value.split (even though it still doesn't much sense) on the onClick callback of the button, wanting to access the array, but probably accessing the array element.
You should give the array element a different name, like memory or memoryElement, to avoid confusion.
When calling the split function (which from looking to the arguments I think you meant to write splice) you would call it directly in the memories array, not memories.value.
I don't know if I have understood the problem well, but this could be a solution?
<ul className='ul'>
{memories.map(memory => (
<li key = {memory.id}>{memory.date}{memory.value}
<button onClick={() => {
setMemories([...memories.filter(m => memory.id !== m.id)]);
}}>delete</button>
</li>
))}
</ul>