Unfocusing dropdown menu sets its state as undefined - javascript

I'm making a dropdown menu that allows user to set a state and then see the page corresponding to chosen values.
I isolated my code to fully reproduce the issue both in text and in this [CodeSandbox]
Desired baheviour - Open menu, set state using its componets, close menu and keep the state.
Current behaviour - Open menu, set state using its components, close menu and state is set to undefined.
I track the changes to the state in the console and can clearly see that adding items to filter is seen in the updated state every time. However when I close the menu the state changes to undefined and the state is unsuable for my needs.
How do I change the code so the state persists when the menu is closed?
Thanks in advance for your time!
import React from "react";
import { default as ReactSelect } from "react-select";
import { components } from "react-select";
export default function BettingDeck(props) {
const sportsOptions = [
{ value: "soccer", label: "Soccer" },
{ value: "dota", label: "Dota 2" },
{ value: "tennis", label: "Tennis" },
{ value: "csgo", label: "CS:GO" }
];
const Option = (props) => {
return (
<div>
<components.Option {...props}>
<input
type="checkbox"
checked={props.isSelected}
onChange={() => null}
/>{" "}
<label>{props.label}</label>
</components.Option>
</div>
);
};
const [sportsSelectorState, setSportsSelectorState] = React.useState({
optionSelected: [],
isFocused: true
});
function handleChange(selected) {
setSportsSelectorState(() => {
return { optionSelected: selected };
});
}
console.log(sportsSelectorState.optionSelected);
return (
<>
<div className="betting-deck-container">
<div className="betting-deck-head-container">
<div className="betting-deck-title">Betting Deck</div>
{/* <SportsSelector /> */}
<span
class="d-inline-block"
data-toggle="popover"
data-trigger="focus"
data-content="Please selecet account(s)"
onBlur={() => {
setSportsSelectorState({ isFocused: false });
}}
onFocus={() => {
setSportsSelectorState({ isFocused: true });
}}
style={
sportsSelectorState.isFocused ? { zIndex: 1 } : { zIndex: 0 }
}
>
<ReactSelect
options={sportsOptions}
isMulti
closeMenuOnSelect={false}
hideSelectedOptions={false}
components={{
Option
}}
onChange={handleChange}
allowSelectAll={true}
value={sportsSelectorState.optionSelected}
placeholder="Select sports to filter"
menuPortalTarget={document.body}
classNamePrefix="mySelect"
/>
</span>
</div>
</div>
</>);}

Every time you set the state, you overwrite it with a new object.
So this:
setSportsSelectorState(() => {
return { optionSelected: selected };
});
practically removes isFocused from the object.
And this removes optionSelected:
setSportsSelectorState({ isFocused: true });
So to always preserve the entire object, spread the previous state (object) into the new and only overwrite the relevant property:
// The parameter in the callback function (prev)
// always holds the previous state, or should
// I say the state as it currently is
// before you change it.
setSportsSelectorState((prev) => {
return { ...prev, isFocused: true };
});
// or
setSportsSelectorState((prev) => {
return { ...prev, optionSelected: selected };
});

Related

How can I do dynamic badge on React CoreUI?

I have a problem with a badge on Core UI. I have a Sidebar and one of the elements is Chat. When a new message comes, the badge must show new message. But the old developer have written different ways and I can not change it. I cannot find anything for this.
My codes
Sidebar elements
const _nav = [
{
_tag: "CSidebarNavItem",
name: "Chat",
to: "/chat",
filter: "feedbacks",
icon: "cil-speech",
badge: {
color: "info",
text: "NEW MESSAGE",
},
},
]
My React component
import navigation from "./_nav";
const [filteredNav, setFilteredNav] = useState([]);
const [chatBadge, setChatBadge] = useState(false);
const handleChatBadge = () => {
setChatBadge(true)
}
// it is a sidebar element for user-role
useLayoutEffect(() => {
allwedMenu().then((res) => {
const arr = [navigation[0]];
res.data.items.forEach((element) => {
arr.push(element.name);
});
setFilteredNav(navigation.filter((item) => arr.includes(item.filter)));
});
}, []);
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
</CSidebarNav>
I need the badge to show when chatBadge is true. But I don't know how can I write this.
You can only add a condition to show the badge when chatBadge is true.
Based on the Value of ChatBadge, you can use the property of the Component CSideBarNavItem to display the badge and pass the colour and text properties.
Here's the updated code:
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
{filteredNav.map((item, index) => (
<CSidebarNavItem
key={index}
name={item.name}
to={item.to}
icon={item.icon}
badge={
item.name === "Chat" && chatBadge
? { color: "info", text: "NEW MESSAGE" }
: null
}
/>
))}
</CSidebarNav>
Hope it helps.

