Highcharts Treemap can't update data properly on React JS - reactjs

I'm having difficulties with Treemap on Higcharts. Currently I'm getting my data with Redux and loading it into Treemap's options with no problem. The original data is an array with 10 items.
The problem occurs when I click on a node, and I do a dispatch to get new data and update the Treemap, if the new data comes with 10 items it will load no problem (just like the parent). But if it has a different length it shows me the following error
TypeError: Cannot read property 'mouseover' of undefined
createChart();
53 | } else {
54 | if (props.allowChartUpdate !== false) {
> 55 | if (!props.immutable && chartRef.current) {
| ^ 56 | chartRef.current.update(
57 | props.options,
58 | ...(props.updateArgs || [true, true])
I can't seem to find what can be.
EDIT: To illustrate more my problem, here's part of my code. I have a main container, a Highcharts component which receive the data as props, a Highcharts custom hook that creates the proper series data based on a Redux Payload.
Main Container has the Treemap container that gets chartSerie props which contains the data to load.
<GridWrapper item xs={12} md={10}>
<TreemapChart serie={chartSerie} idEntity={6927} year={yearsValue} />
/GridWrapper>
The Treemap component initially loads the Treemap perfect, and has this function to dispatch a Redux action and load new data.
events: {
click: function (event) {
dispatch(onTradedSegmentsThunk(idEntity, year, event.point.node.parent))
}
}
The useTreemap hook gets the payload data from the Redux state and creates the proper structure to pass it as chartSerie
const selectedData = segment.length >= 1 ? segment : data
const sortedArray = selectedData.sort((a, b) => segment.length >= 1 ? a.amountFamily < b.amountFamily : a.amountSegment < b.amountSegment)
const newArray = sortedArray.map((item, index) => (
{
id: AlphaId[index],
name: segment.length >= 1 ? item.family : item.segment,
parent: index + 1,
realValue: segment.length >= 1 ? item.amountFamily : item.amountSegment,
value: serieValue[index]
}
))
setSerie(baseSerie.concat(newArray))
}, [data, segment])
return { serie }
What's weird to me is the 'mouseover' property which can't seem to find out why the error mentions it. But I think the problem is that the new data as different length as the first one that gets rendered.

Related

React - problem updating an array of objects using hooks

I have tried a bunch of approaches but on this, but it's still bugging the hell out of me. I am using React 17.0.1 in this project.
I have a array of objects formatted like so:
gameNumberFields: [{ name: 'gameNum', placeholder: 'GGG', size: '3', value: '', dataindex: '0' }];
For now, there is just one object in the array, but there is always the possibility of more down the road (hence why it's an array).
In the code - this field is pre-populated on initialization - so the "value" of the first index in the array might be something like "123". I use initialState to make this happen:
const [gameNumberFields, setGameNumberFields] = useState(scratchTicketFields?.gameNumberFields ?? []);
When the display is shown to the user - this value is shown to the user in an field using the defaultValue.
return gameNumberFields.map((field, index) => {
const ref = React.createRef();
elemRefs.push(ref);
return (
<div className='d-inline-block ticket-number-inputs' key={`game-numbers--${index}`}>
<input
type='text'
id={field.name}
data-index={field.dataindex}
ref={ref}
className='theme-input'
placeholder={field.placeholder}
size={field.size}
maxLength={field.size}
defaultValue={field.value}
onBlur={(event) => handleGameNumberChange(event, field)}
onKeyUp={(event) => autoTab(event, elemRefs)}
required
/>
<span className='dash'>—</span>
</div>
);
});
}; // end GameIdFieldInputs
So far - so good. The problem I am having is in the onBlur event handler. For some reason - when the user changes the value to something else - it always goes back to the old value.
Here is the handler:
const handleGameNumberChange = async (event, field) => {
// get gameNum from the event target
const gameNum = event.target.value; // say this becomes 999
// do a deep copy of the gameNumberField state.
let gameIdField = JSON.parse(JSON.stringify(gameNumberFields));
// check that we are changing the right index in the array
const fieldIndex = gameIdField.findIndex((obj) => obj.name == field.name);
// make a new object changing the value to 999
let newGameObject = { ...gameIdField[fieldIndex], value: gameNum };
console.log('newGameObject', newGameObject);
//NOTE: At this point the newGameObject is correct and updated with the NEW gameNum of 999
// create a new array and PUSH the new game object onto it
let newGameIdArray = new Array();
newGameIdArray.push(newGameObject);
// Once pushed the array has the OLD game number in it . . . so 123 - WHY?!?!
console.log('newGameObjectArray', newGameIdArray);
setGameNumberFields(newGameIdArray); // updates with the 123 game number. . .
}; // end handleGameNumberChange
So in the method, I deep copy the gameNumberFields into a mutable object. I then update the object with the new gameNumber (from 123 to 999) and all works when I print it out with my console.log for newGameObject.
As soon as I push this object in the new Array - it changes back to 123! Can anyone see a flaw in my code here?
When I finally do call setGameNumberFields - it does set the state (I have a useEffect that prints out the values) but again, its always the OLD values.
Any help is welcome and appreciated!

How would I re-render the list everytime the state changes for this text filter using react hooks

I have updated this question with clearer and more concise code on 15/03/22.
I have a text filter and it filters an array (displaying a list) correctly if it matches something in the displayed list, but as the user deletes character by character the filter does not change. I need it to keep matching as the user delete's chars.
Here is the filter which is set as the onChange for the text field:
const [searchInputTitle, setSearchInputTitle] = useState<string>('');
const [filteredItemsArrayState, setFilteredItemsArrayState] = useState<IListItems[]>(props.requests);
const searchTitles = () => {
let filteredArrayByTitle: IListItems[] = [];
filteredArrayByTitle = theRequestsState.filter((item) => {
return item.Title && item.Title.toLowerCase().indexOf(searchInputTitle.toLowerCase()) >= 0;
});
console.log(searchInputTitle, 'searchInputTitle');
if (searchInputTitle && searchInputTitle.length > 0) {
setTheRequestsState(filteredArrayByTitle);
setIsFiltered(true);
} else if (searchInputTitle && searchInputTitle.length === 0) {
const AllItems = props.requests;
let sortedByID: IListItems[] = AllItems.sort((a, b) => a.Id > b.Id ? 1 : -1);
setTheRequestsState(sortedByID);
setIsFiltered(false);
}
};
<TextField
onChange={searchTitles}
value={searchInputTitle}
/>
useEffect(() => {
_onTitleFilterChange(null, searchInputTitle);
if (isFiltered == true) {
setFunctionalArea(null, null);
setRequestingFunction(null, null);
}
}, [isFiltered, searchInputTitle]);
////////
<DetailsList className={styles.DetailsList}
items={filteredItemsArrayState.slice((ListPage - 1) * 50, ((ListPage * 50)))}
/>
Can anyone see why the render is not updating on deletion of char and what I could use to do so?
Update: As I type a character into the search I can see it's finding the searched for char/word and also if I delete chars now it searches and finds actively, but as soon as I stop typing it reverts back to the original array of items.
Can you try to filter the array you give to the DetailsList so you never lost the data ..?
filteredItemsArrayState.filter(s => {
if (searchInputTitle.length === 0)
return true
else
return s.Title.toLowerCase().match(searchInputTitle.toLowerCase())
}).map(....)
Found the reason. Thanks for all your help.
The issue was the setIsFiltered(true); in the title filter function. Other filters were running based on this line and these other filters were recalling the unfiltered list everytime a key was pressed. I removed this line and the issue was fixed.
I have come to realise that useEffect is almost completely mandatory on most of my projects and is React hooks are quite a departure from the original React syntax.

array variable undefined when passing into React child component

I am trying to retrieve props from an internal api for a component, PresetFetcher, and pass them to a child component, PromptForm. My console logs show that the data is pulled from the API correctly and saved into an object {preset} which has the correct data. But no matter what I do, when I try to pass {preset} to the child component, it reports null value and fails to compile. What is wrong here? It must be some very basic misunderstanding on my part.
To keep it simple, I am avoiding state or effect, since the data does not need to be refreshed once the PromptForm receives it.
import axios from 'axios'
import { useParams } from 'react-router-dom'
import React, { useEffect,useState } from 'react'
import PromptForm from './PromptForm';
const API_SEARCH_BASE = 'http://0.0.0.0:5001/api2/preset_loader';
function PresetFetcher(props) {
const { preset_name } = useParams();
const API_SEARCH_URL = API_SEARCH_BASE + '/' + preset_name;
console.log('API_SEARCH_URL: ' + API_SEARCH_URL);
console.log('props entering PresetFetcher page', props);
const test = [{ name: 'item 1' }, { name: 'item2' }];
axios.get(API_SEARCH_URL)
.then
(response => {
console.log('PresetFetcher: response: ', response.data);
const preset = response.data;
console.log('preset after const', preset);
var preset_description = preset[0].preset_description;
console.log('preset_description: ', preset_description);
})
return (
<div> <PromptForm preset_out = {preset_description} /></div>
)
{console.log('props leaving Presetfetcher: ', props)};
}
export default PresetFetcher;
[HMR] Waiting for update signal from WDS...
index.js:8 Array(1)
launcher.js:11 on LauncherPage, preset name is baby-name-generator
launcher.js:12 props on launcherpage are Object
PresetFetcher.js:13 API_SEARCH_URL: http://0.0.0.0:5001/api2/preset_loader/baby-name-generator
PresetFetcher.js:14 props entering PresetFetcher page Object
PresetFetcher.js:13 API_SEARCH_URL: http://0.0.0.0:5001/api2/preset_loader/baby-name-generator
PresetFetcher.js:14 props entering PresetFetcher page Object
PresetFetcher.js:21 PresetFetcher: response: Array(1)
PresetFetcher.js:23 preset after const Array(1)
PresetFetcher.js:25 preset_description: Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.
PresetFetcher.js:21 PresetFetcher: response: Array(1)
PresetFetcher.js:23 preset after const Array(1)0: {id: "2", preset_name: "Baby Name Generator (by Attributes)", preset_author: "WebBabyShower.com", preset_active: "True", preset_launched: "20210610", …}length: 1[[Prototype]]: Array(0)
PresetFetcher.js:25 preset_description: Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.
ReferenceError: preset_description is not defined
PresetFetcher
src/components/PresetFetcher.js:28
25 | console.log('preset_description: ', preset_description);
26 | })
27 | return (
> 28 | <div> <PromptForm preset_out = {preset_description} /></div>
| ^ 29 |
30 | )
31 | {console.log('props leaving Presetfetcher: ', props)};
the API data looks like this. there is only one item in the list.
[
{
"id": "2",
"preset_name": "Baby Name Generator (by Attributes)",
"preset_author": "WebBabyShower.com",
"preset_active": "True",
"preset_launched": "20210610",
"preset_description": "Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.",
"preset_instructions":
That variable is not available in the scope you're calling it. Try moving it outside the then block. I suggest storing it in local state so the component updates when the fetch completes.
const [presetDescription, setPresetDescription] = useState('')
axios.get(API_SEARCH_URL)
.then
(response => {
const preset = response.data;
setPresetDescription(preset[0].preset_description);
})
<PromptForm preset_out={presetDescription} />
You can use the "useState" hook as mentioned in the above answer or you can also use the 'useRef' hook as it can be used to hold values similar to an instance property on a class. You can read more about it here.
To use it in this case, you can do something like this:-
const presetDescription = useRef('');
axios.get(API_SEARCH_URL)
.then
(response => {
const preset = response.data;
presetDescription.current = preset[0].preset_description;
})
<PromptForm preset_out={presetDescription.current} />

Call component method when model property changes

In my Fable app with Elmish I have a view that uses react-slick and a button that should be able to change the slide number on click:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch) ]
children
Button.button
[ Button.OnClick (fun _ev -> dispatch (SlideTo 5)) ]
[ str "Go to slide 5" ]
The react component for the slider is defined by react-slick.
The fable wrapper I had to write on my own, so it's not complete because I only defined the properties I need.
module Fable.Import.Slick
open Fable.Core
open Fable.Core.JsInterop
open Fable.Helpers
open Fable.Helpers.React.Props
open Fable.Import.React
type SliderProps =
| InitialSlide of int
| AfterChange of (int -> unit)
interface IHTMLProp
let slickStyles = importAll<obj> "slick-carousel/slick/slick.scss"
let slickThemeStyles = importAll<obj> "slick-carousel/slick/slick-theme.scss"
let Slider = importDefault<ComponentClass<obj>> "react-slick/lib/slider"
let slider (b: IHTMLProp list) c = React.from Slider (keyValueList CaseRules.LowerFirst b) c
So while react-slick defines a property InitialSlide to set the slide that should initially be shown, there's no property to update the slide afterwards. There is a method slickGoTo that should do what I want. But I don't know how or where to call that method while still being compliant with Elmish.
I could imagine that I have to extend the react component and listen to the model property and then call slickGoTo whenever that property changes. But I don't know if that's possible.
So my question is: How can I have a button that changes the slide number on click using slickGoTo that is defined by the component while not hacking the Elmish architecture?
An alternative to storing the reference to the slider object in the model is to use a mutable variable:
let mutable sliderRef : Browser.Element option = None
let gotoSlide n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
the rest is similar:
type Msg =
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide n ; model, []
Create your slider like this:
slider
[ Ref (fun slider = sliderRef <- Some slider)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
To be able to call the method slickGoTo you need to get a reference to the slider object. To do that use the Ref prop that expects a function of type (Browser.Element -> unit). It gets called once. Save that element somewhere, perhaps in your model:
type Model = {
...
slideNumber : int
sliderRef : Browser.Element option
...
}
let gotoSlide sliderRef n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
That way you can call gotoSlide from your update function.
type Msg =
| SetSliderRef of Browser.Element
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| SetSliderRef e -> { model with sliderRef = Some e } , []
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide model.sliderRef n ; model, []
...
Create your slide like this:
slider
[ Ref (SetSliderRef >> dispatch)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
I have not tested any of this, so take with a grain of salt.
I just found that within React you previously solved such things using componentWillReceiveProps - that sounds a lot like what I want IMO. But this is now obsolete and there are two recommendations based on my usage:
If you used componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
I don't think I can implement the Fully controlled version (this has to be done by the author of the component, right?), but the fully uncontrolled with a key seems to work. So my slider view now looks like this:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch)
Key (string model.SlideNumber) ]
According to the docs everytime the Key changes, the component is recreated. Now this sounds like a lot of overhead and has its own drawbacks (animating the slide doesn't work) but currently it's the simplest solution. Any concerns?

Filter React Native Data

I searched all day but I didn't find the solution to my problem.
I'm new to React Native so I'm stucked ...
Here's my problem :
I fetch an api to the get data, I'm doing it with axios and it's ok I can render my component !
Then I don't want to display ALL ITEMS of my Array but I want to display some items according to different values with 2 buttons :
<Button onPress={Get ALL ITEMS with this value}/>
<Button onPress={Get ALL ITEMS with ANOTHER VALUE} />
My issue is that I'm modifying the state of my array 'products' with new information but that's not what I want. I always want that I'm displaying information according to a particular VALUE in the Array and still get my Full Array...
Here's some piece of code of what I'm doing :
onPressFirstButton() {
let newArray = [...this.state.products];
this.setState({
products: newArray.filter(function(product){
return product.value == 32;
})
});
}
onPressSecondButton() {
let otherArray = [...this.state.products];
this.setState({
products: otherArray.filter(function(product){
return product.other_value == 389;
})
});
}
I'm sure it's not the best way because it's not working.. I hope you will understand what I'm trying to say..
Thank you
Since you seem to be using the products property to populate your template, consider using a separate variable for that instead, that way you don’t have to mess with your state when doing your filtering. Maybe this will help:
displayProducts = []
onPressFirstButton () {
this.displayProducts = this.state.products.filter(product => /* logic */)
}

Resources