React: Uncaught TypeError: Cannot read property 'localeCompare' of undefined - reactjs

I'm building an app where I have a Home page where I render data that I'm getting from an API in the form of cards, and a Favorites page, where the cards marked as favorite are displayed. I'm using React Router, and I have a Topbar component with a Nav and a Search, the latter is utilizing the grouped materialui autocomplete component.
In the Search component I'm trying to check whether the user is in the homepage or in the favorites page, in order to use 2 different arrays. The original array of data for the homepage, and the one with the favorited items for the favorites page and I'm getting the above error. I'm passing both arrays as props, and the search component initially worked with the original beers array. I'm not sure what I'm doing wrong, the error message is not helping me to identify the problem.
This is the logic that I wrote to differentiate among the pages:
let options;
document.location.pathname === '/favorites'
? (options = favoriteBeers)
: (options = beers);
And this is the code for the autocomplete component:
options.map(option => {
const firstLetter = option.name[0].toUpperCase();
return {
firstLetter: /[0-9]/.test(firstLetter) ? '0-9' : firstLetter,
...option,
};
});
<Autocomplete
options={options.sort(
(a, b) => -b.firstLetter.localeCompare(a.firstLetter)
)}
groupBy={option => option.firstLetter}
getOptionLabel={option => option.name}
getOptionSelected={(option, value) => option.id === value.id}
clearOnEscape
style={{ width: 400, margin: 'auto' }}
onChange={(e, value) => handleOpen(value)}
renderInput={params => (
<TextField {...params} placeholder='Search for beer...' />
)}

map returns a new array, this way you should assign its return value to some variable. also you should always spread your object first, otherwise it may overwrite the changes you are performing:
options = options.map(option => {
const firstLetter = option.name[0].toUpperCase();
return {
...option,
firstLetter: /[0-9]/.test(firstLetter) ? '0-9' : firstLetter,
};
});

Related

I don't get the expected value of a react props in child component

I'm trying to manage some clients in a react js application (that I'm maintaining), it was first written in classes components but I'm now adding a functional child component and im not sure if this is the source of the problem or not (react-table examples only use functional component)
I have a main component that will do the data GET from a rest API and save it in state "entries" then I passe it to a child component as a props to render the data in a react-table, the problem is in this section as I have some buttons to edit and delete the data in react-modal, when I try access the props.entries after the buttons clicks I have an empty array of props.entries.
Here's the sandbox of the issue : https://codesandbox.io/s/stale-prop-one-forked-r6cevx?file=/src/App.js
I did a console.log when the delete button is clicked, and you can see that en entries array is empty.
You need to pass the showEditModal & showEditModal in useMemo dependency array. Since you dependency array is empty, when you click on edit or delete, it just points to the old function reference and it have the old entries value.
You can check the entries values by console.log.
Hope this solve your problem
const showEditModal = useCallback(
(client_id) => {
const tmpClient = props.entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setEditModal(true);
console.log('aaa', props);
console.log(client_id);
console.log(props.entries);
console.log(tmpClient);
},
[props.entries]
);
const showDeleteModal = useCallback(
(client_id) => {
console.log('showDeleteModal entries : ', entries);
const tmpClient = entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setDeleteModal(true);
console.log('Delete', entries);
console.log(client_id);
console.log(tmpClient);
},
[props.entries]
);
const columns = React.useMemo(
() => [
{
Header: 'fact',
accessor: 'fact'
},
{
Header: 'Actions',
accessor: 'length',
Cell: ({ cell }) => (
<>
{cell.row.values.length}
<Tooltip title='Supprimer' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values._id}
onClick={() => showDeleteModal(cell.row.values.length)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
<Tooltip title='Modifier' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values.length}
onClick={() => showEditModal(cell.row.values.length)}
>
<EditIcon />
</IconButton>
</Tooltip>
</>
)
}
],
[showEditModal, showDeleteModal]
);

useEffect removes state and then resets? React

