IconButton works on secound click - reactjs

It's driving me nut's. onClick handler is firing on secound click. I dont know why. The functional component is so simple that should work. This component is a part of another comp. so it's a child. Parent comp is on the list. That is whole setup. If I click first time I saw on the debug that it's not's going fire the event. Style span of IconButton are changing in first click. Strange
const MoreInfo = () => {
const [more_info, setMore_info] = useState("test");
const handleExpand = event => {
if (icon_expand === "expand_more") {
setMore_info("test2");
}
};
return (
<IconButton onClick={handleExpand} aria-label="location" size="small">
<Icon style={{ fontSize: 12 }}>{icon_expand} </Icon>
</IconButton>
);
};
export default MoreInfo;

Actually, it does work as you expected.
I just copy pasted your code in codesandbox and it does work here.
https://codesandbox.io/s/zen-dan-d5r8r
However, as you mentioned the whole tree of components and it's child component of any parent component.
So I guess and you should check some of this:
check onClick of parent and make sure it's not conflicting with any other onClick ( name ).
make sure state updation works properly
check all methods are binded ( only if applicable )

Related

React component switch animation with a loader - Glitchy

I have a requirement where i have couple of components out of which only one could be displayed at a particular point of time. The components are of varying heights.
When a user clicks on the button in the currently displayed component i need to
Show a loader to the user
Render the component in background.
Wait till the rendered component calls onLoad callback.
Once onLoad callback is recived, hide the loader.
Something like below (Code sandbox here)
const [loading, setLoading] = useState(false);
const [activeElement, setActiveElement] = useState("Component1");
const onLoad = () => {
setLoading(false);
};
const onClick = () => {
setLoading(true);
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
};
return (
<div className="container">
<ViewWrapperHOC loading={loading}>
{activeElement === "Component1" ? (
<Component1 onLoad={onLoad} onClick={onClick} />
) : (
<Component2 onLoad={onLoad} onClick={onClick} />
)}
</ViewWrapperHOC>
</div>
);
I was planning to write a wrapper component (ViewWrapperHOC in above example) to show the transition between the components and show the loader. Now the problem is when i try to animate, since i have to render both the progressbar and children in viewwrapper, the animation seems to be very glitchy.
Code sandbox here
Could someone suggest a way to fix this. I am open to any alternate animations or any alternate approach to achieve this in any form of pleasant ux. Thanks in advance.
In that second code sandbox, your issue is that as soon as you click, you are changing which element is active, therefore it gets rendered.
You could put a small timeout in the onClick function:
const onClick = () => {
setLoading(true);
setTimeout(() => {
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
}, 100);
};
Then in view wrapper you'll need to get rid of transition: "200ms all ease-in-out, 50ms transform ease-in-out".
You need to have a known height here to be able to make a smooth change in height transition.
See codesandbox fork
Version 2:
Here is version 2, using refs. Main changes are moving the box shadow out of the components to the container in view wrapper. Adding refs to the components, passing the active ref to the wrapper and then setting the height with height transition.
To be honest, I'd probably restructure the code a lot if I had the time.

Set react native component prop through passed reference

