I am practicing a todo list project using React 16 Hooks. However, I found it's hard to get the index using inside map() function like this:
Parent Todo Component:
const Todo = () => {
const dispatch = useDispatch();
const { list } = useSelector(state => state.todoReducer.toJS());
const [value, setValue] = useState('');
function handleOnchange (e) {
setValue(e.target.value)
}
function handleAddItem() {
actionCreators.addItem(dispatch, value);
setValue('');
}
function handleRemoveItem(index) {
// if use handleChecked(index) will trigger handleItemChecked and printed all //indexes everytime
actionCreators.removeItem(dispatch, value);
}
function handleItemChecked(index) {
console.log(index)
}
return (
<>
<input type="text" value={value} onChange={handleOnchange} />
<button onClick={handleAddItem}>+</button>
<List items={list} handleClick={handleRemoveItem} isCompeted={false} handleChecked={handleItemChecked}/>
</>
)
}
Child Component:
const List = ({items, handleClick, isCompleted, handleChecked}) => {
return (
<ListWrapper>
{items && items.length > 0 && items.map((item, index) => {
return (
<ListWrapper.item key={`${item}${new Date()}${Math.random()}`}>
{/* if like this: onClick={handleChecked(index)} will cause the issue */}
{/* <li>{item}<input type="checkbox" onClick={handleChecked(index)}/></li> */}
<li>{item}<input type="checkbox" name={index} onClick={e => handleChecked(e.target.name)}/></li>
<button onClick={handleClick}>-</button>
</ListWrapper.item>
);
})}
</ListWrapper>
)
}
I found if in the child component: List, if I need to get the index of item, I have to assign name={index} . If using handleChecked(index) directly, will cause rendering many times issue in its parent component(Todo). Is any better way to handle this case? Thank you so much in advanced!
as commented by jonrsharpe:
<button onClick={handleClick}>-</button>
here's 2 known methods to fix it:
<button onClick={() => handleClick(index)}>-</button>
or
<button onClick={handleClick.bind(this, index)}>-</button>
read about that: https://reactjs.org/docs/handling-events.html
hope helped :)
Related
I have a list of items, each of which is represented by a component and supports a couple of actions such as "Like", "Save", I have 2 options: one is to keep the handlers in list(Parent) component and pass the handlers to each item component or I can have the handlers in the item component, the code may look like below, I am wondering which one is better, Thanks!
Solution 1:
const ItemComp = ({item, onLike, onSave}) => {
return (
<>
<p>{item.name}</p>
<div>
<button onClick={() => onLike(item.id)}>Like</button>
<button onClick={() => onSave(item.id)}>Save</button>
</div>
</>
)
}
const ListComp = ({items}) => {
const handleLike = (id) => {
console.log(id)
// like it
}
const handleSave = (id) => {
console.log(id)
// save it
}
return (
{items.map(
(item) => {
return <ItemComp item={item} onLike={handleLike} onSave={handleSave} >
}
)}
)
}
<List items={items} />
Solution 2:
const ItemComp = ({item}) => {
// keep event handlers inside of child component
const handleLike = (id) => {
console.log(id)
// like it
}
const handleSave = (id) => {
console.log(id)
// save it
}
return (
<>
<p>{item.name}</p>
<div>
<button onClick={() => handleLike(item.id)}>Like</button>
<button onClick={() => handleSave(item.id)}>Save</button>
</div>
</>
)
}
const ListComp = ({items}) => {
return (
{items.map(
(item) => {
return <ItemComp item={item} >
}
)}
)
}
<List items={items} />
if you are going to use the component in the same context throughout the whole app, or write an explanatory document for your components, its better if the handlers are inside. Otherwise, you would have to write new handlers for each time you use the component.
Think of it as a UI Library. Pass the data, see the result :)
BUT,
if you are going to use the component as a general, container kind of component, you'll have to write the handlers outside, because the component's own handlers wouldn't know how to handle the different kind of data if you introduce new contexts for it.
In a small React app, I'm trying to add delete functionality via a button for a list. Presently, I'm attempting this through the deleteItem function, which makes use of array.splice prototype method.
However, I'm encountering the error, Too many re-renders. React limits the number of renders to prevent an infinite loop.. What is the cause of this error? Shouldn't this function only be invoked once, when the button is clicked?
And how can I resolve this error?
import "./styles.css";
import React, { useState, Fragment } from "react";
export default function App() {
const [items, setItems] = useState(["first item"]);
const [newItem, setNewItem] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
setItems([newItem, ...items]);
};
const handleChange = (event) => {
setNewItem(event.target.value);
};
const deleteItem = (i) => {
setItems(items.splice(i,1))
}
return (
<div>
<form>
<input type="text" value={newItem} onChange={handleChange} />
<input type="button" value="submit" onClick={handleSubmit} />
</form>
<ul>
{items.map((i) => {
return (
<Fragment>
<li>{i}</li>
<button
onClick= {() => deleteItem(i)}> // Amr recommendation
delete
</button>
</Fragment>
);
})}
</ul>
</div>
);
}
Edit: I've taken user, Amr's, recommendation and added a anonymous arrow function to the button. However, a new issue has arisen. I can delete any item up until there exists only one item in the array. The final item cannot be deleted. Why is this?
you are passing function reference on the onClick handler, change it to an arrow function that triggers the delete method onClick= {()=>deleteItem(i)}>
second thing is that you should add keys to your the parent component when you Map over components to prevent unnecessary behavior.
and the last thing is that in your delete method, you are using Array.prototype.splice(), which returns the item that will be removed, from the items, your requested/ required behavior can be achieved through the Array.prototype.filter() method
const deleteItem = (i) => {
setItems(items.filter((item) => item !== i));
};
This is the final result, it should work fine.
import React, { useState, Fragment } from "react";
export default function App() {
const [items, setItems] = useState(["first item"]);
const [newItem, setNewItem] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
setItems([...items, newItem]);
};
const handleChange = (event) => {
setNewItem(event.target.value);
};
const deleteItem = (i) => {
setItems(items.filter((item) => item !== i));
};
console.log(items);
return (
<div>
<form>
<input type="text" value={newItem} onChange={handleChange} />
<input type="button" value="submit" onClick={handleSubmit} />
</form>
<ul>
{items.map((i, idx) => {
return (
<div key={idx}>
<li>{i}</li>
<button onClick={() => deleteItem(i)}>delete</button>
</div>
);
})}
</ul>
</div>
);
}
you can use following code for deleting from an array. it copies 'items' array and delete one item and after that setstate new array.
it prevent re-render whole component,do operations on copy of state and setstate final result.
const deleteItem = (i) => {
let newItems=[...items]
newItems.splice(i,1)
setItems(newItems)
};
Im using react hooks to create a multi-select component, that generates sliders for each selection (using material-ui). Once a user makes their selections I'm generating an individual slider for each selection using map. Each slider component has its own instance of useState to keep track of the value of the slider. Im passing a nodeid as a key to the component and would like to have an array of [nodeid, valueFromSlider] for each slider returned. For example: if a user makes 3 selections and then calls 'update feedback scores' button, returned values should be [[nodeid, valueFromSlider], [nodeid, valueFromSlider], [nodeid, valueFromSlider]].
What is the best way for my parent component to know what each individual sliders state is? is there a way to call component by id? Should i be usings forms or something? first time really using hooks since switching from redux.
Here is my code, thanks in advance.
const NodeSlider = (props) => {
const { node, nodeId } = props;
const [value, setValue] = useState(5);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div>
<Typography id="discrete-slider" gutterBottom>
{node.display_name}
</Typography>
<Slider
step={1}
marks
min={-10}
max={10}
valueLabelDisplay="on"
defaultValue={5}
value={value}
onChange={handleChange}
/>
</div>
);
}
const TunerSlider = (props) => {
const { sliders } = props;
const [feedbackArray, setFeedbackArray] = useState(null);
console.log('sliders', sliders);
const createFeedbackArray = () => {
// FIGURE THIS OUT? use this to call setfeedback array with value from node slider?
};
return (
<div>
{sliders.map((slider) => (
<NodeSlider
key={slider.node_id}
node={slider}
nodeId={slider.node_id}
/>
))}
<Button
variant="contained"
color="primary"
onClick={() => createFeedbackArray()}
>
Update Feedback Scores
</Button>
</div>
);
};
const TunerSearch = ({ nodeData }) => {
const [searchValues, setSearchValues] = useState(null);
const [sliderValues, setSliderValues] = useState(null);
const generateSlider = () => {
setSliderValues(searchValues);
};
return (
<div>
<div>Tuner Search</div>
<Autocomplete
multiple
id="tags-outlined"
options={nodeData}
getOptionLabel={(option) => option.display_name}
filterSelectedOptions
onChange={(event, value) => setSearchValues(value)}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="select nodes"
placeholder="add more nodes"
/>
)}
/>
<Button
variant="contained"
color="primary"
onClick={() => generateSlider()}
>
Generate Node Sliders
</Button>
{sliderValues ? <TunerSlider sliders={sliderValues} /> : null}
</div>
);
};
export default TunerSearch;
The best approach would be to move the states to the highest common parent.
Then you can define your function that will track your feedback in the parent component and then share them with the children component(s).
Here is an example of how I would approach this:
I was looking into React hooks and wasn't sure how useState works with arrays. So I made a working example with 3 controlled inputs, fed by an array. It works but I was wondering if this is the correct/optimal way of doing this.
Working example on codepen
const { useState } = React;
const InputAndButton = ({ tag, index, handleTagChange }) => (
<div>
<input value={tag} onChange={(e) => handleTagChange(e.target.value, index)} />
<button type="button" onClick={() => handleTagChange('', index)}>×</button>
</div>
)
function Tags(){
const [ tags, setTags ] = useState(["", "", ""]);
const handleTagChange = (value, index) => {
// make copy of state
const tagsCopy = [...tags];
// change the [i] value to the new one given
tagsCopy[index] = value;
// set copy back as state
setTags(tagsCopy);
}
return(
<form>
tags
{tags.map((tag, i) => <InputAndButton tag={tag} index={i} handleTagChange={handleTagChange} key={i} />)}
</form>
)
}
ReactDOM.render(<Tags />, document.getElementById("app"));
Can anybody give me some feedback here?
I am trying to change the innerhtml of an element in react, my solution is obviously not ideal since i'm manipulating the DOM directly. How can I write this in the correct way? Would it be better to useState or useRef? Basically, if plus is selected then turn into minus and if minus is selected then turn into plus. Each button is unique and there are multiple buttons
function App() {
const [tags, setTags] = useState([]);
const displayScores = (e) => {
if (e.target.innerHTML == "+") {
e.target.innerHTML = "-";
} else {
e.target.innerHTML = "+";
}
};
return (
<>
{tags.filter((t) => t.studentEmail == students.email).map((tag, index) => {
return (
<p key={index} className="student-tags">
{tag.value}
</p>
);
})}
<input
onKeyDown={addTag.bind(this, students.email)}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
</div>
</div>
<button onClick={displayScores} className="expand-btn">+</button>
</div>
);
})}
</>
)
}
export default App;
I don't think you need to access the DOM for this at all. You could use some React state (boolean) that determines if scores are displayed, and then render a "+" or "-" in the button based off of that boolean.
// Your state
const [scoresDisplayed, setScoresDisplayed] = useState(false);
// Function to update state
const displayScores = () => {
// Toggle the boolean state
setScoresDisplayed(state => !state);
};
// Conditionally render button text
<button onClick={displayScores} className="expand-btn">
{scoresDisplayed ? "-" : "+"}
</button>
in React you should not work with something like inner html,there much better ways like below :
function App() {
const [tags, setTags] = useState([]);
const [buttonLabel, setButtonLabel] = useState("+");
const toggleButtonLabel = () => {
setButtonLabel(buttonLabel === "+" ? "-" : "+");
};
return (
<>
{tags
.filter((t) => t.studentEmail == students.email)
.map((tag, index) => {
return (
<p key={index} className="student-tags">
{tag.value}
</p>
);
})}
<input
onKeyDown={addTag.bind(this, students.email)}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
<button onClick={toggleButtonLabel} className="expand-btn">
{buttonLabel}
</button>
</>
);
}
export default App;