Updating url in GeoJSONLayer using ArcGIS JS API - reactjs

I am working on a simple node/react application that allows for the the request of data using the url parameter in GeoJSONLayer. The way that I set up the api I need to be able to change the url to get a new set of data. I have set up a useEffect() to load the map that has all of the map elements and have told it to listen for changes on the treeURL.
const [treeURL, setTreeURL] = TreeURL();
useEffect(async()=>{
loadMap(treeURL)
},[treeURL]);
The TreeURL function is just a simple useState() to default to getAll which is the default way of getting all of the data back. This part is working fine the map renders with the data that is fetched.
const TreeURL = () => {
const getAll = 'getAll'
const [treeURL, setTreeUrl] = useState(getAll)
return [treeURL, setTreeUrl]
}
When I go to update the data using the setTreeURL() the function runs. treeURL prints out into the console, but useEffect() is not called and the map does not update.
props.list.forEach((item) => {
elements.push(
<li class="mr-3">
<button
key={item}
className="inline-block border border-blue-500 rounded py-1 px-3 bg-blue-500 text-white"
onClick={()=>{
setTreeURL(`getByParams?CONDITION=${item}`)
console.log(treeURL)
}
}>{item}
</button>
</li>)
})
How do I update GeoJSONLayer's URL once the map has been loaded?

While its a little difficult to answer the question without more context regarding what you'd like to achieve by updating the GeoJSON's URL, there are a couple ways I could see you accomplishing this based on how the URL is updated.
If you are trying to query the initial GeoJSON source by adding URL parameters, you might be able to use the customParameters property of a GeoJSON and use the refresh() property to fetch new data. You can check out an example of this in action provided by Esri here.
However, if you are look to load an entirely new GeoJSON, than you might have to resort to creating a new GeoJSON object with the specified URL. You can also accomplish this by reading in your GeoJSON as a blob, as seen in this code snippet below and further explained in this ArcGIS blog article.
const blob = new Blob([JSON.stringify(geojson)], {type: "application/json"});
const url = URL.createObjectURL(blob);
const layer = new GeoJSONLayer({ url });

Related

Algolia: Export React Instant Search results

Is there any way to export Algolia's React Instant Search results to a CSV? I've tried using the react-csv package, but it doesn't work with Algolia's Hit Component. The package requires data as props, but the data is constantly changing since it's React Instant Search.
What I mean by constantly changing is that on page load, you're given the entire index of records found, then you can narrow down the results with the search bar or other filtering components.
I've gone down the Google rabbit hole looking for information about exporting Algolia's search results as a CSV, but I haven't found anything regarding React Instant Search—unless I completely missed it.
Has anyone tried this before? If so, could you point me in the right direction regarding documentation or examples?
Not sure if this solves your problem but one possible way is to use the StateResults widget. The StateResults widget provides a way to access the searchState and the searchResults of InstantSearch.
Here I will create a custom StateResults component in the form of a download button and then connect it using the connectStateResults connector.
I have attached a demo below as well.
For simplicity I didn't format the data to be fed into the CSV builder.
// 1. Create a React component
const StateResults = () => {
// return the DOM output
};
// 2. Connect the component using the connector
const CustomStateResults = connectStateResults(StateResults);
// 3. Use your connected widget
<CustomStateResults />
In your case something like
const StateResults = ({ searchResults }) => {
const hits = searchResults?.hits;
return (
<div>
<button>{hits && <CSVLink data={hits}>Download CSV</CSVLink>}</button>
</div>
);
};
const DownloadButton = connectStateResults(StateResults);
//in your JSX then <DownloadButton />

Conditional rendering with useEffect / useState, why is there a delay in the DOM when switching components?

