updated selectedEntity does not trigger useEffect with useCesium selectedEntity as dependency - javascript

I am using Resium and trying to utilize the useCesium() context as a dependency to a useEffect to do something when an entity is selected on the map. I have my code structured with nested components inside the Resium Viewer component.
as an example:
export function ExampleComponent() {
const {viewer} = useCesium();
useEffect(() => {
console.log(viewer?.selectedEntity);
}, [viewer?.selectedEntity]);
I've noticed when I make a dispatch through a context I've created in the app, then it seems the above useEffect will register that the selectedEntity has been updated and trigger a console.log then, but it won't update if I select a new entity on the map immediately which is my desired function.
I'm wondering if I'm not fully incorporating the useCesium() context Resium provides.
I've tried referencing many of the different objects in the useCesium context. Any object I've referenced does not seem to trigger the useEffect immediately when I interact with said objects in Viewer which is what I want to happen.

Related

How to test callback methods in react functional component when the child component elements are not completely accessible

I am using jest to test my react component. However I need to test the callback methods passed as props to children components.
My component looks something like this
function myReactComp = (props) => {
onRowEditHandler = (rowData) => {
// if this then that logic
// process and trasform data
// set some local state
// dispatch action
}
return (
<ComplexDataTableComponent data={someDataProps} onRowEdit = {onRowEditHandler} />
)
}
I need to test the onRowEditHandler as a function. I want to send arguments to this function explicitly so that specific code logic are triggered.
Now many suggestions in stackOverflow says, the best way to test closure functions in react functional components is to simulate the user behavior. For example, if there is a callback for a button, find the button on the wrapper/instance and trigger the click event.
In my case, the ComplexDataTableComponent is kind of like a container, which has many children and nested components. It is next to impossible to find a specific row element, trigger its edit button and then update the form data so that the call back is triggered.
Is there any way i can get access to onRowEditHandler apart from triggering the wrapper/instance elements?

how to emulate messages/events with react useState and useContext?

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs

Gatsby: Context update causes infinite render loop

I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.

React - Passing state to parent component - too many AXIOS requests

I'm a beginner both in programming and React and I have to create a functioning google Map single page website. I'm using google-map-react.
I have a parent App.js (containing the call to and a HTML sidebar) and a child Map.js containing the map itself and axios request function.
I'm making axios requests to fetch data from foursquare api. It works without side effects. Then I want to pass those data to my app.js and update the parent state so that I can renderthe locations on the sidebar.
This is the function I used (in Map.js). I had to put the call in componentWillReceiveProps as a last resource because componentDidMount didn't work:
https://jsfiddle.net/kd1yuhe5/
I think this may be the issue, but it's also the only way I found to make the list show:
this.props.updateVenues(this.state.venues)
This is the code from App.js
updateVenues(venues) {
this.setState({
venues: venues,
});
}
Then I called the method like this:
<Map updateVenues={this.updateVenues.bind(this)} />
The code works, venues are shown in the sidebar (if you need the code let me know, but I don't think it's relevant), but the I keep making requests until I exceed quota.
Again: I'm a beginner. I just started 3 months ago.
EDIT:
Here are both components:
Map.js
https://jsfiddle.net/kd1yuhe5/5/
App.js
https://jsfiddle.net/xwzrm4bp/2/
When the state of a React component is updated (and without custom implementation of componentShouldUpdate), it triggers a re render of that component (ie call the render function).
If the props of the children of this component have changed since the last render, they will also re render.
They re render because they have received new props, and this will also call their componentWillReceiveProps function.
Since you are fetching data each time Map will receive props, you are fetching data each time something change (state change) on App.
First in Map.js, this.props.query is assigned to this.state.query.
This looks like an error, as in this case what you want are the new props receceived by componentWillReceiveProps, this is the first argument of this function.
So you should assign props.query to this.state.query instead.
Except that actually you should not:
this.state.query is only used in componentWillReceiveProps, therefore there is no need to put props.query into state.query.
Second since you have both this.props.query from the previous props update and props.query which is the new received query, you have the opportunity to fetch only when the query has actually changed:
// Receive the update query from parent and fetch the data
componentWillReceiveProps(nextProps){
if (this.props.query !== nextProps.query) {
this.fetchData(nextProps.query);
}
}
Now you may ask, "ok but why my Map component was always re rendered, even when its props didn't changed".
But they did:
in App.js
<Map
query={this.state.query}
center={this.state.center}
updateVenues={this.updateVenues.bind(this)}
getClickedMarker={this.getClickedMarker.bind(this)}
/>
By calling this.updateVenues.bind(this) and this.getClickedMarker.bind(this) in the render method, you are creating new values (actually new Function references)for the updateVenues and getClickedMarker props, at each render.
Instead, you should bind these method in the contructor of App:
constructor(props) {
super(props);
this.updateVenues = this.updateVenues.bind(this);
this.getClickedMarker = this.getClickedMarker.bind(this);
....
}
....
<Map
query={this.state.query}
center={this.state.center}
updateVenues={this.updateVenues}
getClickedMarker={this.getClickedMarker}
/>
This may limit your API calls a lot, you may also debounce them.

Is my React component being recreated instead of updating?

I am trying to combine Angular and React.js. I have an work example project here I have seen a couple of ways to bring the Angular and React.js together. One of the methods I have seen is to create a directive and create the React component in the link function. For example in the first part of the project to generate the React version(in red) I am using
.directive('reactElementRepeater', function() {
return {
link: function(scope, element) {
var update_react = function(oldVal, newVal){ //Called every time one of the two values change
React.renderComponent(Demo_Element({
numberOfElements: scope.myModel.numberOfElem,
numberInElements: scope.myModel.numberInElem
}), element[0]);
}
scope.$watch('myModel.numberOfElem.length', update_react);
scope.$watch('myModel.numberInElem', update_react);
}
}
});
What I want and what should happen in a React enabled application is for something in the model to be updated, then that update is sent through React and it will alter the DOM as little as possible to reflect that change. It looks like that instead of updating a bit of the DOM this will Create a new React component each time with renderComponent.
React.renderComponent() instantiates the root component, starts the
framework, and injects the markup into a raw DOM element, provided as
the second argument.
Is it actually recreating the elements each time? If that is the case is there a way to alter this so that doesn't happen?
Just to be clear I know about ngReact, I just want to know other ways to speed up Angular with React.
Yes this is fine, it's not mounting the component multiple times.
When you call React.renderComponent() the second argument is the element which react should render the component to. So react notices if you are rendering the same component to a dom element that already contains a mounted instance of the component, and does not re-mount the entire component, it just updates the properties of it instead.
You can see this in action if you make a component with componentDidMount function defined. You'll notice that componentDidMount will only execute the first time renderComponent gets called. And afterwards, subsequent calls to renderComponent on the same target dom element will not call it because the component is already mounted. Likewise getDefaultState and getDefaultProps also only get called on the first renderComponent call.
If you're asking will the render function of the component be called every time the answer is yes. But this is how react works, you want the render function to get called because props might have changed. You can block it from being called by using shouldComponentUpdate (http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) and returning false. However react developers recommend you don't use this to block render calls unless you have specific performance problems - most of the time it should be fine to just let the render call execute as it wont cause any slow dom updates unless things have actually changed.

Categories