How to pass data from one component to another in React.JS - javascript

For all React Gurus! The logic is that I make a query to overpass and get some GeoJSON, now I need to pass this GeoJSON object to another Component (which is not its child) so that there I could make some calculations and show it on the screen.
The general structure is like this: There is a Main.js component which has two children MapBox.js and CalculationResults.js. MapBox.js has a child OverpassLayer.js which gives me GeoJSON. This GeoJSON I need to pass to CalculationResults.js. I tried to implement callback function all the way from parent Main.js but it always returns me the GeoJSON from the previous query. Could you please help me with the correct way of passing data between Components.
This is my OverpassLayer.js
const OverpassLayer = (props) => {
const [geojson, setGeojson] = useState();
useEffect(() => {
makeQuery();
}, [props.street, props.houseNumber]);
const makeQuery = () => {
const query = `[out:json];nwr["addr:street"="${props.street}"]["addr:housenumber"="${props.houseNumber}"][building](59.3518076,24.55017,59.5915769,24.9262831);out geom;`;
const options = {
flatProperties: true,
overpassUrl: "https://overpass-api.de/api/interpreter",
};
overpass(query, dataHandler, options);
};
const dataHandler = (error, osmData) => {
if (
!error &&
osmData.features !== undefined &&
osmData.features[0] !== undefined
) {
console.log(osmData.features[0]);
let area = (getArea(osmData.features[0].geometry.coordinates[0]));
console.log(area);
setGeojson(osmData);
}
};
function keyFunction(geojson) {
if (geojson.features.length === 0) {
return "";
} else {
return geojson.features[0].id;
}
}
function getArea(array) {
if (array) {
let arrayPolygon = array;
let polygon = turf.polygon([arrayPolygon]);
let area = turf.area(polygon);
return area;
}
return 0;
}
return geojson ? <GeoJSON key={keyFunction(geojson)} data={geojson} /> : null;
};

the easiest way is to store the data in localStorage. For the example, below setGeojson(osmData) you can write localStorage.setItem("Geojson", JSON.stringify(Geojson)); and in CalculationResults.js you can call it in useEffect() or componentDidMount():
const getGeojson = JSON.parse(localStorage.getItem("Geojson"));
if(getGeojson){
if(getGeojson.length > 0){
setGeojson(getGeojson);
}
}
the more advanced way is to use redux

Related

How do I avoid infinite loop when using Redux state in useEffect dependency array?

I'm trying to figure out why my useEffect function ends up in an infinite loop.
I have two variables that are hooked into my Redux store:
const vehicles: AllVehiclesCollection = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
const properties: AllPropertiesCollection = useSelector((state: ReduxState) => state.claims?.properties ?? {});
and I have an action that is dispatched to the store that updates these only after a user clicks a button.
I have a useEffect that will trigger based on either of these variables changing.
useEffect(() => {
let fullVehicleList: DropdownData[] = getFormattedVehicleListForDisplay();
let fullPropertyList: DropdownData[] = getFormattedPropertyListForDisplay();
let fullList = fullVehicleList.concat(fullPropertyList);
if (fullList.length > 0) {
setVehiclesAndPropertiesList(fullList);
} else {
setVehiclesAndPropertiesList(null);
}
}, [vehicles, properties]);
Nowhere in this code are the vehicles or properties variables changed or any actions dispatched that would change the Redux state.
getFormattedVehicleListForDisplay function:
const getFormattedVehicleListForDisplay = () => {
let list: DropdownData[] = [];
if (Object.keys(vehicles).length > 0) {
let thisPolicysVehicles = [];
if (vehicles !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysVehicles = vehicles[key];
}
if (thisPolicysVehicles && thisPolicysVehicles.length > 0) {
thisPolicysVehicles.forEach((vehicle: VehicleInformation) => {
if (vehicle.vehicleMake !== OTHER_VEHICLE) {
list.push({
label: formatVehicleForDisplay(vehicle),
value: { ...vehicle, type: 'V' },
});
} else {
list.push({ label: vehicle.vehicleMake, value: {} });
}
});
}
}
return list;
};
getFormattedPropertyListForDisplay function:
const getFormattedPropertyListForDisplay = () => {
let list: DropdownDataOMIG[] = [];
if (Object.keys(properties).length > 0) {
let thisPolicysProperties = [];
if (properties !== null) {
const key = `${selectedPolicy.symbol}${selectedPolicy.number}`;
thisPolicysProperties = properties[key];
}
if (thisPolicysProperties && thisPolicysProperties.length > 0) {
thisPolicysProperties.forEach((property: LocationInformation) => {
if (property.locStreet1 !== OTHER_PROP) {
list.push({
label: formatPropertyForDisplay(property),
value: { ...property, type: 'P' },
});
} else {
list.push({ label: property.locStreet1, value: {} });
}
});
}
}
return list;
};
For reference, the data in vehicles and properties is a set of key-value pairs where the key is a unique identifier of a given account number and the value is an array of vehicle/property objects for that account.
Any idea why this goes into an infinite loop when using Redux state in the dependency array? Is there a different way to use Redux state in a dependency array? Thanks!
When using
const vehicles = useSelector((state: ReduxState) => state.claims?.vehicles ?? {});
Each time this is triggered, and you don't have vehicles in your store, you return a new object {}. and {} === {} // false
So ain your useEffect dependency array, it's each time a new Object, so useEffect is triggered.
So either remove your || {} in your selector (because null === null & undefined === undefined) or consider moving to useShallowSelector as explained in react-redux documentation

