Hello and thank you for your time in advance!
I am struggling with a small issue that I have not encountered before, with React not rendering an UI element based on a check function. Basically, what I am trying to make, is a multiple selection filter menu, where when an option is clicked, the dot next to it changes to red.
For this purpose I append the value of each option to an array, and using array.sort, verify when it is to be added (value is pushed to FilterList) and removed (value is popped from FilterList)
The checkfilter function operates normally, and when logging the state, indeed it works as intended with the array being updated.
However, the JSX code running the checkfilter to render the extra red dot inside the bigger circle, unfortunately does not.
Additionally, when the screen is refreshed, the UI is updated normally, with every option clicked, now showing the aforementioned red dot.
Why is this happening? I have tried several hooks, JSX approaches, using imported components and more that I can't even remember, yet the UI will not update oddly.
Below you can find a snippet of the code. Please bear in mind this is a render function for a flatlist component
const checkFilter = useCallback((element) => {
return filterList?.some((el: any) => (el == element))
}, [filterList])
const removeFilter = useCallback((cat) => {
let temparr = filterList
var index = temparr?.indexOf(cat);
if (index > -1) {
temparr?.splice(index, 1);
}
setFilterList(temparr)
}, [filterList])
const addFilter = useCallback((cat) => {
let temparr = filterList;
temparr.push(cat);
setFilterList(temparr)
}, [filterList])
const renderFilter = useCallback((item) => {
return (
<Pressable
onPress={() => {
checkFilter(item?.item) ? removeFilter(item?.item) : addFilter(item?.item);
console.log(filterList)
}}
style={styles.modalOptionWrapper}
>
<Text style={styles.modalOptionTitle(checkFilter)}>{item?.item}</Text>
<View style={styles.modalOptionRowRight}>
<View style={styles.radioBtn}>
{checkFilter(item?.item) ?
<View style={styles.radioBtnBullet} />
:
null
}
</View>
</View>
</Pressable>
)
}, [filterList])
This may not be correct answer but try this. When I say simple basic codes, like this.
const ListItems = () => {
const [filterList, setFilterList] = React.useState([]); // must be array
const checkFilter = filterList?.some((el) => el === element);
const removeFilter = useCallback(
(cat) => {
// not updating new state, just useing previous state
setFilterList((prev) => [...prev].filter((el) => el !== cat));
// used spread iterator
},
[] // no need dependency
);
const addFilter = useCallback((cat) => {
// used spread indicator
setFilterList((prev) => [...prev, cat]);
}, []);
const renderFilter = useCallback((item) => {
// just checking our codes is right with basic elements
// it may be 'undefined'
return <Text>{JSON.stringify(item)}</Text>;
}, []);
return filterList.map(renderFilter);
};
Related
How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};
I tried making a to-do app using React-native. It is a blank react native project that uses typescript and expo. I'm on linux.
The state containing the todo array gets updated it should but the view is not rendered accordingly untill I click the input button and change the text state.
I have 3 functions to modify the states - textChange (for updating the todoText state), handleAddTask (for updating the array containing tasks), rmTask ( removes item from task array)
const [task, setTask] = useState('');
const [taskItems, setTaskItems] = useState(["Write a task"]);
const textChange = (text: string) => {
setTask(text)
}
const handleAddTask = () => {
taskItems.push(task)
setTaskItems(taskItems)
setTask('')
}
const rmTask = (index: number) => {
taskItems.splice(index, 1);
setTaskItems(taskItems)
}
And the components where the functions are affecting
taskItems.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={()=> rmTask(index)}
>
<Task text={item} />
</TouchableOpacity>)
})
const handleAddTask = () => {
// taskItems.push(task) <-- Dont mutate state like this !
setTaskItems([...taskItems, task]). <-- You should use it like this way.
setTask('')
}
You are mutating the state value without using the setter function. Thats why Its not working correctly. And when you write something new to task input, UI gonna re-render and display the correct thing. Because textChange function implementation correct and update the state.
In two places you are directly manipulating the state rather than using setState.
You should do this way:
//taskItems.splice(index, 1);
//setTaskItems(taskItems)
// -should be
setTaskItems([...taskItems].splice(index, 1))
and
//taskItems.push(task)
//setTaskItems(taskItems)
// -should be
setTaskItems([...taskItems, task])
I have a List which contains ListItem and a search bar. Search functionality is working well as per the requirement and its highlighting the ListItem. Now i am trying to add the scroll so that the List gets scroll to first occurrence of the 'includes' if the search item is not available on visible window but not able to implement it. Tried const ref = React.createRef();
How to get it working?
Here is the codesandbox link
You can easily achieve this one using vanilla js.
First just store the index of all matching ListItem in a state:
const getMatchingListItems = React.useCallback(() => {
return [...messages]
.map(({ id, primary, secondary, person }, i) => {
if (secondary.includes(searchTranscript) && searchTranscript !== "") {
return i;
}
})
.filter((elmt) => elmt !== undefined);
}, [searchTranscript]);
const [matchingListItems, setMatchingListItems] = useState(getMatchingListItems());
Use it to define which className to use:
<div
className={
matchingListItems.includes(i) ? classes.searchHighLight : ""
}
And finally use the useEffect hook to scroll to the first matching LisItem when matchingListItems is updated:
React.useEffect(() => {
setMatchingListItems(getMatchingListItems());
}, [getMatchingListItems, setMatchingListItems]);
React.useEffect(() => {
const firstListMatch = matchingListItems[0];
if (firstListMatch) {
const firstLi = document.querySelector(
`ul div:nth-child(${firstListMatch})`
);
firstLi.scrollIntoView();
} else {
document.querySelector("ul").scrollIntoView();
}
}, [matchingListItems]);
You can try it using this codesandbox.
kinda new to React and I am looking to use useState but only to be applied to the clicked element. For example:
const [backgroundColor, setBackgroundColor] = useState({background: "green"});
in my function I have something like this:
function updateBackground(){
let updateColor = {background: "blue"};
setBackgroundColor(updateColor);
}
then in my return JSX code I have
{subButtons.map(function(subButton, index){
return (<div key={index} onClick={updateBackground} style={backgroundColor}>{subButton}</div>)
})}
but this will apply this function to all generated elements of the .map method.
How can I update my code to be applied to the clicked only element?
Thank you.
You are attempting to manage the discrete state of multiple children in the parent. #Denis Stukalov answered the question by suggesting keeping a corresponding array of state per child. This can work, but I find that it rarely scales well in the real world.
I would suggest keeping track of the state inside the button itself.
const BackgroundButton = (props) => {
const [backgroundColor, setBackgroundColor] = React.useState({background: 'green'});
const updateBackground = () => setBackgroundColor({background: 'blue'});
return <div onClick={updateBackground} style={backgroundColor} {...props} />
};
const App = () => ['button1', 'button2', 'button3'].map(
(subButton, index) => (
<BackgroundButton key={index}>{subButton}</BackgroundButton>
)
);
See this in action in this CodePen.
You should use array instead single background object. Like this:
const subButtons = ['button1', 'button2', 'button3']
const [backgroundColors, setBackgroundColor] = useState(subButtons.map(() => { return { background: "green" } }))
function updateBackground(index){
let newBackgroundColors = [...backgroundColors]
newBackgroundColors[index] = { background: "blue" }
setBackgroundColor([...newBackgroundColors])
}
return (subButtons.map(function(subButton, index){
return (<div key={index} onClick={()=>updateBackground(index)} style={backgroundColors[index]}>{subButton}</div>)
}))
See in playground: https://jscomplete.com/playground/s505358
const [backgroundColor, setBackgroundColor] = useState("green");
function updateBackground(){
let updateColor = "blue"
setBackgroundColor(updateColor);
}
try this instead
I have a function I calling it in render() method
and it's setState a Flag from the state.
So I got this error
Cannot update during an existing state transition (such as within
render).
I read about this error, and what I understand it's because I setState in render method and this is the wrong way.
So I'm forced to do it if u have any idea to handle this tell me.
The main idea about this function
I have an array of an object "Name as Tools" so in every object I have "id, name, count, price"
so that will render a three input in UI like this
and I have a boolean flag in-state "isEmpty" that checks every input in array before sending this data to the database.
Code
State = {
toolsUsed: [
{
id: 0,
name: '',
price: '',
count: '',
},
],
// Checker
isEmpty: false,
}
renderToolsUsed = () => {
const {toolsUsed} = this.state;
const tools = toolsUsed.map((item, i) => {
const {count, price, name, id} = item;
this.setState({
isEmpty: ['name', 'price', 'count'].every(key => item[key].length > 0),
});
return (
<View key={i} style={styles.tools}>
.... Inputs here ...
</View>
);
});
return tools;
};
JSX
render() {
return (
<View>
{this.renderToolsUsed()}
</View>
);
}
You can't update the state like this. It is like infinite loop. setState will trigger render, then render will trigger another setState, then you keep repeat the circle.
I don't know why you need isEmpty when you already have toolsUsed which you can use it to check if all input are empty.
Lets say if you insist to have isEmpty, then you can set it inside input change event.
The code is not tesed. I wrote the code directly from browser. But you can get the idea before the code.
renderToolsUsed = () => {
const { toolsUsed } = this.state;
const tools = toolsUsed.map((item, i) => {
return (
<View key={i} style={styles.tools}>
<TextInput value={item.name} onChangeText={(text) => {
this.setState({
toolsUsed: [
...toolsUsed.slice(0, i - 1),
{...item, name: text },
...toolsUSed.slice(i)
]
}, this.updateEmptyState)
}>
// other input here
</View>
);
});
// ...
};
updateEmptyState = () => {
this.setState({
isEmpty: this.state.toolsUsed.every(x => x.name === '' && x.price === '' && x.count === '')
})
}
The state is not designed to store all the data you have in the app
For what you need isEmpty inside the state?
To do this, use a global variable
Or check it out when you want it out of the render