I am triggering a react-native modal from a parent component, the modal shows up, when I try close the modal with a button in the child component / model itself, nothing happens.
Any help would be appreciated.
This is in child component
state = {
display: false,
};
setModalVisible(visible) {
this.setState({display: visible});
}
onPress={() => {
this.setModalVisible(!this.state.display);
}}
This is in parent component
<DisplayModal display={this.state.display} />
triggerModal() {
this.setState(prevState => {
return {
display: true
};
});
}
this.triggerModal()
You should negate the modal value to open and close it
triggerModal() {
this.setState(prevState => {
return {
display: !prevState.display
};
});
}
And instead of passing the state to setModalVisible, you can just use the callback setState.
setModalVisible() {
this.setState(prevState => ({display: !prevState.display}));
}
onPress={() => {
this.setModalVisible();
}}
Edit:
You added your code and here is something that might be the problem
ADD_DETAILS(index) {
if (index === 0) {
console.log("clicked 0");
this.triggerModal();
console.log(this.state.display);
}
}
The reason why it doesn't open and closes can be because it doesn't pass that if condition.
Edit 2:
Now I see your problem, what you need to do is call triggerModal in the children.
So here is what you need to do
Pass triggerModal as props to the children
Call it when you want to close the modal.
So here is the change in the parent.
<DisplayModal display={this.state.display} triggerModal={this.triggerModal}/>
And in the child
onPress={() => {
this.props.triggerModal();
}}
try this:
in parent =>
state = {
display: false,
};
setModalVisible() {
this.setState({display: !this.state.display});
}
<DisplayModal handlePress = this.setModalVisible.bind(this) display={this.state.display} />
then in child:
onPress={() => {
this.props.handelPress();
}}
This is how I approached it:
function App() {
const [showModal, setShowModal] = useState(true);
const closeModal = () => setShowModal(false);
const activateModal = () => setShowModal(true);
return showModal ? (
<DisplayModal closeModal={closeModal} display={showModal} />
) : (
<button onClick={activateModal}>Display modal!</button>
);
}
const DisplayModal = props =>
props.display ? (
<>
<div>Display!!</div>
<button onClick={props.closeModal}>Hide!</button>
</>
) : null;
Pay no attention to hooks, just notice the pattern I have used, passing the closeModal function as a prop and calling it whenever it is the case, but from the child component.
at first
inital
state ={
display: false}
create function handalepress
handalePress=()=>{
this.setState({
display:!this.state.display
}
<DisplayModal onClick= {this.handalePress}display={this.state.display} />
Related
Suppose I have the following component that I would like to test:
const TestComponent = () => {
const [showModal, setShowModal] = useState(false);
return (
<>
<button
onClick={() => setShowModal(true)}
>
Show Modal
</button>
{ isOpen ? <Modal>...</Modal> : <div>No Modal</div>}
</>
)
}
Now I would like to have the component rendering the Modal component in its initial rendering and test its DOM. How can I pass showModal = true to it?
discribe("Rendered TestComponent", () => {
it("has Modal component", () => {
// Some operation needed here or after rendering the component?
render(<TestComponent />);
expect(screen.getByRole('input', {name: 'first-name' }).toBeInTheDocument;
})
})
First you have to attribute a data-testid to the button tag in order to manipulate it, keep the ID unique throughout the whole application.
Then you can use your favorite test lib to fire a click event, there are many avaliable that can do events, such as fire event:
const TestComponent = () => {
const [showModal, setShowModal] = useState(false);
return (
<>
<button
data-testid="show-modal"
onClick={() => setShowModal(true)}
>
Show Modal
</button>
{ showModal ? <Modal>...</Modal> : <div>No Modal</div>}
</>
)
}
describe("Rendered TestComponent", () => {
it("has Modal component", () => {
const { getByTestId } = render(<TestComponent />);
const showModalButton = getByTestId("show-modal");
fireEvent.click(showModalButton);
expect(screen.getByRole('input', { name: 'first-name' })).toBeInTheDocument;
});
});
I have a RadioGroup component that contains multiple RadioButton components. Here's the code for the RadioGroup component:
const RadioGroup = ({radioGroupData}) => {
const [radioGroupRefreshData, setRadioGroupRefreshData] = useState(radioGroupData);
const handleClick = (index) => {
setRadioGroupRefreshData(radioGroupRefreshData.map((obj, i) => {
if(i !== index) {
return {text: obj.text, isSelected: false};
}
return {text: obj.text, isSelected: true};
}));
};
return (
<View style={styles.container}>
{
radioGroupRefreshData.map((obj, i) => {
return <RadioButton index={i}
text={obj.text}
isSelected={obj.isSelected}
onClick={handleClick} />
})
}
</View>
);
}
The RadioGroup component has a state variable (an array) called radioGroupRefreshData. when each RadioButton is defined inside the RadioGroup, the handleClick function is passed as a prop in order to be called when a RadioButton is clicked. Here is the code for the RadioButton component:
const RadioButton = (props) => {
const [isSelected, setIsSelected] = useState(props.isSelected);
const initialRenderDone = useRef(false);
useEffect(() => {
if(!initialRenderDone.current) {
initialRenderDone.current = true;
}
else {
props.onClick(props.index);
}
}, [isSelected]);
const handlePress = () => {
if(!isSelected) {
setIsSelected(true);
}
}
return (
<TouchableOpacity style={styles.outsideContainer} onPress={handlePress}>
<View style={styles.radioButtonContainer}>
{ (isSelected) && <RadioButtonInnerIcon width={15} height={15} fill="#04004C" /> }
</View>
<Text style={styles.radioButtonText}>{props.text}</Text>
</TouchableOpacity>
);
}
From what I know, each RadioButton component should re render when the Parent's variable radioGroupRefreshData changes, but the RadioButton component's are not re rendering.
Thank you in advance for any help that you can give me!
Since you have a state in RadioButton you need to update it when the props change. So in RadioButton add useEffect like this:
useEffect(() => {
setIsSelected(props.isSelected);
},[props.isSelected]);
Also you don't have to mix controlled and uncontrolled behaviour of the component: do not set RadioButton state inside RadioButton since it comes from the RadioGroup
I have a parent functonal component:
const parentFunc = () => {
if (ref.current) {
ref.current.getKinList();
}
};
<TouchableOpacity
onPress={() => {parentFunc()}
>
<Text>click</Text>
</TouchableOpacity>
<ChildComponent
ref={ref}
/>
child class component:
componentDidMount = () => {
this.ref = { current: { function2 : this.function2 } };
};
function2 = () => {
console.log('called from child');
};
function2 is not getting called from parent component.
There are solutions available, but I am not able to figure out where I am going wrong.
When I consoled ref.current in parentFunc it is coming as undefined
You can do something like this:
export default function App() {
const actions = React.useRef({
setMyAction: (f) => {
actions.current.myAction = f;
}
});
return (
<div>
<div onClick={() => actions.current.myAction()}>click</div>
<ChildComponent actions={actions.current} />
</div>
);
}
const ChildComponent = ({ actions }) => {
actions.setMyAction(() => {
console.log("called from child");
});
return null;
};
Working example
Also keep in mind that ref is a special name, not a usual property.
I'm using a React Native Viewpager to take in user entry, and move to the next page on button press. Important to note that moving to the next page happens on button press, and not by normal scrolling, which is disabled.
The best way I could think to handle this was to have a state on the ViewPager, which would propagate into the child Entries.
ViewPager.tsx:
export default function ViewPager({ route, navigation }) {
const ref: React.RefObject<ViewPager> = React.createRef();
const [currentPage, setCurrentPage] = useState(0);
let setEntryPage = (page: number) => {
ref.current?.setPage(page);
setCurrentPage(page);
}
return (
<View style={{flex: 1}}>
<ViewPager
style={styles.viewPager}
initialPage={0}
ref={ref}
scrollEnabled={false}
>
{
GlobalStuff.map((entry, index) => {
return (
<Entry
key={index}
index={index}
pagerFocusIndex={currentPage}
pagerLength={quizDeck?.litems.length!}
setEntryPage={setEntryPage}
/>
)
})
}
</ViewPager>
</View>
);
};
Entry.tsx:
export function Entry(props: EntryProps) {
const inputRef: React.RefObject<Input> = React.createRef();
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
return (
<View>
<Input
// ...
ref={inputRef}
/>
<IconButton
icon="arrow-right-thick"
color={colorTheme.green}
onPress={() => {
props.index !== props.pagerLength - 1 ?
props.setEntryPage(props.index + 1) :
props.navigation!.reset({ index: 0, routes: [{ name: recapScreenName as any }] });
}}
/>
// ...
Unfortunately, inputRef appears to be null, and there is probably a better way of achieving what I'm trying to achieve anyway.
Anything in your render loop will be called every time the component renders.
// This is called on every render
const inputRef: React.RefObject<Input> = React.createRef();
// So is this, it's always null
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
Put side effects in effects.
// Untested
const inputRef = useRef();
useEffect(() => {
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
}, [inputRef.current, props.pagerFocusIndex, props.index]);
So, I have an icon component - For example, a search magnifying glass. Sometimes I'd like this icon to have a click function - <icon onClick={} /> which works fine. Other times I just want to use it for purely visual purposes.
I know I could pass some sort of function boolean prop. If true, return X, else Y.
The reason I don't want to do this with my icon component is because it's built up of a lot of SVG path information and it seems silly to duplicate all of that for the sake of adding an onClick event.
Is there a tidier, simpler way of doing that?
Here's an example of my code:
Function with param
export function myFunction(param) {
// Do something
}
Component 1:
import { myFunction } from "xxx";
const Icon = ({ ...props }) => {
const {
myParam,
} = props;
return ( <icon></icon> ); // Actually lots of svg stuff going on here...
}
What I'd like to happen sometimes:
return ( <icon onClick={() => myFunction(myParam)}></icon> );
This works, but I'm not sure it's the best, or cleanest way?!
import { myFunction } from "xxx";
const Icon = ({ ...props }) => {
const {
myParam,
clickEvent // true or false
} = props;
if (clickEvent ) {
return ( <icon onClick={() => myFunction(myParam)></icon> );
} else {
return ( <icon></icon> );
}
}
Component 2:
const SomeDiv = ({ ...props }) => {
const {
// Props,
} = props;
return ( <div><icon myParam="paramStuff" /></div> );
// Or
return ( <div><icon myParam="paramStuff clickEvent={true}" /></div> );
}
You could write it like this as well:
const Icon = ({ ...props }) => {
const {
myParam,
clickEvent // true or false
} = props;
const onClick = clickEvent ? () => myFunction(myParam) : undefined;
return ( <icon onClick={onClick}/> );
}
You could check for the prop if it is getting passed and type of prop is function and then based on that you can click or remove the click function. Something like below:
<icon
onClick={() => clickEvent ? myFunction(myAgrs): null } />
You can make it more generic and accept function as props you can do something like:
const { myFunction } = this.props;
<icon
onClick={() => myFunction && typeof myFunction === 'function'? myFunction(myAgrs): null } />