Using setState() to set more complex data - reactjs

what im trying to achieve is to be able to use the setState method to set an object as the state. However im facing some difficulty in doing so.
This is the final endproduct (state) which i was to achieve
state_class_mapping: {
{1} ,
rect: {
fill: "deepskyblue"
},
label: {
stroke: "white"
}
This values are obtained from different areas , the rect and label is preset in my state upon initializing:
constructor(props) {
super(props)
this.state = {
selected_state: null,
state_class_mapping: {},
selected_state_class: { #<---- here
rect: {
fill: "deepskyblue"
},
label: {
stroke: "white"
}
},
default_state_class: { #<---- or here depending
rect: {
fill: "#dddd"
},
label: {
stroke: "black"
}
}
}
The integer value is actually the ID of the object that i have clicked . Upon clicking onto this object , i would run some functions to set the "selected_state" .
My issue is that i have issues creating the state_class_mapping state as its more complex than just setting a static key and value.
What i would envision the way to set would be :
this.setState({state_class_mapping:{
{this.state.selected_state},
{this.state.default_state_class}
})
}
}
But ofcourse my editor shows that it is illegal to do it this way. May i know what is the proper way of doing this?

I just looked at your code and I think you missed some snippets.
First you need to declare correct object according to exact type you declared in state definition.
So in my opinion you need to try like this.
this.setState({state_class_mapping: {
idValue,
...this.state.selected_state,
...this.state.default_state_class
}});
You didn't declare state_class_mapping type as { {}, [{}]} so your code isn't working.
You declared as this type {{}, {}}
Hope this helps you to understand.

Related

Is it bad practice to access HTML elements by ID in React TypeScript?

I was told at a previous job that I should never access HTML elements directly through means like getElementById in React TypeScript. I'm currently implementing Chart.js. For setting up the chart, I was initially using a useRef hook instead of accessing context, but now it seems like I need to grab the canvas by ID in order to instantiate it properly. I want to know if this is kosher.
I suspect something is wrong with me not using a context, because my chart data doesn't load and throws a console error: "Failed to create chart: can't acquire context from the given item"
useEffect(() => {
chart = new Chart(chartRef.current, {
type: "bar",
data: {
labels: labelsArray.map((label) => {
const date = new Date(label);
// add one because month is 0-indexed
return date.getUTCMonth() + 1 + "/" + date.getUTCDate();
}),
datasets: [
{
data: valuesArray,
backgroundColor: "#1565C0",
borderRadius: 6,
},
],
},
options: {
interaction: { mode: "index" },
onHover: (event, chartElement) => {
const target = event.native.target;
(target as HTMLInputElement).style.cursor = chartElement[0]
? "pointer"
: "default";
},
plugins: {
tooltip: {
mode: "index",
enabled: true,
},
title: {
display: true,
text: "Daily Usage Past 30 Days",
align: "start",
fullSize: true,
font: {
size: 24,
},
padding: {
bottom: 36,
},
},
},
scales: {
x: {
display: false,
},
},
elements: {
line: {
borderJoinStyle: "round",
},
},
},
});
return () => {
chart.destroy();
};
}, [labelsArray, valuesArray]);
and HTML:
<div className="mt-80 ml-12 p-8 shadow-lg border-2 border-slate-100 rounded-xl items-center">
<canvas id="chart" ref={chartRef}></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</div>
Also, per the Chart.js documentation: "To create a chart, we need to instantiate the Chart class. To do this, we need to pass in the node, jQuery instance, or 2d context of the canvas of where we want to draw the chart." Not sure how we would do this with a useRef
Yes,
It is not good practice to access dom elements directly through document API.
Because in react
virtual dom is responsible for painting/ re-rendering the UI.
State updation is the proper way to tell react to trigger re-render.
The flow is state updation -> calculate differences -> find who over is using that state -> grab those components -> re-render only those components.
virtual dom is the source of truth for react to render and update actual DOM.
Now, If you directly access some dom elements and do some operation on it, like updating, react will never know that some change has happened and it will basically break the flow of the react, in which case there will be no reason to use react.js
The flow would be accessing some dom element -> updating -> displaying.
The problem with this approach if react encounters that later what i have in virtual dom is not actual presentation in the actual dom, which will create mess.
That is the reason there is useRef hook to manipulate dom.

Array to excel React

I am a little new to this, I am trying to pass my data array to an excel, but nothing works for me so far
I have an example
import {ExcelFile, ExcelSheet} from "react-export-excel";
const [listSelectNewTable, setListSelectNewTable] = useState([])
const DataExcel = [
{
columns: [
{ value: "Clave Cliente", widthPx: 50 },
{ value: "Nobre Cliente", widthCh: 20, widthCh: 20 },
{ value: "Clave Articulo", widthPx: 60},
{ value: "Nombre Cliente", widthPx: 60},
{ value: "Clave Unidad", widthPx: 60},
{ value: "Precio", widthPx: 60},
],
data: [listSelectNewTable],
}
];
class Download extends React.Component {
render() {
return (
<ExcelFile>
<ExcelSheet dataSet={DataExcel} name="Price"/>
</ExcelFile>
);
}
Can someone tell me what I'm doing wrong?
Before we begin:
Well obviously you're not going to have any data, if you're not passing any to the dataSet:
const [listSelectNewTable, setListSelectNewTable] = useState([])
// listSelectNewTable = []
Also you are attempting to call useState outside of a reactjs component and then proceed to use a class component later down the line. Without meaning to sound condescending, you really should study up on when to use life-cycle methods and when to use hooks (hint: it's either one or the other), because it's outside of the scope of this question / answer, but your code will never work if you don't put the time in to at least understand the basics.
The remainder of my answer assumes you at least fixed that.
The Problem:
Furthermore, even if it had any data, it would result in an error, because your data is incorrectly typed:
The dataSet prop type is defined as:
dataSet: Array<ExcelSheetData>
considering the following package type definitions:
interface ExcelSheetData {
xSteps?: number;
ySteps?: number;
columns: Array<string>;
data: Array<ExcelCellData>;
}
type ExcelCellData = ExcelValue | ExcelCell;
type ExcelValue = string | number | Date | boolean;
interface ExcelCell {
value: ExcelCell;
// [Note]: this is actually incorrectly typed in the package itself.
// Should be value: ExcelCellValue instead
style: ExcelStyle;
}
Essentially, if you had typescript enabled (which by the way I'd advise you to do), you'd see you're attempting to pass incorrect data-type to the dataSet property.
What you should be passing to dataSet prop is
Array<ExcellCellData>
// expecting: Array<ExcelValue | ExcelCell> (eg. ['A', 22])
What you are actually passing to to dataSet prop is
Array<Array<never>>
// received: [[]]
// [] = Array<never>
As you can see, the Array<Array<>> wraps it twice, meaning you are passing an unnecessarily wrapped array there.
Solution:
Now that we know the cause, let's fix it:
const DataExcel = [
{
columns: [
{ value: "Clave Cliente", widthPx: 50 },
{ value: "Nobre Cliente", widthCh: 20, widthCh: 20 },
{ value: "Clave Articulo", widthPx: 60},
{ value: "Nombre Cliente", widthPx: 60},
{ value: "Clave Unidad", widthPx: 60},
{ value: "Precio", widthPx: 60},
],
data: listSelectNewTable, // previously was [listSelectNewTable]
// Note we removed the extra wrapping nested array [] ˆ
},
];
Now you are passing your data correctly - but obviously you're not going to have anything inside the excel sheet barring the columns unless you pass any actual data instead of the empty array.
Final note (not necessary for answer):
Also in general I'd recommend using packages that have higher star ratings on their github page (this one has 65) and is only a fork of a an already existing package, especially for more complex stuff like excel exporting. While there sometimes truly are hidden diamonds in the rough, most of the time the stars are a good indicator of the quality of the package.
The fact that I found a typing error inside my own answer after a random glance at the package for the first time makes me fear how many other errors are actually in it, unless you know and trust the author or it's a package for a very niche thing.
Because the recursive
interface ExcelCell {
value: ExcelCell; // should be ExcelCellValue
style: ExcelStyle;
}
makes positively no sense and would just require you to recursively state the object interface definition ad infinitum
There are a couple of things that you need to change:
You cannot use the useState hook with a class component. To use this hook, change your component to a functional one.
You are not using the library correctly. Here is a good example of how to use it: https://github.com/rdcalle/react-export-excel/blob/master/examples/simple_excel_export_01.md
Specifically, you did not add any column to the sheet.
I'm guessing you want to provide dynamic data for the component (it should not always display the same stuff). If you want to do this, you should inject the data at the parent's component level.
So for your class:
import React, { useState } from 'react';
import ReactExport from "react-export-excel";
const ExcelFile = ReactExport.ExcelFile;
const ExcelSheet = ReactExport.ExcelFile.ExcelSheet;
const ExcelColumn = ReactExport.ExcelFile.ExcelColumn;
const Download = ({ data }) => {
const [listSelectNewTable, setListSelectNewTable] = useState(data);
return (
<ExcelFile>
<ExcelSheet data={listSelectNewTable} name="Price">
{
listSelectNewTable[0].columns.map(d =>
<ExcelColumn label={d.value} value={0} />)
}
</ExcelSheet>
</ExcelFile>
); }
export default Download;
And for the parent:
<Download data={DataExcel} />
With the data you had originally in your class in the parent, so that you can inject it into your component.

Mapbox layer not updating after source update

I'm using Redux state to update an array of coordinates in a Mapbox source. I initially check if there is a source with the id, if yes, I set the data of the source, if not I add the source to the map. When the redux state is changed, it triggers an effect which updates the coordinates of the features in the geojson object and uses setData to change the source. I've tried removing the layer, changing source and adding the layer, which just gave me the old layer (even though the source had indeed been updated). I also tried just updating the source alone and seeing if the layer would update dynamically, it did not.
Here is the code for the effect, which is triggered when the redux state is changed.
useEffect(() => {
const geoJsonObj = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
};
for (let i = 0; i < (props.mapRoutes.length); i++) {
geoJsonObj.data.features.push({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: props.mapRoutes[i].geometry.coordinates
}
});
};
const routeLayer = {
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#ff3814',
'line-width': 5,
'line-opacity': 0.75
}
};
const jsonString = JSON.stringify(geoJsonObj);
const jsonObj = JSON.parse(jsonString);
if (props.mapRoutes.length) {
if (map.current.getSource('route')) {
map.current.getSource('route').setData(jsonObj);
} else {
map.current.addSource('route', jsonObj);
map.current.addLayer(routeLayer);
};
};
}, [props.mapRoutes]);
Neither of these worked and I am having trouble finding how to update a layer based on an updated source. Everything seems right when I inspect the source in the console, I just can't manage to update the layer on the map.
Any help would be appreciated.
I found the problem, I was using the original geoJson object for the setData method instead of the data entry, which was one level too high in the object. Just a simple error which was overlooked.

console.log this.props show undefined but renders correctly on react redux page

I have a very strange error in react redux project. I have payload
const req = {
setcolor:false,
hours:2,
searchterm:'',
dropdownvalue:1,
dropdownvaluetwo:1,
rooms:[
'private-messages',
'project2',
'project3',
'project4',
'project5',
'project6',
'project7',
'project8',
'project9'
],
chatrooms:[
{
key:1,
color:false,
name:'private-messages'
},
{
key:2,
color:false,
name:'project2'
},
{
key:3,
color:false,
name:'project3'
},
{
key:4,
color: false,
name:'project 4'
},
{
key:5,
color: false,
name:'project 5'
},
{
key:6,
color: false,
name:'project 6'
},
{
key:7,
color: false,
name:'project 7'
},
{
key:8,
color: false,
name:'project 8'
},
{
key:9,
color: false,
name:'project 9'
}
],
projectchat:[
{
projectname:'private-messages',
chatmessages:[
{
username: 'ankur',
message: 'whatsup'
},
{
username: 'ankur',
message: 'kya hal hai'
}
]
},
{
projectname:'project2',
chatmessages:[
{
username: 'ankur',
message: 'whatsup'
},
{
username: 'ankur',
message: 'kya hal hai'
}
]
}
]
};
Now, on front end , If i use
<div>
{this.props.projectlist.hours}
</div>
I see correct value on page which is 2. But, if i do
<div>
{this.props.projectlist.chatrooms[0].name}
</div>
It throws me an error as property undefined.
Now, if i do
<div>
{console.log("this.props",this.props.projectlist}
{this.props.projectlist.chatrooms[0].name}
</div>
This console log shows me undefined. This is really strange error. Either, I am overlooking something very simple or this quirk is related to redux internal working. Can someone help me out?
I am assuming you are getting this req object from an endpoint, or from some kind of async. If this is the case, then there is a point in time when this.props.projectlist is not yet your request object, therefor this.props.projectlist.chatrooms will be undefined, which mean there is no index 0 for chatrooms and so on.
You can solve this by making sure your values are in place before you try and use them.
Something along the lines of
if (this.props.projectlist && this.props.projectlist.chatrooms && this.props.projectlist.chatrooms[0] && this.props.projectlist.chatrooms[0].name ) {
// run my code
}
I find this solution to be longwinded and personally because I use lodash in my javascript projects you can just use lodashs' get, like so ( if you want to use lodash, of course). Alternatively you could try and make a little function to run a null coalesce for you.
if (_.get(this.props.projectlist, 'chatrooms[0].name', false ) {
// run my code
}
(the third arg here is a a value if any of the nodes are not found/undefined. I've set it to false to pass over this if statement, because our data is not there)
The above examples are for JS, If you want to keep it all inside the jsx you can use the && trick as well, something like :
{this.props.projectlist && this.props.projectlist.chatrooms && this.props.projectlist.chatrooms[0] && this.props.projectlist.chatrooms[0].name &&
<div>
{this.props.projectlist.chatrooms[0].name}
</div>
}
TL:DR - your props probably aren't there the very first render when the console.log is running.

How can I get an item in the redux store by a key?

Suppose I have a reducer defined which returns an array of objects which contain keys like an id or something. What is the a redux way of getting /finding a certain object with a certain id in the array. The array itself can contain several arrays:
{ items:[id:1,...],cases:{...}}
What is the redux way to go to find a record/ node by id?
The perfect redux way to store such a data would be to store them byId and allIds in an object in reducer.
In your case it would be:
{
items: {
byId : {
item1: {
id : 'item1',
details: {}
},
item2: {
id : 'item2',
details: {}
}
},
allIds: [ 'item1', 'item2' ],
},
cases: {
byId : {
case1: {
id : 'case1',
details: {}
},
case2: {
id : 'case2',
details: {}
}
},
allIds: [ 'case1', 'case2' ],
},
}
Ref: http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
This helps in keeping state normalized for both maintaining as well as using data.
This way makes it easier for iterating through all the array and render it or if we need to get any object just by it's id, then it'll be an O(1) operation, instead of iterating every time in complete array.
I'd use a library like lodash:
var fred = _.find(users, function(user) { return user.id === 1001; });
fiddle
It might be worth noting that it is seen as good practice to 'prefer objects over arrays' in the store (especially for large state trees); in this case you'd store your items in an object with (say) id as the key:
{
'1000': { name: 'apple', price: 10 },
'1001': { name: 'banana', price: 40 },
'1002': { name: 'pear', price: 50 },
}
This makes selection easier, however you have to arrange the shape of the state when loading.
there is no special way of doing this with redux. This is a plain JS task. I suppose you use react as well:
function mapStoreToProps(store) {
function findMyInterestingThingy(result, key) {
// assign anything you want to result
return result;
}
return {
myInterestingThingy: Object.keys(store).reduce(findMyInterestingThingy, {})
// you dont really need to use reduce. you can have any logic you want
};
}
export default connect(mapStoreToProps)(MyComponent)
regards

Resources