Anyone who has worked with React Drag & Drop, help needed!
In this example (https://github.com/react-dnd/react-dnd/blob/master/examples/01%20Dustbin/Multiple%20Targets/Dustbin.js#L20) on Line 20, there is props.onDrop(monitor.getItem()); this code where the function onDrop passed to component Dustbin via props is called.
I need to know if there is a way of calling a method defined inside Dustbin instead of passing via props.
Eg: this.onDrop(monitor.getItem()); or currentComponent.onDrop(monitor.getItem());
Yes there is, the third argument to drop is 'component' (see here: http://react-dnd.github.io/react-dnd/docs-drop-target.html), which is the actual component that was dropped into. So you can do something like:
const dustbinTarget = {
drop(props, monitor, component) {
component.onDrop(monitor.getItem());
},
};
This argument is available for all of the drop target methods except for canDrop, since the instance may not be available at the time it was called.
Related
I’m working on a react app in which I want all of the user inputs to live inside a global json object. (This is a multi page form) This object lives inside the parent function (P) and uses useState (let’s call the functions obj and setObj). I wrap the children function with a useContext such that all children (a,b,c). <context.Provider>’s has value={{obj, setObj}}.
Function a grabs the context created in P and it consists:
Const {obj,setObj} = useContext(*context created in P*)
<TextField
…
OnChange= {((e)=>setObj((prev)=>({…prev, e.target.name: e.target.value}))}
name=“name”
Value = {obj[“name”]}
/>
(The syntax may be off here, but it works in my program.
The problem I have is that every time I try to type in the textField, it exits out of the textField (where I have to click the textField EVERYTIME to just type ONE character). I want to create a program where, if possible, children can update the obj in the global state as if the textField was in the same function.
I have implemented this in things like and components. It works perfectly, but I think it works here because those values are limited (To things like Y/N or elements in a list). I believe the error comes because we are always updating a variables from another function (which is what I want to happen).
Any suggestions would be of great help!
Thanks!
I simulated my Context + DND problem in https://codesandbox.io/s/adoring-booth-33vqo . I have other components which will be added to this example and I will use a Context hook to share values across the page.
After the initial render, everything looks fine. The idea of the list is to change the order within itself and when ones changes the order with drag-drop, it throws an "Invalid Hook" error.
So the (first) real question is, what is triggering this error which is linked to the line
const { lang1Library, updateLang1Library } = useContext(LangContext)
;
Thanks in advance for your help.
Geo
It's not a good approach to provide a link for the whole project even if it is small. But I had a quick look and there's at least one thing you're doing wrong:
// DragEndFct.js
export default function DragEndFct(result, libName) {
const { lang1Library, updateLang1Library } = useContext(LangContext);
This is not React component, but it uses a hook - and it is wrong. Hooks have a special meaning in React and should be used properly (Rules of Hooks).
You can't use hooks inside regular functions and expect them to work. That is why you are getting that error.
So, there are many ways you can try to fix this. For instance, DragEndFct is a regular function, you can declare more arguments and pass stuff you get from context:
// you are using it in components right ?
function DragEndFct(result, libName, param3, param4) {}
// so you probably have access to the context there
// and can pass data from the context when you call it.
// something like this
onDragEnd={function (result) {
console.log();
DragEndFct(result, StaticVars.LANG1_LIBRARY_NAME, lang1Library, updateLang1Library);
}}
You could even make DragEndFct to be a React component - it can just return null (which means no UI will be rendered) but in that case you will have hooks and all other stuff there. It really depends on what you need and how you will use it.
I need to test that a react component is called with opened={true} prop after an button click is fired. I am using testing-library ( testing-library/react + testing-library/jest-dom).
I mocked the Component using something like
import Component from "./path-to-file/component-name"
...
jest.mock("./path-to-file/component-name", () => {
return jest.fn().mockImplementation(() => {
return null
})
})
I first tried with:
expect(Component).toBeCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenCalledWith(expect.objectContaining({"opened": true}))
expect(Component).toHaveBeenLastCalledWith(expect.objectContaining({"opened": true}))
but I got Error: expect(jest.fn()).toBeCalledWith(...expected).
Same went for expect.objectContaining({"opened": expect.anything()})
And even for expect(Component).toBeCalledWith(expect.anything())
And the difference is empty array:
I also tried with expect(ChartMenu.mock).toBeCalledWith(expect.anything()). I got a different error but still not working (this time the error was Error: expect(received).toBeCalledWith(...expected) + Matcher error: received value must be a mock or spy function)
Thank you in advice!
EDIT: here is a simplified version of the component I want to test:
const Component = () => {
const [chartMenuOpened, setChartMenuOpened] = useState(false)
return (
<Flex>
<EllipseIcon onClick={() => setChartMenuOpened(true)}>
+
</EllipseIcon>
<ChartMenu
opened={chartMenuOpened}
close={() => setChartMenuOpened(false)}
/>
</Flex>
)
}
Basically I want to make sure that when the + icon is clicked the menu will be opened (or called with open value). The issue is that I cannot render ChartMenu because it needs multiple props and redux state.
I was able in the end to mock useState in order to check that the setState was properly called from the icon component (in order to make sure there won't be future changes on the component that will break this using this post).
But I would still really appreciate an answer to the question: if there is any way to create a spy or something similar on a react component and check the props it was called with? Mostly because this was a rather simple example and I only have one state. But this might not always be the case. Or any good idea on how to properly test this kind if interaction would be really appeciated.
I think you are on the right track to test if the component has been called with that prop, it's probably the syntax issue in your code
I learn this trick from colleague and you can try to see if this helps fix your issue.
expect(Component).toHaveBeenCalledWith(
expect.objectContaining({
opened: true,
}),
expect.anything()
);
While the question on how to is answered, I did some extra research on this and it seems like in React components there is a second parameter refOrContext (so basically most of the time it's an empty object, but it can also be a ref or a context)
Despite pointing out the reason for the behavior, I also wanted to highlight that it is safer to use expect.anything() as the second argument (rather than just {} which would work only in most of the cases ):
More information about React second argument here
I got two screens and want to pass the state from one to the other.
Simplified Screen 1:
import React, { Component } from 'react';
import { View, TextInput} from 'react-native';
import { buttons } from '../../components/buttons/Buttons';
export default class LoginScreen extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
}
}
render() {
return(
<View>
...
<TextInput onChange={(input) => this.setState({username: input})}></TextInput>
...
<Button.main onPress={() => this.props.navigation.navigate('Register', {username: this.state.username})} title='Create Account'/>
</View>
)
}
}
Now, if I press the button, I want to pass this.state.username to the new screen. If I don't use onChange={(input) => this.setState({username: input})} and set a value for the username manually like this:
this.state = {
username: 'Test',
}
The value gets passed without problem and I can access it from the new screen. But when I try to change this.state.username while the TextInput changes and then pass this.state.username I get the following warning:
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property isTrusted on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist().
I don't know how to use event.persist() as proposed in the warning and the username doesn't get passed.
Sorry if this is a stupid question, but I couldn't find a solution and I'm a beginner. I would be glad if someone could help me with this :)
Try using onChangeText instead of onChange.
Adding on to accepted answer to provide a more elaborate explanation.
The behaviour observed is a combination React's intended design for events and how Javascript uses the pass-by-reference concept for passing objects into function:
React's design for events
The context here is that the function is passed to the onChange handler rather than onChangeText. onChange will pass an event object to the function while onChangeText passes a String. There is a catch to how event objects works in React.
React uses SyntheticEvent for events passed into event handlers and SyntheticEvents are pooled. So rather than creating new SyntheticEvent objects, these pooled objects are reused and all properties will be nullified after a callback is invoked (see React docs).
Pass-by-reference for objects in Javascript
In Javascript, when an object gets passed into a function as argument, the function receives the reference to that object as parameter rather than creating a copy of the object. Thus, modifying the object in the function will lead to the original object being modified.
Putting them together
Setting the passed event object parameter to state is basically storing the reference to one of the event objects in the pool into state. Since this object is reused (modified) and its content does not persist for long, the content retrieved from the reference stored in the state will quickly become irrelevant. This will cause the app to behave in unexpected ways.
The development environment detects this and thus give a warning.
The use of onChangeText - like the accepted answer recommended - will not only solve the problem but also seemingly be closer the original intention of the code.
Is there a way to evoke a method on a chart. Such as:
chart.zoomOut()
I'm struggling to find a handle for the chart object.
To render the chart i use:
<AmCharts ref={`chart_${this.props.tileid}`} {...this.chart} dataProvider={this.props.data} />
https://github.com/amcharts/amcharts3-react
I tried to inspect the element to see if I can access these methods through:
ch = this.refs[`chart_${this.props.tileid}`]
However in the 'ch' object I can not seem to find any of the methods mentioned in:
https://docs.amcharts.com/3/javascriptcharts/AmSerialChart#zoomOut
How would one reference a element to evoke a method on it?
The amchart3-react component sets the chart as a state. Thus the method can be evoked by calling:
this.refs.chartref.state.chart.zoomOut();
You will have to talk to the maintainers of amCharts3-react. The wrapper component library could have defined the component to accept as property an event-handler that is called when the wrapper's componentDidMount method has been called, so that the event-handler can grab an instance of an object that the wrapper component is a container of.
Example:
// Note, this is just a hypothetical property. It will not actually do anything
// when applied to your code.
<AmCharts onChartRendered={chart => this._amChart = chart } {...props} />
But it seems like the author of the wrapper did not define the component to accept any such property, and therefore there isn't any way for you to invoke any methods from amCharts.