Intro / Context
I am trying to build an application using React that allows for image or video display based on a chosen menu item. I am currently using Advanced Custom Fields within WordPress to build my data objects and using graphQL to query them into my project.
I want the application to display either a video component or an image component. This choice will be determined through conditional rendering and based on the contents of the object's fields.
Each object contains a title, an image field and a video field. If the entry in question should be displayed as an image the video field will be set as the string 'null'. All fields will return strings regardless.
I am using useState to target a particularly active field, however, despite triggering a change in state conditional rendering does not appear to change.
The Application
This is my approach
function Display({ objects }) {
const [setVisualOption, changeVisualOption] = useState(false);
const [appState, setState] = useState({
myObjects: objects,
activeTitle: "null",
activeImage: "null",
activeMediaUrl: "null",
});
function toggleActive(index, trackIndex) {
setState({
...appState,
activeTitle: appState.myObjects[index].title,
activeImage: appState.myObjects[index].image[0].mediaItemUrl,
activeMediaUrl: appState.myObjects[index].mediastreamurl,
});
changeVisualOption(appState.activeImage.includes("null"));
}
useEffect(() => {}, [
appState.activeTitle,
appState.activeImage,
appState.activeMediaUrl,
setVisualOption,
]);
return (
<div className="display">
<div className="list-box-right>
{appState.myObjects.map((element, index) => (
<>
<div
key={index}
className="menu-item"
onClick={() => {
toggleActive(index);
}}
>
{element.title}
</div>
</>
))}
</div>
<div className="right-grid">
{setVisualOption ? (
<VideoComponent activeImage={appState.activeImage}></VideoComponent>
) : (
<ImageComponent activeImage={appState.activeImage}></SingleImage>
)}
</div>
</div>
);
}
The summarise, to component takes objects as prop which are being passed down from another component making the graphQL query. I am then setting the initial values of useState as an object and setting an activeTitle, activeImage and activeMediaUrl as null.
I am then using a function to toggle the active items using the setState modifier based upon the index that is clicked within the return statement. From there I am using setVisualOption and evaluating whether the activeImage is contains 'null' (null.jpg), if this is true setVisualOption will be set to true allowing the Video Component to be rendered
The Problem
To be clear, there are no errors being produced and the problem is a slight rendering issue where it requires double clicks to alter the state and trigger the correct response from the tertiary operator.
The issue is within the conditional rendering. If I set my object fields to all images and return only the Image Component there are no issues, and the state change can be seen to register visually as you click down the listed options.
It is only when I introduce the conditional rendering that there is a delay, the first click does not generate the correct response from the component I am trying to display however the second click triggers the right response.
As you can see, I am also using useEffect to try and trigger a rendered response when any of the described states change but still encounter the same problem.
Does anyone know what is the cause of this bug? when looking at the console.log of setVisualOption is not appearing as true on first click when it aught to.
Any insights would be great thanks
You set your visual option right after you set your appState, this is why appState.activeImage in changeVisualOption is not updated because state updates in React is asynchronous. You can either use useEffect to update visual option when the appState changes or you can use appState.myObjects[index].image[0].mediaItemUrl in changeVisualOption
function toggleActive(index, trackIndex) {
setState({
...appState,
activeTitle: appState.myObjects[index].title,
activeImage: appState.myObjects[index].image[0].mediaItemUrl,
activeMediaUrl: appState.myObjects[index].mediastreamurl,
})
changeVisualOption(appState.myObjects[index].image[0].mediaItemUrl.includes("null"))
}
or
useEffect(() => {
changeVisualOption(appState.activeImage.includes("null"))
}, [appState])

The order for inlineStyleRanges is wrong

I have a react application that allows our users to input data into the Editor component provided by draft-js. I made my own toolbar component our user could interact with by passing functions that update the EditorState. I also need to be able to save the EditorState contents to our database. We're able to save the database by using the ff code:
const serialized = JSON.stringify(
convertToRaw(editorState.getCurrentContent())
);
saveStringToDatabase(serialized);
and are able to restore the values from the database using
const [editorState, setEditorState] = useState(() => {
if (!serializedEditorState) {
return EditorState.createEmpty();
}
const contentState = convertFromRaw(JSON.parse(serializedEditorState));
return EditorState.createWithContent(contentState);
});
. All is good in the user interacting with the Editor component from the front-end.
Now the problem is when I try to restore the data saved in the database to the front-end, I get a wrong snapshot of the state editor. I'm suspecting it's because the result of the convertToRaw() function has the wrong order for blocks[index].inlineStyleRanges. In my attached screenshot, index 3 of inlineStyleRanges of the blocks object should be index 4 and index 4 should be in index 3. I think I might be using the wrong functions for updating the EditorState but can't confirm as I do not see any other resources describing the actual correct way to update EditorState. We update the editor state using the ff code:
const removedInlineStyle = Modifier.removeInlineStyle(
editorState.getCurrentContent(),
newSelection,
inlineStyle,
);
const newInlineStyle = Modifier.applyInlineStyle(
removedInlineStyle,
newSelection,
inlineStyle,
);
const newEditorState = EditorState.push(
EditorState.acceptSelection(editorState, newSelection),
newInlineStyle,
'change-inline-style',
);
setEditorState(newEditorState);
here is a github issue of someone else that's experiencing something like mine. enter image description here