I need to show/hide a modal based on user interaction (ie - a button press) in a component which is neither a parent or child of the modal itself. I'm currently trying to do so by passing the modal as a prop to the modal controller, but the following errors are thrown depending on which method I call:
TypeError: modal.setNativeProps is not a function
TypeError: modal.setState is not a function
Is there a way to show the modal given how this is structured?
import Modal from 'react-native-modal'
const modalRef = React.createRef();
const modal = <Modal ref={modalRef} isVisible={false}>
<ModalController modal={modalRef} />
export const ModalController = ({modal}) => {
function onButtonPress(){
modal.setState({isVisible:true})
modal.setNativeProps({isVisible:true})
}
return (
<Button title='Show Modal' onPress={onButtonPress()} />
)
}
Ciao, in case there is no parent/child relation between components, the only way I found to pass/set data from one component to another is use a global state manager like react-redux. With redux you can easly define a global state and set it from component that fire modal open/close. Modal component reads this state and open/close itself.
So I think you've gotten a little confused.
Firstly, remember that everything inside ModalController is going to execute on every render. Your function onButtonPress will be created every render (this is fine), but you are actually calling that function when you pass it to onPress render . This means you're executing onButtonPress on every render, which is probably not what you want.
This is an easy fix - you just remove the () so it's just onPress={onButtonPress}. Now it'll only trigger when the button is pressed.
More fundamentally, the solution to your problem is much simpler than what you've done in your code. Generally 'refs' are only used in special cases where you really want to tell your components what to do (like telling a ScrollView to scroll to a particular position, or telling an input to focus so the keyboard shows). If you're using a ref it should be very intentional.
So a simple solution to have a component with a button that shows a modal could look like:
import React, {useState} from 'react';
import {View, Button, Text} from 'react-native';
import Modal from 'react-native-modal';
export const ModalController = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
function onButtonPress() {
setIsModalVisible(true);
}
return (
<View>
<Button title='Show Modal' onPress={onButtonPress} />
{isModalVisible && <MyModal />}
</View>
);
};
const MyModal = () => (
<Modal>
<Text>Hey I am a modal</Text>
</Modal>
);
Notice the use of useState. This is how you hold 'state' in a functional component like this (as opposed to a class component where you would use setState.
I hope this helps. Let me know if you have questions!

useEffect function open/close modal based in props works only at first time (click)

Im learning React and using the new implemented "Hooks" from Documentation. I got a problem with a Modal (Dialog from Material UI) and the open/close function using useEffect() function.
I have already read these both articles: React.useState does not reload state from props and How to sync props to state using React hook : setState()
It's already helped me, I have forgot to use the useEffect() function instead I was just setting the useState from what comes from props. Learned that useState will be executed only one time for setting the initial state. But I have however more one problem.
function AddItemModal(props) {
const [open, setOpen] = useState(props.isOpen);
useEffect(() => {
setOpen(props.isOpen);
}, [props.isOpen]);
function handleClose() {
setOpen(false);
}
That works for the first time when I click at the add button (in another Component) and handle the click (it changes the state to true) and I pass it as props to the Modal. But when I click (in modal) at close and try to click at add to open it again, nothing happens. In case needed, here's the code from the component where I handle the click and call the modal.
function ItemsTable() {
const [addModalOpen, setAddModalOpen] = React.useState(false);
const handleAddClick = () => {
setAddModalOpen(true);
};
<Box mt={4} position="fixed" bottom={10} right={10}>
<Fab color="secondary" aria-label="Add" onClick={handleAddClick}>
<AddIcon />
</Fab>
</Box>
<AddItemModal isOpen={addModalOpen} />
You have split your modal state across two components which is confusing and going to lead to bugs like this. You should put the state in one place and update it where necessary. In this case, you could keep the modal state in the ItemsTable and pass in a handler for the modal to access.
Something like:
function ItemsTable() {
const [addModalOpen, setAddModalOpen] = React.useState(false);
const handleAddClick = () => {
setAddModalOpen(true);
};
const handleClose = ()=>{
setAddModalOpen(false)
}
<Box mt={4} position="fixed" bottom={10} right={10}>
<Fab color="secondary" aria-label="Add" onClick={handleAddClick}>
<AddIcon />
</Fab>
</Box>
<AddItemModal isOpen={addModalOpen} handleClose={handleClose}/>
I am not sure if I understood exactly what are you trying to do, but I see that useEffect does not use the state. In order useEffect to be called more than one times, you need to pass the state to it, so your [props.isOpen] needs to change to [open]

Is there an efficient way to create an onPress event handler in React Native's Stateless/Functional Component?

I have a stateless/functional component which gets an array of objects having two properties i.e. title and onPress() as props.
Now for the Button component's onPress event I want to call this object's onPress function as well as an action creator (which too is being passed as props to this component).
Is there any way to just create one instance of onPress event handler which would call these two functions(button object's onPress function and action creator) ?
Below is a snippet code:
buttons.map(button =>
<Button
title={button.title}
onPress={ // call button.onPress and action creator using event handler }
/>
)
The reason for asking this is because every time this component would re-render the event handler too would be created again which could cause performance issue.
Do not separate props, just pass the button itself alongside with action creator:
buttons.map(button =>
<Button
button={button}
action={action}
/>
)
And in your Button component create a handlePress function and invoke the press there.
const Button = ( {button, action }) => {
const handlePress = () => {
button.onPress();
action();
}
return (
<div>
<button onPress={handlePress}>{button.title}</button>
</div>
)
};
I don't know React Native but I assume there is no so much difference with React itself.

How to use enzyme ShallowWrapper to find a React Component stored as a prop in another React Component?

I have a jest/enzyme test which creates a ShallowWrapper around a component, finds a specified semantic-ui-react Button (by id), simulates a click on the button, and then looks to see if the click toggled certain content.
Sample JSX:
<Popup
trigger={<Button onClick={this.toggleShowThing} id="special-btn">a button</Button>}
content="Popup Words"
/>
{this.state.showThing &&
<div className="special-thing">The Thing's Words</div>
}
Sample Test:
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
wrapper.find('#special-btn').simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});
This test worked when I just had the Button. When I added the Popup and the Button was placed into the trigger prop then I received an error because #special-btn could not be found.
Error: Method “props” is only meant to be run on a single node. 0 found instead.
An enzyme snapshot of the component shows that the Popup looks like this:
<Popup
content="Popup Words"
on="hover"
position="top left"
trigger={
<Button
id="special-btn"
onClick={[Function]}
>
a button
</Button>
}
/>
I need my test to work again. How do I gain access to the #special-btn again in the test so that I can call .simulate('click') on it?
This is what worked for me, although there is no documentation for it:
import {shallow, ShallowWrapper} from "enzyme";
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
const button = new ShallowWrapper(
wrapper.find('Popup').prop('trigger'), wrapper
);
button.simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});
In other words:
Find the Popup component.
Get the component rendered in its trigger prop. Note that this is not yet a shallow wrapper, so no fancy APIs yet.
Manually create the wrapper using ShallowWrapper (it's important to pass the second argument).
Now you can access all the enzyme APIs to interact with the button.
Note, it seems that you can avoid using the constructor and use wrap() utility method instead (also not documented):
const button = wrapper.wrap(wrapper.find('Popup').prop('trigger'));
Assuming Popup is some third-party component that has already been tested, I would approach testing the following way:
(1) Find the Popup and check if the trigger prop's Button's onClick prop is componentWrapper.instance().toggleShowThing
(2) As a separate thing, set this.state.showThing to false and verify no div with className special-thing is rendered; set this.state.showThing to true and verify it is rendered.
(*) this.toggleShowThing should also be tested on its own.
The problem is that you cannot do it. You need to rewrite your test. Your button is now wrapped by a Popup component thus you don't have access to it. But you can move your selector to the Popup and test if clicking the popup triggers required change. There's no other way around.
// JSX
<Popup
trigger={<Button onClick={this.toggleShowThing} id="special-btn">a button</Button>}
content="Popup Words"
id="popup"
/>
{this.state.showThing &&
<div className="special-thing">The Thing's Words</div>
}
// test
it('shows the thing when the button is clicked', () => {
const wrapper = shallow(<MyComponent />);
wrapper.find('#popup').simulate('click', { preventDefault() {} });
expect(wrapper.find('.special-thing').exists()).toBe(true);
});

Resources