React ES 6 Classes refs - reactjs

I am very new in react native and I tried to refactor a source code from old react into react using ES 6 Class, but I got an error 'Cannot read property 'close' of undefined'. Can anyone help me why this.refs.drawer in closeDrawer is undefined?
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}
Regards

EDIT I did not notice that you were using arrow functions in your component's member functions, so you do not need to bind them. There were some other issues, though
This is a binding issue. This should work:
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true)}
onClose={() => this.setDrawerState(false)}
content={<DrawerScene closeDrawer={this.closeDrawer} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer}
openDrawer={this.openDrawer}
/>
</Drawer>
);
}
The problem with your code is that you are applying bind to the result of a function call. For instance, when you do this.setDrawerState(true).bind(this), the function is called, returns the appropriate value, and then bind is applied to it. This usually would result in an error, but here you are also trying to access a ref that has not yet been set up (because before that happens all prop values have to be evaluated before passed to the new component, which is exactly the problem here, the function is called before the component is instantiated).
Just so you know a bit more about bind: it is a property of a function object, so you need to access it from the reference to that function (in this case, its name). The result of bind is a new function with the same behaviour of the original one, save for the new this value or any other parameters you pass.

Try to set ref like this instead of a string:
drawer = null;
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref={((component)=> this.drawer=component)}
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}

Related

How can I get the value of a React prop in Jest unit tests?

