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
Related
I'm currently learning react and came to the following problem.
When I start dragging div I update state writing div's id in it.
useEffect - writes that it's updated.
console.log() before return does the same.
So if I'm not mistaken, it comfirms that state updated. (I used it to debug and to see if state even updates)
But when dropHandler runs, it says that startBlock is ''. So it doesn't contain value.
export function SomePage() {
const [startBlock, setStartBlock] = useState('') # using this state to store id of start div.
useEffect(() => {
console.log("changed to", startBlock)
}, [startBlock])
function dragStartHandler(e) {
setStartBlock(e.currentTarget.parentElement.id);
}
function dropHandler(e) {
e.preventDefault();
console.log('drop', startBlock)
}
console.log(startBlock)
return (
<div draggable dragStartHandler={dragStartHandler} dropHandler={dropHandler}> # simplified doesn't really matter
)
}
I know that useState is async. But as I already said, useEffect printed that value was updated. So I'm quite confused.
The questions are:
Why startBlock in dropHandler doesn't have value?
How can I fix it?
The attributes you're looking for are called onDragStart and onDragEnd. Correct the names and it works properly.
export function SomePage() {
const [startBlock, setStartBlock] = useState('') # using this state to store id of start div.
useEffect(() => {
console.log("changed to", startBlock)
}, [startBlock])
function dragStartHandler(e) {
setStartBlock(e.currentTarget.parentElement.id);
}
function dropHandler(e) {
e.preventDefault();
console.log('drop', startBlock)
}
console.log(startBlock)
return (
<div draggable onDragStart={dragStartHandler} onDragEnd={dropHandler}> # simplified doesn't really matter
)
}
https://codesandbox.io/s/react-playground-forked-od6bcr?file=/index.js
I'm following this example in order to achive dynamically-created elements that can be printed using react To Print.
I have the following code (showing sections to keep this question as clean as possible):
/*This section is loaded after a user has been selected from a select input*/
{rows?.map((row,index) => (
<PrintHojaVacaciones key={index} vacacion={row}/>
))}
const PrintHojaVacaciones = ({vacacion}) => {
const componentRef = useRef();
return (
<div>
{vacacion.id}
<ReactToPrint
trigger={() => {
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}}
content={() => componentRef.current}
/>
<Diseno_PrintHojaVacaciones ref={componentRef} value={vacacion}/>
</div>
)
}
export default PrintHojaVacaciones
const Diseno_PrintHojaVacaciones = React.forwardRef((props,ref) => {
const { value } = props;
return (
<div ref={ref}>{value.aprobadapor}</div>
);
});
export default Diseno_PrintHojaVacaciones;
Thing is, useRef is undefined. I have been trying with CreateRef as well, with the same result. I also tried to "move" my code to the codesandbox above displayed and it works well, but in my own application, it returns undefined. The whole useRef is new to me and I don't understand it well yet, so any help would be appreciated.
The route is being called using Lazy loading from react router (I don't know if this could be the culprit)
I don't know exactly what I did to make it work, I really have no idea, but my Trigger now looks like this and it is now working (not sure if I'm missing something else). The difference is that the function is not inside brackets after the =>.
trigger={() =>
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}
I'm trying to start learning react but fail understanding basic logic.
I have a todo list page, which works fine with a strike-through, but if I try to change the strike through to REMOVE instead, my app disappears on click.
Here's my code, hopefully you can understand:
function Note({ notes, note, onClickSetter }) {
const { input, id } = note
const [strikeThrough, setStrikeThrough] = useState(false);
function onNoteClick(event) {
const { value, id } = event.target
//setStrikeThrough((prev) => !prev) - the strike through which is canceled right now
onClickSetter(prev => prev.filter(aNote => aNote.id !== id)) // why this doesn't work?
}
return (
<>
<h1 style={ strikeThrough ? {textDecoration: 'line-through'} : { textDecoration: 'none' }} id={id} onClick={onNoteClick}>{input}</h1>
</>
)
}
a little explanation on my props:
notes - literally the list of notes which comes from a useState on father component (we shouldn't touch this from my understanding of react)
note - self note information
onClickSetter - the other part of useState, the setter one.
So on another words, I have the notes which holds all notes, and onClickSetter which is in another words is setNotes - both part of useState
on top of that I have a note information, because this is a note component
the father component:
function Body() {
const [Notes, setNotes] = useState([])
return (
<div className='notes-body'>
<NewNote onClickSetter={setNotes}/>
{Notes.map((note) => { return <Note key={note.id} notes={Notes} note={note} onClickSetter={setNotes}/>})}
</div>
)
}
function NewNote({ onClickSetter }) {
const [input, setInput] = useState('')
function onInputChange(event) {
const { value } = event.target
setInput(value)
}
function onButtonClick(event) {
onClickSetter((prev) => {
try {
return [...prev, {input: input, id: prev[prev.length-1].id+1}]
}catch{
return [{input: input, id: 0}]
}
})
setInput('')
}
return (
<>
<Input placeholder="add new note" className='note-text' onChange={onInputChange} value={input}/>
<Button className='btn btn-primary add-note' onClick={onButtonClick} />
</>
)
}
The reason is that event.target.id is a string representing a number since all HTML attributes has the string type. Whilst in your data structure, the ID is a number. So, e.g. "1" vs 1. This can be hard to spot sometimes.
The easiest way to fix this is to add a parseInt to the right place to convert the string to a number:
onClickSetter((prev) => prev.filter((aNote) => aNote.id !== parseInt(id)))
However, I also want to mention (and this is more advanced stuff but I like to get people on the right track :) ) that really, you shouldn't pass the whole setter down into the child component, but instead a callback called something like onRemoveNote that accept the note id and the actual filtering/removal would happen in the parent component.
This would be better placement of concerns. For now though, the above will work and I can help you out on stack overflow chat if needed :).
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..
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.