How to write JavaScript code that allows for defaults to be overrided

I would like to use this text-highlighting library in my Vue project. Here's an example from their website of how it can be used:
import TextHighlighter from '#perlego/text-highlighter';
import { isDuplicate } from './utils';
import highlightsApi from './services/highlights-api';
class ArticleView {
constructor(data) {
this.data = data;
const pageElement = document.getElementById("article");
this.highlighter = new TextHighlighter(
pageElement,
{
version: "independencia",
onBeforeHighlight: this.onBeforeHighlight,
onAfterHighlight: this.onAfterHighlight,
preprocessDescriptors: this.preprocessDescriptors,
onRemoveHighlight: this.onRemoveHighlight
});
}
onBeforeHighlight = (range) => {
return !isDuplicate(range)
}
onRemoveHighlight = (highlightElement) => {
const proceed = window.confirm("Are you sure you want to remove this highlight?");
return proceed;
}
preprocessDescriptors = (range, descriptors, timestamp) => {
// Add an ID to the class list to identify each highlight
// (A highlight can be represented by a group of elements in the DOM).
const uniqueId = `hlt-${Math.random()
.toString(36)
.substring(2, 15) +
Math.random()
.toString(36)
.substring(2, 15)}`;
const descriptorsWithIds = descriptors.map(descriptor => {
const [wrapper, ...rest] = descriptor;
return [
wrapper.replace(
'class="highlighted"',
`class="highlighted ${uniqueId}"`
),
...rest
];
});
return { descriptors: descriptorsWithIds, meta: { id: uniqueId } };
}
onAfterHighlight = (range, descriptors, timestamp, meta) => {
highlightsApi.saveBatch(meta.id, descriptorsWithIds)
.then((result) => {
// Do something with the highlights that have been saved.
})
.catch((err) => console.error(err));
}
render = () => {
// Code that takes the data for the article and adds it to the DOM
// based on a html template here.
}
}
Using the above example, I would like to setup the highlighter (similar to the above code, but in a different file, for example ./utils/highlighter.js) with all the default options I want (onBeforeHighlight, onRemoveHighlight, etc.), and then be able to import it from there and override the options for which I don't want to use the defaults, so it looks something like this in the importing file:
import highlighter from "../utils/highlighter.js";
const overridingOptions = {
onAfterHighlight: (range, descriptors, timestamp, meta) => {
console.log(range, descriptors, timestamp, meta);
}
};
const target = document.getElementsByClassName("testme")[0];
highlighter(target, overridingOptions);
For some reason, I am not able to understand how to modify the ArticleView example to fit my needs, so I think I need to see this done once. How should the code in ./utils/highlighter.js look to make this possible?

