I've been banging my head against my keyboard for the past 8 hours trying to figure out why each input field is showing different values for the same state.
Here is the code -
const BeaconSettingsCard = (props) => {
const [settingsItems, setSettingsItems] = useState([]);
const handleAddBeaconBtnOnClick = () => {
const id = settingsItems.length;
const newItem = (
<InputItem
id={id}
key={id}
type="InputField"
title="Test Title"
value="Test Value"
onChange={(e) => handleBeaconIdInputFieldOnChange(e, id)}
/>
);
setSettingsItems((settingsItems) => [...settingsItems, newItem]);
};
const handleBeaconIdInputFieldOnChange = (e, id) => {
console.log("settingsItems: ", settingsItems); // each input field shows a different settingsItems value ??
};
let cardHeaderButton = (
<InputItem type="Button" width="150px" onClick={handleAddBeaconBtnOnClick}>
Click to Add
</InputItem>
);
return (
<SettingsCard
headerButton={cardHeaderButton}
settingsItems={settingsItems}
/>
);
};
export default BeaconSettingsCard;
When I log the "settingsItems" state in the onChange event for each input field, I get different values.
On the first dynamically generated inputfield, it logs settingsItems as []. On the second, it logs [{React Component}]. On the third, it logs [{React Component}, {React Component}] and so forth.
These should all be logging the same state value! My friend who is a react wiz couldn't seem to figure this out either. Really hoping someone here can. Thank you.
I solved this strange issue by converting the code from React hooks to a normal React component.
Not ideal, but works. Hopefully if someone runs into a strange issue like this, this solution will work for you too.
Related
Here an example of the problem :
codesandbox.io
export default function App() {
const [hasInputChanged, setHasInputChanged] = useState(false);
let colorList = ["orange", "blue", "yellow"];
function handleChange(e) {
setHasInputChanged(true);
}
const MyLittleInput = () => {
return <input onChange={(e) => handleChange(e)} />;
};
return (
<>
{colorList.map((color) => (
<MyLittleInput key={color} />
))}
</>
);
}
I tried different solutions as defining Keys or using useRef but nothing worked
It's too much code to be debugged easily, but for what I can see on the fiddle, there are serveral things wrong, first of all you are doing really too much things for a simple increment/decrement of a input value. But most important you are defining theyr value using the parametresListe state, but never really changing it wit the setParametresListe function, which should be the only way to safely change controlled form inputs.
Just try to do a bit of cleaning on your code and to use the useState as it is meat to be used
Let us know any updates!
UPDATE:
Having a look at your cleaned code, the problem is that a input inside a component gets builded again and again.
The reason for that, is that each input should have they unique "key" prop, so react can easily understand what input is changed and update only that one.
You have 2 ways to make this work, for the first, I've edited your code:
import "./styles.css";
import React, { useState } from "react";
const DEFAULT_INPUT_STATE = {
orange: "",
blue: "",
yellow: ""
};
export default function App() {
let colorList = ["orange", "blue", "yellow"];
const [inputState, setInputState] = useState(DEFAULT_INPUT_STATE);
const handleChange = (e) => {
const { name, value } = e.target;
console.log(name);
setInputState({
...inputState,
[name]: value
});
};
return (
<>
{colorList.map((color, i) => (
<input
key={color}
name={color}
value={inputState[color]}
onChange={(e) => handleChange(e)}
/>
))}
</>
);
}
As you can see, I've just removed the component for the input and did a bit of other changes, but If you still want to use a component, you can moove all the .map function inside of it, but there's no way to create the input inside a component if it is in a .map function
There is too much code, difficult to follow through, in your example. In the nutshell, I see in dev tools, when I update an input, the entire example component is re-rendered, thus all input elements got destroyed and replaced by newly created ones, without focus. It must be just a bug in your code: once an input is updated it renders different stuff, instead of just changing the input value. But it is beyond something someone here would debug for you for free :D
Using ReactJS, Firestore - Firebase v9.
I have an autocomplete search bar on my web, built with just pure React. The question that I am trying to solve for at least one month is, if I can make this autocomplete input work with Firestore - (user type E.g 'elepha', auto suggestion appears with offer with word elephant - this is what I coded, and with same case, user will click on the suggestion of elephant, and this word elephant will be send to Firestore.)
Cuz there is not any solution on internet, I wonder, if my question is even possible to make, or not.
My simple autocomplete bar code - (animals_data stands for file with animals names)
and I tried to add onClick={pleaseSend} which is basic addDoc function, but when I click on the suggestion, in Firestore will only appear blank input "".
<SearchBar data={animals_data} />
And the filtering code:
function SearchBar({ placeholder, data }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [newAnswer, setAnswer] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "Answers")
const createUser = async () => {
await addDoc(usersCollectionRef, {name: newAnswer}).then(()=>{
window.location.reload()
}).catch((err)=>{
console.log(err)
})
};
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = data.filter((value) => {
return value.full_name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
};
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
};
return (
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
{filteredData.length !== 0 && (
<div className="dataResult">
{filteredData.slice(0, 15).map((value, key) => {
return (
<a className="dataItem" onClick={createUser} target="_blank">
<p>{value.full_name} </p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
EDIT 1: added code screenshots
web:
Thank you very much for reading.
after analyzing your code, I notice that you don't update the value newAnswer using its setter. Therefore, you should use the setter to update the state on user click and then add the firestorm document. You can do that by either using a button/unmodifiable input field in instead of an anchor tag to store the value of each option, then use this value inside the click handler to update the state and then use a useEffect to update firestore every time the state changes. Let me know if you need help with some code. Please post it below your original question as an edit without changing it.
I'm trying to create a simple CRUD application with react and Firestore (new to both).
I never used react with db, so previously I would simply set the state of title and content to "" after submit, but that's create a loop with Firestore onSnapshot. I could wrap it in form and use preventDefault(), but (possibly my mistake) it created strange outcome unless I left behind async. I'm thinking about this issue for a few days and yet to find the right answer to it. Maybe I could prevent Firestore to accept and save empty strings? That cant be a reasonable answer as it would create an extra loop front to back for no other reason than my shortcoming. I think the answer should be some sort of a conditionally state setting, but that's just a guess again. Now I`ve watched quite a few hours a tutorials on youtube, but they all leave out this issue, which I think its a bit silly. I would like to find the answer to it as even though I might could come up with something that will work, but I really like to see the common use case of it which defo will be more elegant than my trial and error solution. Thank you in advance!
function App() {
const [newTitle, setNewTitle] = useState("")
const [newContent, setNewContent] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "users")
useEffect(
() =>
onSnapshot(collection(db, "users"), (snapshot) =>
setUsers(snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })))
),
[]
);
const createEntry = async () => {
await addDoc(usersCollectionRef, { Title: newTitle, Content: newContent })
}
return (
<div>
<input type="text" placeholder="Title" onChange={event => { setNewTitle(event.target.value)} }/>
<textarea type="text" placeholder="Content" onChange={event => { setNewContent(event.target.value) } }/>
<button onClick={createEntry}> Add </button>
{users.map((user) => {
return (
<div className="note">
<h1> {user.Title} </h1>
<p>{user.Content}</p>
</div>
)
})}
</div>
);
}
Took me forever to figure out what Im doing wrong, but seems like it finally I overcome it. Now took my answer with a pinch of salt as Im hoping it will be helpful for you, but might not all my "theories" will be 100% correct. (PS: changed users to notes as it makes a lot more sense)
While I was studying I remembered a project which exactly the same as this, but without firestore backend. I reused that project and done a lot of trial and error to find the solution.
I did remember that on that project it was broken down to components and we passed back the relevant info to the app.jsx. But while i was thinking i realised that we actually done more than that.
I think might be the issue that here I used 3 different states.
const [newTitle, setNewTitle] = useState("")
const [newContent, setNewContent] = useState("")
const [users, setUsers] = useState([]);
Now its all nice and functional, but uncontrolled I believe? Now what we have done in the previous project that we actually stored the individual user entries in and object and passed back the id`s to the app.js. While we were doing it we reset the state of the object fields to "".
const [note, setNote] = useState({
title:"",
content:""
});
I think might be the reason for this to work now that we have objects in an array, not individual strings seemingly not related to each other. ? Its a guess from my side.
app.jsx
const [notes, setNotes] = useState([]);
const usersCollectionRef = collection(db, "notes")
const createEntry = async (note) => {
await addDoc(usersCollectionRef, { title: note.title, content: note.content })
}
return (
<div>
<CreateArea onAdd={createEntry} />
{notes.map((noteItem) => {
return (
<Note
key={noteItem.id}
id={noteItem.id}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
</div>
);
}
createArea.jsx
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(event) {
props.onAdd(note);
setNote({
title: "",
content: ""
});
event.preventDefault();
}
return (
<div className="create-area">
<form>
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows="3"
/>
<button onClick={submitNote}>Add</button>
</form>
</div>
);
}
By doing this I use the spread operator to handle the value changes and until it is submitted it wont be reset to "". I think the issue might have been that the i would have needed 3 state to do this initially.
first state: ""
second state: handles the input changes
third state: submitting and reset value to ""
Doing this with my original states wouldnt be logically possible i think so one extra step needed. On submission it sends back the note where we have access to its fields to save it. Its probably not a biggie for some, but Im happy that i find solution on my own, although it took me longer than i expected. I hope it can be helpful for someone. Please note that all the "ideas" why it didn`t work are ideas only. If you have anything to add or correct me you are more than welcome to! Thank you!
I'm new to React, been working on it for the past week. I'm trying to make a simple app that has a 'product create' form and a list of products with a search bar (using Input component from antd); in the list I can click on any product to open the details page.
Right now I'm blocked by some not properly working logic or something I miss. When I tried the Input onChange with an Array I created in the code it worked fine, but now that I'm using a mock api (from fakestoreapi.com to be precise), I can't make it work.
ProductsList.tsx
function ProductsList() {
const [list, setList] = useState<Array<Product>>([]);
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => { // I think something is wrong here
ProductService.getAll()
.then((res: any) => {
setList(res.data);
setLoading(false);
})
.catch((e: Error) => console.log(e));
}, []); // tried: 'query' const from state, 'filterList' from state
function onChange(e: React.ChangeEvent<HTMLInputElement>) { // Or here (or both here and in useEffect)
console.log('in onChange');
const filterList: Array<Product> = list.filter((item) =>
item.title.toLowerCase().startsWith(e.target.value.toLowerCase())
);
setList(filterList);
}
return (
<div>
<Spin spinning={loading}>
<List
header={
<Input
type="text"
placeholder="Search product"
allowClear
onChange={onChange}
/>
}
split
dataSource={list}
renderItem={(item) => (
<List.Item key={item.id}>
<Link to={`/products/${item.id}`}>{item.title}</Link>
</List.Item>
)}
></List>
</Spin>
</div>
);
}
export default ProductsList;
I tried adding some dependencies to the useEffect hook, but maybe they were the wrong ones. As I said, with a local array this worked, but now after loading the full list once, when I get to the Input and search something, the list is deleted. I think I spotted the problem in the fact that I don't reset the list to the full one, but I don't actually know how to do that (that's why I'm here). I tried to search something online but except for dependencies, I didn't find something specific to help me.
If needed, here is the ProductService.getAll() function:
function getAll() { // http is axios
return http.get<Array<Product>>(`/products`);
}
I'll be glad to add everything that could be helpful if needed.
const [list, setList] = useState<Array<Product>>([]); // The full list
const [filteredList, setFilteredList] = useState<Array<Product>>([]); // the list you display
function onChange(e: React.ChangeEvent<HTMLInputElement>) { // Or here (or both here and in useEffect)
console.log('in onChange');
const temp: Array<Product> = list.filter((item) => //keep the filter on the full list but only display the filtered list
item.title.toLowerCase().startsWith(e.target.value.toLowerCase())
);
setFilteredList(temp);
}
//the datasource: dataSource={filteredList}
I'm still unexperienced with react so that even after searching for a solution and finding some pointers I still cant grasp what the problem is or how to solve it.
I have a Component that renders a list of images. It also contains a search input. I copy the search input onChanged to the state. If onKeyPressed is the return key or when the search button is pressed, that text is again copied from state to the state.searchTerm. The search itself is an effect that watches for changes in searchTerm an then executes a search, updating the list of images. However I feel like, because I change the state with every onChange in the search input, I trigger a re-render of the entire component including the list of images which is just annoying. How can I get rid of this?
I tried to shrink my styled and dynamic code to a minimal working version. What would be the best way to solve this? Would it help to split list and search into separate components with individual state with the parent just holding the list of assets, passing it to the list child and the search child getting a reference to onSearch?
But then I just move the problem since if the search child re-renders, the parent will as well, right?
function AssetListTool ({}) {
const [assets, setAssets] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [searchText, setSearchText] = useState('');
const params = {
limit: 30,
sort: 'title:desc',
searchTerm
};
const Asset = memo(function ({asset }) {
return <>
<div className="asset">
<img src={asset.thumbnail} />
</div>
</>;
});
useEffect(() => {
const matchingAssets = assetApi.getAllAssets({...params });
Promise.all([matchingAssets],
).then(responses => {
setAssets(assets.concat(responses[0].items));
});
}, [searchTerm]);
const onSearchTextChanged = useCallback((event) => {
setSearchText(event.target.value);
}, [searchText]);
function onSearchKeyPressed(event) {
if (event.key === 'Enter') {
onSearch();
}
}
function onSearch() {
setAssets([]);
setSearchTerm(searchText);
}
return (
<>
<div>
<div>
<input onChange={onSearchTextChanged} onKeyPress={onSearchKeyPressed} value={searchText}
type="text"/>
</div>
<div>
<button onClick={onSearch}>
<Icon icon={faSearch}/>
</button>
</div>
</div>
<div>
{assets && assets.length > 0 &&
<div>
{assets.map((asset) => <Asset asset={asset}/>)}
</div>
}
</div>
</>
);
}
export default AssetListTool;
Ah, should've searched just a little more and I was on the right track... The solution is to not touch the state for the text input and instead use a reference to it which is then read for the search as was explained here:
Getting input values without rerender
Thought about a local variable but that didnt work and using event.target.value was always missing the last input. So ref is the trick here..