React changing the states of the whole elements in the map function, not only the button that is pressed

Hello everyone, I am trying to passing a method through a context api component to another component which, i have a map function there. I want my showInfo state changes to true or false depending on the button clicking, when i clicked the button, all the showInfo's of my states is changes, so thats not what i want, I want that specific item to change when i press to it. Can someone explaine where is the mistake that i've made?
MY CONTEXT APİ
import React from "react";
export const ToursContext = React.createContext();
class ToursContextProvider extends React.Component {
constructor(props) {
super(props);
this.changeState = this.changeState.bind(this);
this.state = {
tours: [
{
id: 0,
imageURL:
"https://images.unsplash.com/photo-1524231757912-21f4fe3a7200?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1351&q=80",
title: "İstanbul'un Güzelliğinin Sadece Bir Parçası Galata Kulesi",
showInfo: true,
info: "LOREM İPSUM AMET 1",
},
{
id: 1,
imageURL:
"https://images.unsplash.com/photo-1541432901042-2d8bd64b4a9b?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1319&q=80",
title: "Tarihi Süleymaniye Camii",
showInfo: true,
info: "LOREM İPSUM AMET 2",
},
],
};
}
changeState(itemdelete) {
this.setState({
showInfo: !this.state.showInfo,
});
console.log(itemdelete);
}
render() {
return (
<ToursContext.Provider
value={{ ...this.state, changeState: this.changeState }}
>
{this.props.children}
</ToursContext.Provider>
);
}
}
export default ToursContextProvider;
MY MAP LIST COMPONENT
import React from "react";
import { ToursContext } from "../contexts/Tours";
function Tours() {
return (
<div className="container">
<div className="row">
<ToursContext.Consumer>
{(value) => {
const { changeState } = value;
return value.tours.map((item) => (
<div className="col-md-4" key={item.id}>
<div className="card bg-dark text-white">
<img src={item.imageURL} className="card-img" alt="..." />
<div className="card-img-overlay">
<h5 className="card-title">{item.title}</h5>
<button
type="button"
onClick={changeState.bind(this, item)}
className="btn-sm btn-primary"
>
Bilgiyi Göster!
</button>
</div>
{value.showInfo ? "true" : "false"}
</div>
</div>
));
}}
</ToursContext.Consumer>
</div>
</div>
);
}
export default Tours;
You state is atomic. This means that it is treated as a single value. With classes, you have option to modify state object partially. For example, you have object with fields a and b. You can change both fields at once, only a or only b. But there is no option to modify state deeply. Let's imagine that you have state object like this:
{
"a": { "subfield_1": [], "subfield_2": "some string"},
"b": 3
}
You again, can modify a or b, but if you want to add item into array a.subfield_1 or change a.subfield_2, you will have to modify whole a, like this:
setState({
a: {
...a,
subfield_1: this.state.a.subfield_1.concat("new item"),
},
});
In you case, to change something inside tours key, you will have to modify whole tours key. It would be something like this:
changeState(itemdelete) {
this.setState({
tours: tours.map((item) =>
item.id !== itemdelete.id ? item : { ...item, showInfo: !item.showInfo }
),
});
}

React: Handling mapped states