unable to select all checkboxes in tree using angular2-tree on init

Objective : i have a button named "feed data" so when ever i click it the data will be loaded i mean the tree with checkboxes here my requirement is when ever i click it along with data all the check boxes have to be checked on init i tried using
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
but it is not working below is my code
click(tree: TreeModel) {
this.arrayData = [];
let result: any = {};
let rs = [];
console.log(tree.selectedLeafNodeIds);
Object.keys(tree.selectedLeafNodeIds).forEach(x => {
let node: TreeNode = tree.getNodeById(x);
// console.log(node);
if (node.isSelected) {
if (node.parent.data.name) //if the node has parent
{
rs.push(node.parent.data.name + '.' + node.data.name);
if (!result[node.parent.data.name]) //If the parent is not in the object
result[node.parent.data.name] = {} //create
result[node.parent.data.name][node.data.name] = true;
}
else {
if (!result[node.data.name]) //If the node is not in the object
result[node.data.name] = {} //create
rs.push(node.data.name);
}
}
})
this.arrayData = rs;
tree.selectedLeafNodeIds = {};
}
selectAllNodes() {
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
// firstNode.setIsSelected(true);
}
onTreeLoad(){
console.log('tree');
}
feedData() {
const results = Object.keys(this.data.info).map(k => ({
name: k,
children: this.data.info[k].properties
? Object.keys(this.data.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
feedAnother() {
const results = Object.keys(this.dataa.info).map(k => ({
name: k,
children: this.dataa.info[k].properties
? Object.keys(this.dataa.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
onActivate(event) {
this.selectedDataList.push(event.node.data);
console.log(this.selectedDataList)
}
onDeactivate(event) {
const index = this.selectedDataList.indexOf(event.node.data);
this.selectedDataList.splice(index, 1);
console.log(this.selectedDataList)
}
below is my stackblitz https://stackblitz.com/edit/angular-hrbppy
Use updatedata and initialized event to update the tree view to check all checkboxes.
app.component.html
<tree-root #tree *ngIf ="nodes" [nodes]="nodes" [options]="options" [focused]="true"
(initialized)="onTreeLoad()"
(updateData)="updateData()"
(select)="onActivate($event)"
(deselect)="onDeactivate($event)">
</tree-root>
It'll initiate tree-root component only if nodes variable is available,
then in the initialized and updateData event call selectAllNodes method to select all checkboxes.
app.component.ts
updateData() {
this.selectAllNodes();
}
onTreeLoad(){
this.selectAllNodes();
}
Refer to this slackblitz for working example.
just, in your function feed data call to your function this.selectAllNodes() enclosed in a setTimeout. You can see your forked stackblitz
setTimeout(()=>{
this.selectAllNodes()
})
NOTE: I see in your code you try to control in diferents ways the items selected. I simplified using a recursive function.
In this.treeComp.treeModel.selectedLeafNodeIds we have the items that are changed, so
getAllChecked()
{
const itemsChecked=this.getData(
this.treeComp.treeModel.selectedLeafNodeIds,null)
console.log(itemsChecked);
}
getData(nodesChanged,nodes) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
//in nodesChanged we has object like {1200002:true,123132321:false...}
if (nodesChanged[node.id]) //can be not changed, and then it's null because
//it's not in object or can be changed to false
data.push({id:node.id,name:node.name})
//or data.push(node.name); //if only need the "name"
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children)]
}
);
return data
}
Updated I updated the function getData to include the "parent" of the node, but looking the code of #Raghul selvam, his function like me more than mine.
getData(nodesChanged,nodes,prefix) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
if (nodesChanged[node.id])
data.push(prefix?prefix+"."+node.name:node.name)
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children,prefix?prefix+"."+node.name:node.name)]
}
);
return data
}
And call it as
this.getData(this.treeComp.treeModel.selectedLeafNodeIds,null,"")
You could add this in your onTreeLoad function. You could add a boolean flag(treeLoaded) for tracking if the tree has loaded or not.
onTreeLoad(tree){
this.selectAllNodes();
this.treeLoaded = true;
}