How to use a method in the Main component on another

I am trying to save notes into localStorage (or in this case localforage). I have a simple text area with a button called "save." The save button is located in another file indicated below.
I used an example found here to try to set the items.
This is the code I wrote:
SaveMessage() {
var message = <Notepad></Notepad>;
reactLocalforage
.SetItem("Message", message, function(message) {
console.log(message);
})
.catch(function(err) {
console.log(err);
});
}
The var message is something I'm not too sure of either. Notepad is another component with the code which contains the text area and buttons:
<div className="button-container">
<button
className="save-button"
onClick={() => {
props.onSaveMessage(saveMessage);
}}
>
Save
</button>
<button className="remove-button">Show all</button>
</div>
The area where it says onClick I was hoping there would be a way to use the SaveMessage method with localforage initially I tried creating it as a prop (from a tutorial) so in the main method I'd have:
render() {
return (
<div>
<Notepad onSaveMessage={this.SaveMessage}></Notepad>
</div>
);
}
and then on the Notepad component:
<button
className="save-button"
onClick={() => {
props.onSaveMessage();
}}
>
Save
</button>
When I click the save button on my application I am hoping something will be set within the local-storage on the browser, but I get an exception:
TypeError: Cannot call a class as a function
The error occurs when I set item on the save message code above and when I try to call it as a prop onSaveMessage(saveMessage).
You haven't added enough code to be able to fix your exact issue, but I can help you understand how you should proceed to fix it. In React when you want to share information between components, you need to lift that data into a parent component, and then share it through props. If your component tree is profound, then this task can get tedious. So several libraries have been developed to manage your app's state.
I suggest you start by reading "Lifting State Up" from the React docs page. To help you apply these concepts to your current situation, I've created a CodeSandbox in which I try to replicate your situation. You can find it here.
Once you understand the need to "lift" your state, and how you can share both data and actions through props you can migrate to state handler tool. Some of them are:
React Context
Redux
MobX
There are much more. It is not an extensive list, nor the best, just the ones I have used and can vouch that they can help you.
I hope this helps.

How to use the rerenderEvents() method on a pre-rendered Calendar

UPDATED 3-26-2019
I am trying to give users the ability to add events to the Calendar and for those events to appear upon input. I am using a React.js front end and can get the events to render in a new calendar but it does not delete the old one (I just end up with 10+ calenders that render on top of each other).
I've tried using the .destroy() method and then re-rendering the calendar but the method doesn't seem to be available/functioning so I figured I would try rerenderEvents() and keep the same calendar object but that function doesn't seem to be accessible either. Would love some assistance in solving this from anyone who is familiar with FullCalendar v.4
Once the user inputs the data it's collected into a data object and append it into the state but if I just mirror the above, it renders a whole new calendar on top of the old one.
It's like I cannot get the Calendar object once rendered to make those method calls, but am having trouble capturing it in a separate function. All the Docs say is call the .destroy() or call the .rerenderEvents() and not what to call them on.
//This successfully Renders
componentOnMount() {
var calendarNew = document.getElementById('calendar');
let newCalendar = new Calendar(calendarNew, {
plugins: [ dayGridPlugin, timeGridPlugin, listPlugin ],
events: this.state.events
});
console.log(newCalendar)
await this.setState({ calendarObj: newCalendar })
// let myNewEvents = newCalendar.getEvents()
// console.log(myNewEvents)
// let stateEvents = this.state.calendarObj.getEvents()
// console.log(stateEvents)
this.state.calendarObj.render();
}
I've tried the following to mitigate the double render...
async handleNewEvent() {
// code that creates an object and sets the state to the new event array//
var newEventArr = existingEvents.concat(newEvent)
console.log(newEventArr)
await this.setState({ events: newEventArr })
await this.setState({ currentIndex: (this.state.currentIndex + 1) })
this.props.change(this.state.events, this.state.currentIndex)
//Contains the new event array in the console
console.log(this.state.events)
this.state.calendarObj.rerenderEvents();
}
But this does nothing. I cannot even render() from the handleNewEvent() as it appears as if the state is unable to hold functions in it and I cannot find a way to pass the function from the ComponentDidMount() to the handleNewEvent(). Im at a loss...

Resources