I have a component made with React Select. The options passed into the options prop on the Select depends upon state that users entered previously. Every time the component renders there are checks to see if selectOptions already includes items from the state array
<Select
styles={err === '' ? inputStyles : inputStylesErr}
className="basic-single"
classNamePrefix="select"
isClearable={true}
isSearchable={true}
isMulti={true}
placeholder={`Select or search health zones in ${province}, ${state.address.country}`}
options={selectOptions}
defaultValue={selectOptions.some((option) => option.value === state.healthZonesServed[0]) ? (
state.healthZonesServed.map((zone) => {
return { ['label']: zone, ['value']: zone }
})
) : ''}
onChange={(values) => handleAddHealthZones(values.map((value) => value.value))}
/>
const handleAddHealthZones = (value) => {
setState({
...state,
healthZonesServed: value
})
}
If a user populated their healthZonesServed array and then goes back and changes their province (the piece of state which controls the selectOptions) and then goes back to this component I need the healthZonesServed array to be reset to []
I do this in a useEffect. I can see in my console.log the healthZonesServed resets to an empty array on page load then somehow re-populates its previous values from somewhere. Would anyone have any insight as to why this is happening and a possible solution?
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return
} else {
setState({
...state,
healthZonesServed: []
})
console.log('HIT')
}
}, [])
The most probable reason, why this is not working is because you are using setState in a functional component. Try using the useState hook for the purpose of managing the state, in your case, setting the heathZoneServed array to empty array.
const [healthZoneServed,sethealthZoneServed] = useState([]);
sethealthZoneServed(value);
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return;
} else {
sethealthZonesServed([]);
console.log('HIT');
}
}, [healthZonesServed]);
Hope this was helpful.

How to get the value of selected option from React hooks?

I have this React Component like below:
const ProductCell = (props) => {
const [option, setOption] = useState();
return(
<div>
<NativeSelect
value={option}
onChange={e => setOption(e.target.value)}
>
{props.product.variations.nodes.map( // here I extracted all the item
(item, i) => (
<option value={item} key={i}>{item.name}</option> // set option value to item object
))
}
</NativeSelect>
<Typography variant="h5" className={classes.title}>
{option.price} // I want to update the value of price according to the selected option.
</Typography> // according to the selected option above
</div>
)
}
I have a NativeSelect component which is from React Material-Ui, so basically it is a Select html tag. In the code above, what I do is, extract all the element inside props.product.variations.nodes and put all the extracted item and put each of the element into a <options/> tag.
The Json object for item will look like this:
"variations": {
"nodes": [
{
"id": "someId",
"name": "abc1234",
"variationId": 24,
"price": "$100.00"
},
{
.. another ID, name,variation and price
}
]
}
As you can see, I targeting the part of id, name , variationId and price as an object. Therefore each <option/> tag will present with item.name as the presentation to user. So far in this part having no problem, let say having 5 variations, and can present all of them.
What I want to do is:
I want to update the value of price under the <Typography /> component. Example, user selected 3rd options in the Select, I want to update the price value of the 3rd item in <Typography /> .
What I tried:
I create a react hooks const [option, setOption] = useState(); , then when handleChange, I setOption() with event.target.value in NativeSelect component . Therefore the value of <option /> tag is set as item object.
Lastly, I get the price value from the hooks in the Typography section.
But what I get is:
The price value is undefined in console log. So I can't get the value of option.price.
and this error:
TypeError: Cannot read property 'price' of undefined
Question:
How can I get the option.price value(which I expect it is same with item.price) outside the NativeSelect component in my above example?
I tried my best to explain based on what I understand by this time being. So any help will be well appreciated.
Update:
Here is what I got when console log the item object in variation.node.map() section and data object inside onHandleChanged section, but also produce the same result:
You have to set a default selected option on your ProductCell component. Also your onChange handler will receive a string instead of an object when you access the value on event.target.value.
From the docs
function(event: object) => void event: The event source of the callback. You can pull out the new value by accessing event.target.value (string).
event.target.value will be a string even though you pass the value as object on NativeSelect component.
What you might want to do? Don't set the current selected item as an object, instead use the id and have a function that look-ups the item using the current selected id.
Check the code below.
const ProductCell = (props) => {
const { variations } = props.product;
const { nodes } = variations;
// we're setting the first node's id as selected value
const [selectedId, setSelectedId] = useState(nodes[0].id);
const getSelectedPrice = () => {
// finds the node from the current `selectedId`
// and returns `price`
const obj = nodes.find((node) => node.id === selectedId);
return obj.price;
};
function onChange(event) {
// event.target.value will be the id of the current
// selected node
setSelectedId(parseInt(event.target.value));
}
return (
<div>
<NativeSelect value={selectedId} onChange={onChange}>
{nodes.map((item, i) => (
<option value={item.id} key={i}>
{item.name}
</option>
))}
</NativeSelect>
<Typography variant="h5">{getSelectedPrice()}</Typography>
</div>
);
};
Also notice that were passing the id as a value prop on each of our options.
<option value={item.id} key={i}>
And how we now display the price, we're calling our getSelectedPrice().
Update
I thought a better solution. I realized that you can set your selected state as an object and on your onChange handler given the id from event.target.value find the item on nodes and set that as your new selected state.
const ProductCell = (props) => {
const { variations } = props.product;
const { nodes } = variations;
const [selected, setSelected] = useState(nodes[0]);
function onChange(event) {
const value = parseInt(event.target.value);
setSelected(nodes.find((node) => node.id === value));
}
return (
<div>
<NativeSelect value={selected.id} onChange={onChange}>
{nodes.map((item, i) => (
<option value={item.id} key={i}>
{item.name}
</option>
))}
</NativeSelect>
<Typography variant="h5">{selected.price}</Typography>
</div>
);
};

Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:
const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [props.parameterCurrent])
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
console.log(parameterCurrent[key]);
return (
prevState => ({
...prevState,
parameterSelections: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
Swal.fire({
position: "center",
icon: "success",
title:
JSON.stringify(key) + " set to " + JSON.stringify(valKey),
font: "1em",
showConfirmButton: false,
timer: 2500,
heightAuto: false,
height: "20px"
});
})
.otherwise(function (err) {
alert(
Swal.fire({
position: "top-end",
icon: "error",
title: err,
showConfirmButton: false,
timer: 1500,
width: "16rem",
height: "5rem"
})
);
});
}
);
};
const classes = useStyles();
return (
<div>
{Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
console.log(parameterSelections[key])
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterSelections[key]}
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
}
</div >
)};
export default Parameter;
The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).
Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!
This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect
In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [parameterCurrent])
However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.
I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...
I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