Render same component with different details each time

I want the same message with different URL, based on a prop. I basically have the below render method, which I call inside my main one.
renderNoBasicMode = () => {
const { securityMode } = this.props;
// Need this, while isFetching securityMode === '',
// Unless, we don't this is rendering on multipel renders.
if (securityMode !== SecurityMode.BASIC && securityMode !== SecurityMode.EMPTY) {
return (
<div className="badge badge-light" data-test="non-basic-mode">
<NoResource
icon="user-o"
title="Non Basic Security Mode"
primaryBtn="New User"
primaryCallback={this.openCreateUserModalPromise}
moreUrl={NonBasicSecurityMode.url}
>
No users available when Lenses is running on {securityMode} security mode.
</NoResource>
</div>
);
}
return null;
};
And I want to display a different url based on the value of the NonBasicSecurityMode, which I have here:
const NonBasicSecurityMode = [
{ securityMode: 'mode1', url: 'https://...' },
{ securityMode: 'mode2', url: 'https://...' },
{ securityMode: 'mode3', url: 'https://...' }
];
The securityMode, is deternment by an API request.
export const securityModeSelector = createSelector(
lensesConfigSelector,
config => (config.&& config['security.mode']) || ''
);
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
};
}
Basically, I tried mapping through them, and a forEach, but I was apparently wrong. Can you help me figure this out? Thanks!!

How to write state changing routine, similar to Redux?

How can I write a really super, simple state changing routine? I need something like Redux, but way simpler, don't need all the bells & whistles.
I was thinking of a global object i.e. myState = {}, that is changed via setMyState() / getMyState().
I'm using JavaScript, and wondering if this would be done via a timer that polls say every 10ms, or so.
So in my JavaScript client app (I'm using ReactJS), a call to my getMyState("show-menu") inside a render() would update the Component's state just like using this.state..
The reason I want this is:
1) Wanna know how to write it for learning purposes.
2) Need something simpler that Redux, simple like Meteor's Session vars, so don't have to pass this.Refs. down to child compnents which setState on parent components.
3) Redux is a mouthful, there is still lots to digest and learn to use Redux.
Seems like you could do this pretty simply with a constructor.
function State () {
this._state = {};
...
}
State.prototype.get = function () {
return this._state;
};
State.prototype.set = function (state) {
return this._state = state;
};
var STATE = new State();
But then you have to do the polling you mentioned in your post. Alternatively, you can look at eventEmitter libraries for javascript, for example https://github.com/facebook/emitter, and turn the State object into an event emitter.
Update
Not sure if this is what you're looking for, at all, but it's simpler.
function makeStore () {
var state = { };
return {
set (key, value) { state[key] = value; },
get (key) { return state[key]; }
};
}
const store = makeStore();
store.set("counter", 1);
store.get("counter"); // 1
Believe it or not, there's really not a lot to Redux.
There's, perhaps, a lot to think about, and it's extra work to keep everything untied from your store...
But have a quick look:
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
This is the code I intend to use.
Looking at it, I'm pretty much just creating a store (with a function to run every time there's an event), there's the subscription to have a listener run, after the store has updated, there's a line where I fire an action, and... ...well, that's it.
function createStore (reduce, initialState) {
var state = initialState;
var listeners = [];
function notifyAll () {
listeners.forEach(update => update());
}
function dispatch (event) {
const newState = reduce(state, event);
state = newState;
notifyAll();
return store;
}
function subscribe (listener) {
listeners.push(listener);
return store;
}
function getState () {
return state;
}
const store = {
getState, subscribe, dispatch
};
return store;
}
// THIS IS MY APPLICATION CODE
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
<button id="Decrement">-</button>
<output id="Output"></output>
<button id="Increment">+</button>
The very tiny, very easy implementation of a store (note that the real thing is more complex) is above. dispatch and subscribe are very useful, here.

Categories