I'm very new to coding and trying to figure out an issue I have come across.
I am using axios to pull a json file and store it in a state. (I am also using Redux to populate the form)
Then I am using .map() to dissect the array and show one value from within each object in the array.
example json:
unit :
[
{
designName : x,
quantity : 0,
},
{
designName : y,
quantity : 0,
},
{
designName : z,
quantity : 0,
}
]
I have then added an input to select the quantity of the value mapped and now I want to give that value back to the state, in order to send the entire modified json back to the API with Axios.
I feel like I'm close but I'm unsure what I need to do with the handleQuantity function.
Here's my code:
import React, { Component } from 'react';
import store from '../../redux_store'
import axios from 'axios';
import { Button, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
store.subscribe(() => {
})
class developmentSummary extends Component {
constructor(props) {
super(props)
this.state = {
prjName: store.getState()[0].developmentName,
units: []
}
}
componentDidMount() {
axios.get('https://API')
.then(
res => {
console.log(res)
this.setState({
units: res.data.buildings
})
console.log(this.state.units.map(i => (
i.designName
)))
}
)
}
handleQuantity() {
}
render() {
return (
<>
<div className="Text2">
{this.state.prjName}
</div>
<div className="Text2small">
Please select the quantity of buildings from the list below
</div>
<ul>
{this.state.units.map((object, i) => (
<div className="Card-center">
<Card key={i} style={{ width: "50%", justifyContent: "center" }}>
<Card.Body>{object.designName}</Card.Body>
<Card.Footer>
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={this.handleQuantity}
/>
</Card.Footer>
</Card>
</div>
))}
</ul>
Thanks in advance!
You have to pass the change event, unit object and the index to handleQuantity and then paste your changed unit as new object in between unchanged units.
Here is the code:
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={(event) => this.handleQuantity(event, object, i)}
/>;
And the code for handleQuantity
handleQuantity = (event, unit, index) => {
const inputedNumber = +event.target.value; // get your value from the event (+ converts string to number)
const changedUnit = { ...unit, quantity: inputedNumber }; // create your changed unit
// place your changedUnit right in between other unchanged elements
this.setState((prevState) => ({
units: [
...prevState.units.slice(0, index),
changedUnit,
...prevState.units.slice(index + 1),
],
}));
}

How can I display dynamic state in react

I create a state dynamically, but I need use this state in my render.
See my issue:
handleChange = (name, checked) => {
this.setState({
[name]: checked,
})
}
So, in my render how can I display my [name] state? (I don't know his name, since it's dynamic)
render(){
console.log(this.state....?)
return(
<p>Hello</p>
)
}
My function handleChange is called when I checked my checbkox. So, if I have 5, 10, 20 checkboxes how can I display my [name] state?
----> UPDATE - FULL CODE <----
I'm using a hover propriety to display my checkbox:
CSS using material ui:
hideCheckbox: {
display: 'none',
},
showCheckbox: {
display: 'initial',
},
My main class:
export class Item extends Component {
state = {
isHovering: true,
checkboxChecked: false,
}
handleGetCardSelected = (id, checked) => {
//Here I set isHovering to display my checkbox
//checkboxChecked is a control variable to always display the checkbox if it's checked
if(checked){
this.setState({
isHovering: !this.state.isHovering,
checkboxChecked: true,
})
} else {
this.setState({
checkboxChecked: false,
})
}
}
handleMouseHover = () => {
if(!this.state.checkboxChecked){
this.setState(this.toggleHoverState);
}
}
toggleHoverState = (state) => {
return {
isHovering: !state.isHovering,
};
}
return(
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
)
}
My CheckboxCard:
import React from 'react';
import { makeStyles, withStyles } from '#material-ui/core/styles';
import { Checkbox } from '#material-ui/core';
const GreenCheckbox = withStyles({
root: {
color: '#43a04754',
'&$checked': {
color: '#43a047',
},
'&:hover': {
color: '#43a047',
backgroundColor: 'initial',
},
},
checked: {},
})(props => <Checkbox color="default" {...props} />);
export default function CheckBoxCard(props){
const [state, setState] = React.useState({
idItem: false,
});
const handleCheckbox = name => event => {
setState({ ...state, [name]: event.target.checked });
let id = name
let checked = event.target.checked
props.handleGetCardSelected(id, checked)
};
return(
<GreenCheckbox
checked={state.idItem}
onChange={handleCheckbox('idItem')}
value="idItem"
inputProps={{
'aria-label': 'primary checkbox',
}}
/>
);
}
By default, my checkbox is hidden because my state isHovering is true, so the css variable hideCheckbox ('display: none') is set.
If I hover the element, is called handleMouseHover and the checkbox is displayed!
If I checked my checkbox, is set checkboxChecked for true and now I'm always displaying my checkbox! But, if I've two or more elements, all checkbox is displayed, because checkboxChecked is an unique element!
So, my checkboxChecked must be dynamic and per item! In this way, if is checked, only this checkbox will be displayed. The others no!
For first element: ${this.state.checkboxCheckedITEM1 && classes.showCheckbox}
For second element: ${this.state.checkboxCheckedITEM2 && classes.showCheckbox}
For second element: ${this.state.checkboxCheckedITEM3 && classes.showCheckbox}
I update my code in sandbox: https://codesandbox.io/embed/material-demo-16t91?fontsize=14
How can I do that?
So, if I have 5, 10, 20 checkboxes how can I display my [name] state?
I think the smart way to do this is to nest components. Have an abstract parent component, that only renders the child component once the name is available. By using this pattern your problem can be solved.
Check out this helpful article
You can filter your object to get only checked names.
Object.keys(this.state).filter(name => this.state[name]);
If you have a finite list of checkbox component
render() {
return (
<div>
<Checkbox
handleChange={() => this.handleChange('abc', !this.state[abc])}
className={this.state['abc'] ? 'checked-class' : '' }
/>
</div>
)
}
In another situation whereby you render a list of dynamic checkbox.
componentDidMount() {
//Calling API to return dynamic list of checkboxes, and dump them into a state
this.setState({ listOfCheckboxes });
}
Then in your render method, you will be using .map function
render() {
return (
<div>
{
this.state.listOfCheckboxes.map(x => (
<Checkbox
handleChange={() => this.handleChange(x.name, !this.state[x.name]) }
className={this.state[x.name] ? 'checked-class' : '' }
/>))
}
</div>
)
}
By doing so :
this.state[name]
You can look at Object.keys(), but if in future you would need to handle more than one state, iteration does not seem to solve the problem.
You could also give it a prefix [name_${name}]: checked, but would not recommend that at all.
If it is not a problem, use an object and you will have full controll over it.
this.setState({
something: {
name,
checked
},
})

Uncaught TypeError: Cannot read property 'icon' of null

i have a form for editing the tab. When a edit icon is clicked to edit that tab a form in dialog box appears where the input box has current data in it. But when i hit save without touching the icon field i get an error of Uncaught TypeError: Cannot read property 'icon' of null. If i did not touch the name field and only touch on icon field and hit save button then the tab gets edited. How can i make icon field work too like name field is working ? I mean if i want to only edit name, i can edit the name from name field and save without touching icon field which will save the tab name with edited name and current icon.
How can it be possible?
class EditForm extends Component {
render() {
const { tab } = this.props;
console.log('tab object is', this.props.tab);
const listOfIcon = _.map(this.props.fetchIcon.icons, (singleIcon) => ({
text: singleIcon.name,
id: singleIcon.id,
value: <MenuItem primaryText={singleIcon.name} />
}));
return (
<div>
<form
onSubmit={(e) => {
console.log('auto', e.target.auto);
e.preventDefault();
this.props.editTab(
tab.id,
e.target.text.value,
this.state.icon
);
this.props.closeTabIcon();
}
}
>
<div className="tab-name">
<TextField
hintText={tab.name}
name="text"
defaultValue={tab.name}
hintStyle={{ display: 'none' }}
floatingLabelStyle={{ color: '#1ab394' }}
floatingLabelFocusStyle={{ color: '#1db4c2' }}
underlineStyle={{ borderColor: '#1ab394' }}
/>
</div>
<div className="icon">
<AutoComplete
floatingLabelText={tab.icon}
name="auto"
filter={AutoComplete.noFilter}
openOnFocus
dataSource={listOfIcon}
textFieldStyle={{ borderColor: '#1ab394' }}
className="autocomplete"
onNewRequest={(e) => { this.setState({ icon: e.id }); }}
/>
</div>
<button className="btn">Save</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state);
return {
fetchIcon: state.fetchIcon,
tabs: state.tabs.tabs.map(tab => {
const icons = state.fetchIcon.icons.find(icon => Number(icon.id) === tab.icon);
return {
...tab,
icon: icons && icons.name
};
})
};
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({
editTab,
closeTabIcon
}, dispatch);
}
The state of a componnet is intitated with the null. YOu can set the intital value of state in constrocutor of the class
class EditForm extends Component {
constructor(props) {
super(props)
this.state ={}
}
render() {
const { tab } = this.props;
console.log('tab object is', this.props.tab);
const listOfIcon = _.map(this.props.fetchIcon.icons, (singleIcon) => ({
text: singleIcon.name,
id: singleIcon.id,
value: <MenuItem primaryText={singleIcon.name} />
}));..........
initialize 'input box' with empty value from code behind.

Categories

Resources