I have a component like this:
export const TransactionReason = ({
changeReason,
reason
}) => {
const handleNext = () => {
if (!reason)
changeReason({
value: defaultReason
});
}
return (
<View>
<Picker
testID='reasonPicker'
selectedValue={reason?.code}
onValueChange={(value) => changeReason({
code: value,
value: value
})}
>
<Button
testID='reasonButton'
onPress={handleNext}
/>
</View>
);
I have an action called changeReason in the code that I'm testing. That action changes the value of the prop called reason. So, in my test, I'm firing the action with:
const changeReason = jest.fn();
const rendered = render(
<TransactionReason
changeReason={changeReason}
reason={null}
/>
);
const reasonNext = rendered.getByTestId('reasonButton');
fireEvent.press(reasonNext);
// the action changeReason has been called
// expect(rendered.props) ?
So here I wanted to check the new value of reason.
In other test, I have been testing props with:
expect(Picker.props.someProp).toEqual('Desired value);
But in those cases, that someProp is explicit in the component:
<Picker someProp={'a value'} />
Here I got and undefined when I try to access to:
expect(rendered.props.reason).toEqual('Desired value);
Any hint please?

React PDFDownloadLink - call onclick event through code explicitly

PDFDownloadLink from react-pdf library downloads a pdf when someone clicks on it.
I want to trigger this click event via code based on some condition .
How do I explicitly invoke the click of PDFDownloadLink through code?
A bit late, but you can pass a ref to the render function's return value and use it to call click() on imperatively. For that to work you need to use a separate component wrapper:
const DownloadLink = memo(function () {
const linkRef = useRef(null)
const onLoadingFinished = useCallback(function () {
// When this function is called the first time it is safe to initiate the download
const elem = linkRef?.current
if (elem !== null) {
elem.click()
}
}, [])
return (
<PDFDownloadLink document={<MyDoc />} fileName={'my-file.pdf'}>
{({ blob, url, loading, error }) => (
// You shouldn't call setState() here, so we need to use a separate component to keep track of whether the document has finished rendering
<WorkaroundContainer ref={linkRef} loading={loading} onLoadingFinished={onLoadingFinished} />
)}
</PDFDownloadLink>
)
})
const WorkaroundContainer = forwardRef(function ({ loading, onLoadingFinished }, ref) {
useEffect(() => {
if (!loading) {
onLoadingFinished()
}
}, [loading])
// If you only want to initiate the download imperatively, hide the element via CSS (e.g. `visibility: hidden`)
return (
<div ref={ref}>
{loading ? 'Loading...' : 'Download PDF'}
</div>
)
})

useRef in a dynamic context, where the amount of refs is not constant but based on a property

In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}

Material UI Snackbar autoHideDuration as null

I am trying to implement a dynamic Material UI Snackbar that can either have a specific time to "auto-hide" or not. This information will come as props when I call my custom component.
About the autoHideDuration property, the documentation says:
The number of milliseconds to wait before automatically calling the onClose function. onClose should then set the state of the open prop to hide the Snackbar. This behavior is disabled by default with the null value.
I know that if I omit this parameter, my Snackbar will not auto-hide. But if I try to specify this parameter with a null value, I get a Type Error:
Type null is not assignable to type number
Here is the relevant part of the code I have:
const SnackbarComponent = (props: SnackbarProps) => {
const autoHideValue = props.stayOpen ? null : 4000;
return (
<Snackbar
open={ture}
autoHideDuration={autoHideValue} // Type Error on this line
onClose={handleClose}
/>
);
};
And I call it like this:
<SnackbarComponent stayOpen={true} />
The only solution that I could think of, is to have a conditional return. But this doesn't seem like the best way to do it:
const SnackbarComponent = (props: SnackbarProps) => {
if(props.stayOpen){
return (
<Snackbar
open={ture}
onClose={handleClose}
/>
);
} else {
return (
<Snackbar
open={ture}
autoHideDuration={4000}
onClose={handleClose}
/>
);
}
};
Any ideas on how to implement this?
const SnackbarComponent = (props: SnackbarProps) => {
const snackbarProps = {
open: true,
onClose: handleClose,
// this condition solves your problem
...(props.autoHideDuration && { props.autoHideDuration })
// or with default value
// ...(!props.stayOpen && { autoHideDuration: 4000 })
}
return <Snackbar {...snakbarProps} />;
};

How to place return code in a function: React

I currently have a react project I'm working on. My render method looks like this going into my return method:
render() {
let elements = [];
this.dropdownCounter().forEach(item => {
if(item != "attributeProduct") {
console.log('setting');
elements.push(
<Dropdown
title={this.state[item][0]['title']}
arrayId={item}
list={this.state[item]}
resetThenSet={this.resetThenSet}
/>
);
}
});
this.state.attributeProduct.map(attributeItem => {
elements.push(
<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>
);
});
return (
I have a lot of code going on in the render area due to different drop downs dependent on other methods. Is there a way that I can do something like this instead?
render() {
allMyPrereturnStuff()
return()
}
Then just place all this code in allMyPrereturnStuff()? I've tried creating this function and passing everything there but it doesn't work due to all the "this". Any ideas?
Yes, you can easily drop in normal javascript expressions into JSX:
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{this.renderMoreStuff()}
</div>
);
You can even base it on flags:
const shouldRenderMoreStuff = this.shouldRenderMoreStuff();
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{shouldRenderMoreStuff ? this.renderMoreStuff() : null}
</div>
);
Do note that it is often an anti-pattern to have render* methods in your components other than the normal render method. Instead, each render* method should probably be its own component.
Don't forget to bind your allMyPrereturnStuff() method in the constructor so "this" will work inside it.
constructor(props) {
super(props);
// ... your existing code
this.allMyPrereturnStuff = this.allMyPrereturnStuff.bind(this);
}
allMyPrereturnStuff = (params) => {
// ... all the code
}
However, you might want to consider breaking out the code to components, which is more Reacty way to do things.
For example, you could refactor this
this.state.attributeProduct.map(attributeItem => {
elements.push(<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>);
});
To something like (somewhat pseudocody):
const DropdownList = (props) => {
return (<Dropdown
title={props.attributeItem.name}
arrayId='attributeMetaProduct'
list={props.list}
resetThenSet={props.resetThenSet}
/>);
}
And in the original component's render function, have something like
render() {
return (this.state.attributeProduct.map(attributeItem => {
<DropdownList attributeItem={attributeItem}
list={ this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID) }
resetThenSet={this.resetThenSet}
/>);
}

Resources