ReactJS - Dynamic State

How do I dynamically create the state so I can assign 'open'/'close' based on front-end requirements?
E.g.
I have a table with rows in which I have a Fab component. That component has to set a unique ID to open/close to handle the animation. I do not know how many of these rows I will have so I can't pre-populate the state with active1, active2 etc.
Code Example (In this example I am passing 'active1', 'active2' with the object manually, but realistically that isn't a real approach I feel.
{Object.keys(characters).map((k, i) => {
<Fab
active= // how do I specify the state of 'this' instance?
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.setState({ //how so I specify 'this' instances state? })}>
...redacted
I've tried to add a sample similar to my code. Let's say I am passing in 5 Users and each User should have this Fab. At this point my state is blank so I have tried implementing a for each loop into componentdidmount to populate the state, this worked, but I had two issues based on how I managed state.
A) All fabs were open, always.
B) No fabs would ever open.
This is because of my onPress not updating the correct state.
I feel like I have to be way over-complicating this issue and I am tearing up my code to get it to work, so I figured I'd come here to ask.
Let me know what other code I can share.
A way of achieving this is to keep an activeArray in state,
which you can toggle activeness of an element buy pushing or removing the element (or just its id or some property which is unique) from activeArray.
So there should be a function on onPress like:
handlePress = (k) => {
if(this.state.activeArray.indexOf(k) > -1){
// remove element
// for example:
this.setState(state => ({activeArray: state.activeArray.filter(activeElement => activeElement !== k)}))
} else {
// push element
// for example:
this.setState(state => ({activeArray: [...state.activeArray, k]}))
}
}
Then for Fab component:
<Fab
active={this.state.activeArray.indexOf(k) > -1}
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.handlePress(k)}>
This line:
active={this.state.activeArray.indexOf(k) > -1}
means if k is inside activeArray then active is true.
You can set the dynamic state using JSON bracket ([]) notation.
{
Object.keys(characters).map((k, i) => {
let dynamicStateName = "active"+i //it will set the dynamicStateName as active with index. ie, active1, active2 and so on
return(
{ this.state[dynamicStateName] !== false || this.state[dynamicStateName] !== undefined ?
(<Fab
active= {dynamicStateName}// specify the state of 'this' instance here as dynamicStateName
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => {(this.state[dynamicStateName] != undefined) ? (this.state[dynamicStateName] ? this.setState({ [dynamicStateName]: false }) : this.setState({ [dynamicStateName]: true })) : this.setState({ [dynamicStateName]: true })} } /> // it will set the state as true if undefined. It will act as toggleable
):
( <button onPress={() => this.setState({ [dynamicStateName]: true })}>OpenFabButton<button>
// initially the dynamicState will be either undefined or false. At that time the button will be show. On clicking the button it will enable the fab component
)
)
})
}
This will fix yours

Resources