This is a simple representation of my real problem in a project, it seems i am missing something in react or may be the problem is not related to react, this is my code https://codesandbox.io/s/lucid-northcutt-c3fw4?file=/src/App.js
i need to show the item on the list when click on it, and this never happens because that condition never get true and i don't know why?
{id === itemId && el}
When you click the element, you successfully set the itemId value to the id of the clicked element:
const handleItemClick = id => {
setItemId(id);
};
However, upon re-rendering the component you dynamically generate all new id values:
id={v4()}
It's highly unlikely (re: impossible) that the newly generated UUID will be the same as the previously generated one.
Don't generate new UUID id values on every render. Either use static values or perhaps just an incrementing value from .map():
{arr.map((el, i) => {
return <Item
id={i}
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
))}
Issue :
id={v4()} this will create a new uuid on each render, so when you click handleItemClick it update itemId and which cause re-render and all ids got changed due to id={v4()} so this will never get true {id === itemId && item}
{arr.map(el => {
return <Item
id={v4()} // <------ Issue is hear
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
})}
Solution :
// instead of creating uuid on render you can create and save it as state, so it won't change on each re-render
const [items, setItems] = useState([1, 2, 3, 4].map(() => v4()));
{items.map(id => {
return (
<Item
id={id} // <----- Then use it like this
itemId={itemId}
item={id}
handleItemClick={handleItemClick}
/>
);
})}
WORKING DEMO :
First you missed the key prop, when using map(). It will just throw the warning, not to worry about much :P.
the main problem in your code is, v4 will generate random keys on each render. so
the id will never be the same as itemId. try to use some static id. if you can like the map comes with index. you may use that
arr.map((el,index) => {
return <Item
id={index}
key={el}
itemId={itemId}
item={el}
handleItemClick={handleItemClick}
/>;
});
I'm assuming v4 generates a random ID.
So id === itemId will never be true.
And I don't see itemId being initialized anywhere.
It would be great if you can tell me what is that condition for?
Or try {id === itemId || item}
Hope this helps
Thank you
Related
I have a table with checkboxes and I want to save selected checkboxes' ids in state. So here's my code.
Input looks like this (I use coreui so this is the inside of a table's scopedSlots):
selected: (item) => {
return (
<td style={{ width: '40px' }}>
<CInputCheckbox
className="mx-auto"
id={item.id}
onChange={(e) => handleSelect(e)}
/>
</td>
);}
And this is the rest:
const [selectedRows, setSelectedRows] = useState([]);
const handleSelect = (e) => {
const id = e.target.id;
const index = selectedRows.indexOf(id);
const rows = [...selectedRows];
if (index === -1) {
rows.push(id);
} else {
rows.split(index, 1);
}
setSelectedRows(rows);
};
And the weirdest thing happens - in the chrome's react devtools I see that the first id is being added to the selectedRows array and then when I select another row - the previous item in the array is being overwritten. When i console.log my selectedRows array it shows empty array always (even if I see in the devtools that there's one item). I have no idea what I'm doing wrong here.
There is not a lot of information to work with in your question, so I had to make many assumptions about how are they suppose to work, but this is what I got so far:
the handleSelect function has a little error when it comes to removing items from the list, your function is using split but I think what you meant was slice
I think the scopes of handleSelect and const [selectedRows, setSelectedRows] = useState([]); are wrong. For what I can see, they are at the same level of selected component, which will fail as they will only be able to keep track of one selected component at the time, and I think you are needing to keep track of multiple selected components.
Here is a working version of what I think you are trying to do
https://codepen.io/richard-unal/pen/eYWXXOo?editors=1111
If you need clarification on something, please let me know, I'm aware that I'm not the best explainer.
I am currently designing an application that contains a list of values in a list, called modifiers, to be edited by the user to then store for later use in calculations. To make it easier to find a specific modifier, I added a search function to the list in order to pull up the similar modifiers together to the user. However, once the user puts in a value into the filtered list and then unfilters the list, the component incorrectly assigns the values to the wrong modifiers. To be more specific, the ant design <List> component when filtered fails to put the proper defaultValue for each associated input. Namely, when I input a value into the first item in the filtered list and then unfilter it, the List incorrectly places that value within the first element on the unfiltered list, rather than the modifier it was supposed to be associated with. It should be putting the proper value with the associated element by assigning the value that its grouped with in the context I have stored, but it obviously fails to do so.
Here is the Ant Design List Component I am talking about, I have removed some callbacks that aren't necessary to understand the problem. The renderitem prop takes the dataSource prop as an input and maps all of the values into it to be inputs for the <List.Item> components.
EDIT:
I failed to mention the hook in the first line, that is utilized by the search function in order to filter the words looked through to update the list accordingly. I also removed some unnecessary inline css and components since they are not relevant to the problem to improve readability. I have also decided to give a more concrete example of my issue:
This is an image of the initial values set by the user.
This is an image immediately after searching the exact name of the modifier and the list gets filtered. Clearly, the value from the first item of the unfiltered list is being put into the input of the first item of the filtered list, which is the main problem. Now when the search is undone, everything does get properly set, so I am unsure how to fix this.
I have some ideas as to why this is occurring. I know that the input components are not being re-rendered, and rather their ids are just being swapped out when the search occurs. So if there are any ways to either forcefully re-render the input components in addition to the list sections, please tell me!
const Modifiers = () => {
const [searchFilter, setSearchFilter] = useState(military); //Only for words in search bar, "military" will be replaced with entire data set later
const context = useContext(Context)
const search = value => {
if(value != ""){
setSearchFilter(searchFilter.filter(mod => mod.name.toLowerCase().indexOf(value.toLowerCase()) != -1))
}
else {
setSearchFilter(military)
}
}
const updateContext = (e, name) => {
let id = name.toLowerCase().replace(/ /gi, "_");
if(context.modifiers[id] != undefined){
context.modifiers[id] = parseFloat(e.target.value)
}
}
return (
<Layout>
<SiteHeader/>
<Content style={{ padding: '1% 3%', backgroundColor: "white"}}>
<Typography>
<Title level={2} style={{textAlign: "center"}}>
Modifier List
</Title>
</Typography>
<List dataSource={searchFilter} header={<div style={{display: "flex"}}> <Title level={3} style={{paddingLeft: "24px"}}>Modifiers</Title> <Button shape="circle" size="large" icon={<InfoCircleOutlined/>}/> <Search allowClear placeholder="Input Modifier Name" enterButton size="large" onSearch={search}
renderItem={mod => (
<List.Item extra={parseTags(mod)}>
<List.Item.Meta description={mod.desc} avatar={<Avatar src={mod.image}/>} title={<div style={{display: "flex"}}><Title level={5}>{mod.name}: </Title> <Input defaultValue={context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] != undefined ? context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] : ""} id={mod.name} onChange={(e) => updateContext(e, mod.name)}/></div>}/>
</List.Item>
)}
/>
</Content>
</Layout>
);
}
export default Modifiers;
Here is the Context Class, the modifiers field is what is the issue currently. It only has 2 currently, but the problem persists when more are added, and these 2 modifiers are the first in the unfiltered list as well.
export class Provider extends React.Component {
state = {
name: "None Selected",
tag: String,
flag: "images/flags/ULM",
modifiers: {
army_tradition: 0,
army_tradition_decay: 0,
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
Here is what one element in the military array looks like for reference as well. The regex inside the <List.Item> component is merely converting the name field of the object into one that matches whats stored within the context.modifiers field.
export const military = [
{
name: "Army Tradition",
desc: "Adds to the rate of army tradition gained each year.",
function: "ADDITIVE",
type: "WHOLE NUMBER",
category: "MILITARY",
image: "/images/icons/landLeaderFire.png",
},
...
Thanks for any help you can give.
I have solved the issue, I replaced the "id" prop with a "key" prop (which the documentation doesn't even tell you about) and now everything works properly!
I have simple list which is dynamically added on add button click. in my list there is a checkbox is also present .so I have an issue when I toggle the checkbox my whole list is re render why ?
let take example I added A,B,C,D in my list when I toggle D checkbox it should only render D item currently it render whole list why ?
here is my code
https://codesandbox.io/s/stupefied-wildflower-gv9be
const Item = ({ text, checked, onCheckedHandler }) => {
console.log(checked, "ssss");
return (
<div className={checked ? "bg" : ""}>
<span>{text}</span>
<input type="checkbox" onChange={e => onCheckedHandler(e, text)} />
</div>
);
};
Every time items changes (whether by adding a new item or checking a value), you are creating a new onCheckedHandler in your App. This propagates down to your Item component. Since the previous onCheckedHandler property is not referentially equivalent to the previous one, it renders (and you see that console log for each item). Memoizing the component alone won't help because a property being passed to it is changing every time.
To get around that, you need to memoize the onCheckedHandler, try this:
const onCheckedHandler = useCallback((e, selectedText) => {
const target = e.target
setItems(items => {
const i = items.findIndex(i => i.text === selectedText);
let obj = items[i];
obj.checked = target.checked;
return [...items.slice(0, i), obj, ...items.slice(i + 1)];
})
}, [setItems])
The you can wrap your Item compoennt with React.memo, and it should work as expected. You'll also need to import the useCallback the same way you import useState
I have a problem with saving the state of the search query.
When the popover is brought into focus, the searchString starts with undefined (second undefined value in picture). When the key 'b' is pressed, the event is fired, and it sets the value to "" (initialized value). As shown, when "bart" is in the search query, console only registers "bar". Does anyone know why this behavior occurs? The end goal is that I am trying to retain the search string on selection (it disappears onclick) -> would appreciate any help with this. The main code block where these changes are happening:
<Autocomplete
open
onClose={handleClose}
multiple
classes={{
paper: classes.paper,
option: classes.option,
popperDisablePortal: classes.popperDisablePortal,
}}
value={pendingValue}
onChange={(event, newValue) => {
setPendingValue(newValue);
}}
// inputValue={searchString}
// onInputChange={(event, newValue) => {
// setSearchString(newValue);
// }}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No values"
renderOption={(option, { selected }) => (
<React.Fragment>
<DoneIcon
className={classes.iconSelected}
style={{ visibility: selected ? 'visible' : 'hidden' }}
/>
<div className={classes.text}>
{option.value}
</div>
</React.Fragment>
)}
options={[...suggestions].sort((a, b) => {
// Display the selected labels first.
let ai = selectedValue.indexOf(a);
ai = ai === -1 ? selectedValue.length + suggestions.indexOf(a) : ai;
let bi = selectedValue.indexOf(b);
bi = bi === -1 ? selectedValue.length + suggestions.indexOf(b) : bi;
return ai - bi;
})}
getOptionLabel={option => option.value}
renderInput={params => (
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
className={classes.inputBase}
// onChange={(event) => {
// console.log("event.target: ", event.target);
// console.log("event.currentTarget: ", event.currentTarget);
// setSearchString(event.currentTarget);
// }}
value={searchString}
onChange={handleInputChange}
/>
)}
/>
I have tried to store the value and re-populate it using both through the Autocomplete props and the InputBase (doing it on both causes it to crash). I have added a sandbox for your ref: CodeSandbox
Appreciate all the help!
Material UI autocomplete by design resets the search value every time you select an option. If you want to by pass it, use useAutocomplete hook to fine tune the component according to your need.
As for delayed console log values, you're setting the new value and then you're console logging the old value. So obviously it will print the old value, what else did you expect?
You code should have been like this
const handleInputChange = event => {
// new value => event.currentTarget.value
// old value => searchString
// these values never mutate throughout this function call
setSearchString(event.currentTarget.value);
// searchString still remains the same here and
// won't change even if you call setState
// it remains the same throughout this entire function call
// Since Mutation is not allowed in Functional Programming
// This is perhaps why Functional Programming is
// far better than Object Oriented Programming 😉
console.log('searchString: ', event.currentTarget.value);
}
However this isn't the right way to observe state changes. Better way would be something like this,
// This will be called whenever React
// observes a change in anyState
useEffect(() => {
console.log(anyState)
}, [anyState])
I am starting with a simple array of JSX elements:
const jsxArray = dataItems.map(item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
))
Inside render, or rather return since I use functional components for everything now, I'd like to filter for JSX elements where the isActive attribute was tagged true.
return (
{jsxArray
.filter(jsxElement => // want to filter in JSX elements
// that are true for customAttribute keyed to `item.isActive`)
}
)
Is there any way to do it?
If there is not precisely a good way I am open to workarounds.
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
You don't filter the list after you render it. At that point it's just a tree of nodes that doesn't have much meaning anymore.
Instead you filter the items first, and then render only the items that pass your criteria.
const jsxArray = dataItems.filter(item => item.isActive).map(item => (
<div>
<h3>{item.title}</p>
<p>{item.body}</p>
<p customAttribute={item.isActive} >{item.tags}</p>
</div>
))
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
Not necessarily. When dealing with filtering like this myself I create two variables, one for the raw unfiltered list and one for the filtered items. Then whatever you're rendering can choose one or the other depending on its needs.
const [items, setItems] = useState([])
const filteredItems = items.filter(item => item.isActive)
return <>
<p>Total Items: ${items.length}</p>
<ItemList items={filteredItems} />
</>
Instead of accessing the jsx element properties (which I think it's either not possible or very difficult) I suggest you to act in this way:
Save the renderer function for items in an arrow function
const itemRenderer = item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
)
Save the filter function in an arrow function
const activeItems = item => item.isActive
Use them to filter and map
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Use them to map only
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Hope this helps!
Usually you would filter the plain data first and then render only the markup for the filtered elements as described in #Alex Wayne answer.
If you worry about duplication of the markup, that can be solved by extracting a component from it:
const Item = ({title, body, isActive, tags}) => (
<div>
<Header>{title}</Header>
<Paragraph>{body}</Paragraph>
<Paragraph customAttribute={isActive}>{tags}</Paragraph>
</div>
);
For rendering the filtered list you can then do:
{items.filter(item => item.isActive).map(item => <Item {...item} />)}
and for the unfiltered list:
{items.map(item => <Item {...item} />)}