I have React Component with state:
this.state={
items: [
{
label: '1 Some text',
active: true
},
{
label: '2 Some text',
active: false
},
{
label: '3 Some text',
active: false
},
{
label: '4 Some text',
active: false
}
]
}
I have a menu, when I click on it, I would like to change the state:
this.state.items.map(item => {
return <li onClick={}>{item.label}</li>
})
If I click on any of the menu items, I want to make it active: true, and set all others to active: false.
Update:
manuOnClick=(e, label)=>{
let newState = this.state.items.map(item=>{
if(label === item.label)
{
let newItem={
label:label,
active:true
}
return newItem
}
else
{
let newItem={
label:item.label,
active:false
}
return newItem
}
})
this.setState({
items:newState
})
}
Sorry,
onClick={(e,item.label)=>this.manuOnClick(e,item.label)
is not correct, it should be
onClick={(e)=>this.manuOnClick(e,item.label)
Try this :
manuOnClick=(e, label)=>{
let newState = this.state.items.map(item=>{
if(label === item.label)
{
let newItem={
label:label,
active:true
}
return newItem
}
else
{
return item
}
})
this.setState({
items:newState
})
}
this.state.items.map(item => {
return <li onClick={(e)=>this.manuOnClick(e,item.label)}>{item.label}</li>
})
You'll want to use this.setState:
https://reactjs.org/docs/react-component.html
I would highly recommend considering splitting this component out into different components, one per item, each with it's own state management. Is there a reason the state for all of them need to be managed at this parent level? Simpler is better than complex.
That being said, if you do move forward and decide that you need it at this level, I'd recommend some thing like the following for each onClick:
this.setState((state) => {
const itemsCopy = state.items.slice();
itemsCopy[itemIndex].active = true;
return { items: itemsCopy };
});
Related
I have data like this one below
let data = [
{
name: 'basic',
to: 'aaa',
subMenus: [
{
name: 'general conc',
to: 'geneal',
},
{
name: 'example view',
to: 'example',
},
{
name: 'fancy',
to: 'bbb',
innerSubMenus: [
{
name: 'adding',
to: 'add',
},
{
name: 'getting',
to: 'get',
},
]
}
]
}
]
I need to filter data based on name (in main, subMenus, and innerSubMenus)
Here is the piece of code
function deepFilter(inputText){
data.filter(items => items.name.toLowerCase().includes(inputText.toLowerCase()))
}
As you can see, the function filters the first prop (name --> basic in this case when inputText = 'basic', doesn't work when inputText = 'general conc') but I want to be able to filter names in subMenus and innerSubMenus as well. Please, guide me on how to do it. Thanks
expected outputs:
deepFilter('basic') -> true // only this part is covered by my implementation
deepFilter('general conc') -> true
deepFilter('adding') -> true
deepFilter('ffff') -> false //since there is not name with value of 'ffff' in data
I think this should work well.
function deepFilter(inputText, datas) {
return datas.filter(data => {
function checkInsideObj(object, inputText) {
for (let value of Object.values(object)) {
if (object.name && object.name.toLowerCase() === inputText.toLowerCase()) {
return true;
}
if (Array.isArray(value)) {
return value.some(item => {
return checkInsideObj(item, inputText)
}
)
}
}
return false;
}
return checkInsideObj(data, inputText)
}
)
}
deepFilter("input", data)
Okay, I am experiencing some behaviour I don't really understand.
I have this useState hook
const [permanent, setPermanent] = useState(false)
and this useEffect hook
useEffect(() => {
if (permanent) {
dispatch({ value: 'Permanent booth', key: 'period' })
} else {
dispatch({ value: '0', key: 'period' })
}
}, [permanent])
It triggers a rerender on initial render, and I do not call setPermanent upon rendering my component, I have checked this both by commenting every single setPermanent call out in my application. And I have also tried replacing it with a function that logs to the console.
//const [permanent, setPermanent] = useState(false)
const permanent = false
const setPermanent = () => {
console.log('I am called') //does not get called on initial render
}
I know it triggers a rerender because when I comment one of the second dispatch call in it out, it does not trigger the rerender.
useEffect(() => {
if (permanent) {
dispatch({ value: 'Permanent booth', key: 'period' })
} else {
//dispatch({ value: '0', key: 'period' })
}
}, [permanent])
Is there a reason for this, because I cannot seem to find documentation explaining this behaviour?
EDIT --------------
const shopOptions = (() => {
const options = [
{ label: 'Choose a shop', value: '0' },
]
Object.keys(stores).forEach(store => {
options[options.length] = { label: store, value: options.length }
})
return options
})()
const genderOptions = [
{ label: 'Choose a gender', value: '0' },
{ label: 'Female', value: '1' },
{ label: 'Male', value: '2' }
]
const periodOptions = [
{ label: 'Choose a period', value: '0' },
{ label: '1 week', value: '1' },
{ label: '2 weeks', value: '2' },
{ label: '3 weeks', value: '3' },
{ label: '4 weeks', value: '4' }
]
const initialState = {
shop: shopOptions[0],
gender: genderOptions[0],
period: periodOptions[0],
}
function reducer(prevState, { value, key }) {
const updatedElement = { ...prevState[key] }
updatedElement.value = value
return { ...prevState, [key]: updatedElement }
}
//form
const [state, dispatch] = useReducer(reducer, initialState)
useEffect hooks run both after the first render and after every update of variables passed to the dependency array (in your case [permanent]).
Because you have a boolean value that triggers the effect, it's hard to know whether it's the first render or a re-render within the effect. In your case I would consider not using a useEffect here, and instead dispatching what you need while updating the state. For example:
const [permanent, setPermanent] = useState(false)
const makePermanent = () => {
setPermanent(true)
dispatch({ value: 'Permanent booth', key: 'period' })
}
const makeTemporary = () => {
setPermanent(false)
dispatch({ value: '0', key: 'period' })
}
This is my solution. Just a boolean property and ordering hook will do the job.
const _isMounted = React.useRef(false)
const [filter, setFilter] = React.useState('')
React.useEffect(() => {
if (_isMounted.current) {
getData(filter) // Now it will not get called very first time
}
}, [filter])
/* Order is important. [] or so called componentDidMount() will be the last */
React.useEffect(() => {
_isMounted.current = true
return () => {
_isMounted.current = false
}
}, [])
In my reducer, I have the initial state which looks like this:
const initialState = {
isLoading: false,
events: [
{
year: 2021,
place: [
{
id: 1,
name: "BD"
},
{
id: 2,
name: "BD Test"
}
]
},
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
I have been trying to implement the functionality of deletion operation. So, when the button will be clicked the "deleteItems" action will be dispatched that will remove the corresponding items from the place. This functionality works fine. But,I am trying to remove the whole items from the events array if there is no values in place.
This is what I have tried already but it just removes the individual place. But, I need to write the logic here of removing the whole items when place becomes empty.
case "deleteItems":
return {
...state,
events: state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
})
};
So, after modifications, the state would look like this:(when there is no values in place for year 2021)
const initialState = {
isLoading: false,
events: [
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
Does anybody know how to accomplish this. Any helps would be highly appreciated.Thanks in Advance.
Demo can be seen from here
I removed the places first.
Then I filtered events based on whether the place array is empty or not.
After that, I returned the state.
case "deleteItems":
const eventsPostDeletingPlaces = state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
});
const eventsWithPlaces = eventsPostDeletingPlaces.filter((each) => each.place.length);
return {
...state,
events: eventsWithPlaces
}
Check the edited sandbox here
Basically the same logic as in the first answer, but with reduce instead of a map and an extra filter. Just an option.
case "deleteItems":
return {
...state,
events: state.events.reduce((events, event) => {
const place = event.place.find(x => x.id === action.id);
if (place) {
event.place = event.place.filter(x => x.id !== action.id);
}
if (event.place.length > 0) {
events.push(event);
}
return events;
}, [])
};
codesandbox
I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}
I have nested objects on my state:
constructor(props) {
super(props);
this.state = {
aba1Array: [
{
item:[
{
id: 1,
nome: "apple",
selected:false,
},
{
id: 2,
nome: "juice",
selected:false,
}
]
},
{
item:[
{
id: 3,
nome: "apple",
selected:false,
},
{
id: 4,
nome: "juice",
selected:false,
}
]
}
],
};
}
Now I want to make a copy of the array, update an item, and setState again (is this the correct way to update, right ?), how to do this ? this is my current code:
updateItem(id){
let newItems = Object.keys(this.state.aba1Array).map((subitem) => {
subitem.item.map((item) => item.id===id ? {...item, selected: true } : item);
});
this.setState({ aba1Array: newItems });
}
this is returning:
TypeError: undefined is not an object (evaluating 'subitem.item.map')
edited: The problem begun when I added second level of nesting , before it was working fine with just one level , like this :
let newItems = Object.keys(this.state.aba1Array).map((item) => {
item.id===id ? {...item, selected: true } : item);
edited2: in another part of the screen where I only list the items , i'm using:
listItem() {
return this.state.aba1Array.map((subitem)=> {
return ( subitem.item.map((item) => { return ( <View>
...
and it works fine. Why in the update function it gives me error ? I don't understand
Edited3: - SOLUTION -
My solution was to use library object-path-immutable:
updateItem(itemIndex,subIndex){
const newObj = immutable.update(this.state.aba1Array, [subIndex,'item', itemIndex , 'selected' ], v => true );
this.setState({ aba1Array: newObj });
}
My solution was to use library object-path-immutable:
updateItem(itemIndex,subIndex){
const newObj = immutable.update(this.state.aba1Array, [subIndex,'item', itemIndex , 'selected' ], v => true );
this.setState({ aba1Array: newObj });
}
Best solution :
I forgot to return the value on map function , so the new array wasnt getting the full tree of the object , the best solution is:
let newItems = this.state.aba1Array.map((subitem) => {
subitem.item.map((item) => {
item.selected = true;
return item;
})
return subitem;
});