Modal with variable content - javascript

I'm new to react native.
My screen contains 5 buttons, each one opens the same <Modal>, but the <View> inside it will change depending on the button clicked.
If I click the first button, a text input will be shown into the modal.
If I click the second button, a switch will be shown into the modal.
I've made a modal component (Modal.tsx) :
export default class Modal extends Component {
constructor(props) {
super(props)
}
public render() {
return (
<View style={style.modal} >
{this.props.children}
<View>
)
};
}
// Specific modal implementation with TextInput
const ModalWithTextInput = props => (
<Modal>
<TextInput
value={props.someValue}
/>
<Modal>
)
// Specific modal implementation with Switch
const ModalWithSwitch = props => (
<Modal>
<Switch
value={props.someValue}
/>
<Modal>
)
And now in my 5-button-screen (ButtonsScreen.tsx), I need to open the right modal depending on the button clicked :
openTextModal = () => {
this.setState({ modalType: 'text' });
}
openSwitchModal = () => {
this.setState({ modalType: 'switch' });
}
These functions are called with onPress={this.openTextModal}
Finally, I need to render the modal, to be able to do something like :
<View>
{this.renderModal(modalType)}
</View>
But I don't know how to deal with the renderModal function
renderModal = (type) => {
if (type === 'text') {
return ???
}
if (type === 'switch') {
return ???
}
}
Anyone can please help ? Thanks.

You can use all you've already done to do this :
renderModal = (type) => {
if (type === 'text') {
return <ModalWithTextInput someValue="default text" />
}
if (type === 'switch') {
return <ModalWithSwitch someValue={false} />
}
}
Be sure to import these components in your file to be able to use them (not needed if you declared them in the same file)

All you want to do is change the content the Modal renders. So you do not need to render different Modals for them just the content. Do not forget to set the modal state to visible.
renderModal = () => (
<View>
<Modal visible={this.state.modalVisible}>
{this.renderModalContent()}
</Modal>
</View>)
renderModalContent = () => {
if(this.state.modalType === 'switch'){
return (<Switch
value={props.someValue}
/>);
} else {
return (
<TextInput
value={props.someValue}
/>)
}
}

Related

Modal component does not render on a custom button component

I am trying to render a custom and dynamic modal on button clicks. For example, when a "Game" button is clicked, I would like a modal to render with specfics about the game and when a "Bank" button is clicked, I would like the modal to populate with specfics about a bank.
First, when I add an onClick function to a custom button component, the modal does not render. However, when I put the onClick function on a regular button, the modal does render. How can I simply add an onClick function on any component to render a dynamic modal?
Second, I would like to populate each modal with differnet data. For example, a "Game" button would populate the modal with a title of "Game" and so on. I'm using props to do this, but is that the best solution?
Here is the code I have so far, but it is broken when I add the onClick function to components.
// Navbar.js
import { ModalContext } from '../contexts/ModalContext'
function Navbar() {
const [showModal, updateShowModal] = React.useState(false)
const toggleModal = () => updateShowModal((state) => !state)
return(
<ModalContext.Provider value={{ showModal, toggleModal }}>
<Modal
title="Title"
canShow={showModal}
updateModalState={toggleModal}
/>
</ModalContext.Provider>
)
// does not render a modal
<Button
onClick={toggleModal}
type="navItem"
label="Game"
icon="windows"
/>
// render a modal
<button onClick={toggleModal}>Show Modal</button>
)
}
import { ModalContext } from '../contexts/ModalContext'
// Modal.js
const Modal = ({ title }) => {
return (
<ModalContext.Consumer>
{(context) => {
if (context.showModal) {
return (
<div style={modalStyles}>
<h1>{title}</h1>
<button onClick={context.toggleModal}>X</button>
</div>
)
}
return null
}}
</ModalContext.Consumer>
)
}
// modalContext.js
export const ModalContext = React.createContext()
// Button.js
function Button({ label, type = 'default', icon }) {
return (
<ButtonStyle buttonType={type}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
First problem:
I think the onClick prop of the <Button> component is not pointing to the onClick of the actual HTML button inside the component.
Could you please check that? And if you think It's been set up in the right way, then can you share the code of the component?
Second Problem
Yes, there's another way to do that. And I think it's React Composition. You can build the modal as the following:
<Modal
showModal={showModal}
updateModalState={toggleModal}
>
<div className="modal__header">{title}</div>
<div className="modal__body">{body}</div>
<div className="modal__footer">{footer}</div>
</Modal>
I think this pattern will give you more control over that component.
Issue
You are not passing the onClick prop through to the styled button component.
Solution
Given style-component button:
const ButtonStyle = styled.button``;
The custom Button component needs to pass all button props on to the ButtonStyle component.
// Button.js
function Button({ label, type='default', icon, onClick }) {
return (
<ButtonStyle buttonType={type} onClick={onClick}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
If there are other button props then you can use the Spread syntax to collect them into a single object that can then be spread into the ButtonStyle component.
// Button.js
function Button({ label, type = 'default', icon, ...props }) {
return (
<ButtonStyle buttonType={type} {...props}>
{setIcon(icon)}
{label}
</ButtonStyle>
)
}
Second Question
For the second issue I suggest encapsulating the open/close/title state entirely in the modal context provider, along with the Modal component.
Here's an example implementation:
const ModalContext = React.createContext({
openModal: () => {},
});
const Modal = ({ title, onClose}) => (
<>
<h1>{title}</h1>
<button onClick={onClose}>X</button>
</>
)
const ModalProvider = ({ children }) => {
const [showModal, setShowModal] = React.useState(false);
const [title, setTitle] = React.useState('');
const openModal = (title) => {
setShowModal(true);
setTitle(title);
}
const closeModal = () => setShowModal(false);
return (
<ModalContext.Provider value={{ openModal }}>
{children}
{showModal && <Modal title={title} onClose={closeModal} />}
</ModalContext.Provider>
)
}
Example consumer to set/open a modal:
const OpenModalButton = ({ children }) => {
const { openModal } = useContext(ModalContext);
return <button onClick={() => openModal(children)}>{children}</button>
}
Example usage:
function App() {
return (
<ModalProvider>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<OpenModalButton>Modal A</OpenModalButton>
<OpenModalButton>Modal B</OpenModalButton>
</div>
</ModalProvider>
);
}
Demo

How to execute a method for the active button in the list using typescript and react?

i have items with add button listed in a dialog. when one add button in list is clicked it wil be active and clicking button for other item in list will make it active. Basically most recent clicked button in list will be active.
below is the picture of add buttons in list
[![enter image description here][1]][1]
below is my code,
function Dialog({items}: Props){
return (
<List items={items}/>
);
}
function List({items}: Props);
const [activeIndex, setActiveIndex] = React.useState();
return (
items.map((item: any, index: number) => {
<Card>
<AddButton
index={index}
isActive={activeIndex===index}
setActiveIndex={setActiveIndex}
/>
</Card>
}
);
}
function AddButton ({isActive, setActiveIndex, index}: Props) {
const {toggleDrawing} = useDrawing(item);
const handleClick = () => {
setActiveIndex(index);
if (isActive) { //if button is active then only call toggleDrawing and it doesnt work
toggleDrawing();
}
}
return (
<IconButton
active={isActive}
onClick={handleClick}
/>
);
}
The above code, properly makes the respective button active in list. but it doesnot call toggleDrawing method for the button clicked.
what i want is when user clicks add button on one item in list then this should become active and toggleDrawing and when user clicks another button in list then toggleDrawing for previous should be stopped and this button should be active and toggleDrawing should start for this button
how can i do it?
could someone help me with this. thanks.
use React.useEffect that listens to changes in the active prop of the AddButton and then toggle your drawing.
function Dialog({items}: Props){
return (
<List items={items}/>
);
}
function List({items}: Props);
const [activeIndex, setActiveIndex] = React.useState();
return (
items.map((item: any, index: number) => {
<Card>
<AddButton
index={index}
isActive={activeIndex===index}
setActiveIndex={setActiveIndex}
/>
</Card>
}
);
}
function AddButton ({isActive, setActiveIndex, index}: Props) {
const {toggleDrawing} = useDrawing(item);
const handleClick = () => {
setActiveIndex(index);
}
React.usEffect(() => {
if (isActive) {
toggleDrawing();
}
}, [isActive]);
return (
<IconButton
active={isActive}
onClick={handleClick}
/>
);

reuse Modals in React

I have a very simple modal that I want to reuse to display dynamic data
class Modal extends Component {
constructor(props) {
super(props)
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
handleKeyDown(event) {
if (event.key === 'Escape') {
this.handleCloseModal()
}
}
handleCloseModal() {
// What should I do here? Unmount?
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown, false);
}
render() {
return(
<div className="modal">
<button onClick={() => this.handleCloseModal()}>close modal</button>
{this.props.children}
</div>
)
}
}
export default Modal
I would like to open the modal with the data from elem.description for each elem in the records array.
{records.map(function(elem, index) {
return <button
key={index}
onClick={// Something like <Modal>{elem.description}</Modal>}
/> open modal for {elem.name}</button>
})}
I read about some implementations that will do something like
<Modal show=true>...</modal>
and toggle the visibility of it. can i Just Unmount the component? is that a a good practice?
One way of doing that is to save the clicked element in the state and then, when it is clicked display the modal.
{records.map(function(item, index) {
return <button
key={index}
onClick={this.openModal(item)} // open modal and set the current item in the state
/> open modal for {elem.name}</button>
})}
You need a function to set the element as current and open the modal
openModal = (item) =>() => {
this.setState({currentItem:item, isModalVisible: true})
}
Finally in your modal just pass an item component as chidlren and give it data coming from the state
<Modal isVisible={this.state.isModalVisible}>
<MyItem data={this.state.currentItem} />
</Modal>

render displaySIngleElement component onClick react

I am pretty new to react and I have been stuck in a problem for quite a good time.
I have a component DisplayList that iterates through an array of objects and displays them in a list form. Each object becomes a button. I also have another component to render the single view of each item on the list once the item is clicked. My problem is that I get to render the single view of all my items at once INSIDE my displayList component. All I want is to be able to click on the list item and render another component with ONLY info about the item I clicked on and passing my "project" as the props to it. what should I do? What is my error?
My DisplayList component (the part that matters for this problem):
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = () => {
this.setState({
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={this.handleClick}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
You are just a hint away from doing it the right way:
Change the condition in your onClick() as:
onClick={()=>this.handleClick(project.id)}
{ this.state.displayProject_id === project.id ?
<DisplaySingleItem project={project} /> :
null
}
Now define handleClick() as:
handleClick = (project_id) => {
this.setState({
displayProject_id: project_id
})
}
Don't forget to define the initial state in the constructor:
this.state = {
displayProject_id:null
};
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={()=>this.handleClick(project)}>
{this.state.displayProject && this.state.displayProject.id==project.id ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
</ListItem>
</div>
change your JSX like the above so you pass the current project to handleClick and change handleClick like the following.
handleClick = (project) => {
this.setState({
displayProject : project
})
}
It should now display the <DisplaySingleItem/> for the clicked project.
For you to be able to show only the project that was selected it is important that you have a reference to it. Right now your handleClick() function does not accept and parameters or data that you can identify the project that was selected.
My solution for you is to pass the project as a parameter to handleClick(project). So your code should look like.
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = (project) => {
this.setState({
selectedProject: project, // <- use this state to show your popup or
// whatever view you're using
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={() => this.handleClick(project)}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
)
}

State Change in one class when the buttons are clicked on left hand sidebar in the UI and data shown in the content area on right hand side

There are two classes one is caregiver and other is Registeration Form.
I want to show the registeration form class inside the content tag of caregiver when the regitser button available on the sidebars is clicked. So different data needs to be shown with each button clicked on right side
class Caregiver extends React.Component {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<Layout>
<Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
style={{ background:'#122a44'}}
>
</Sider>
</Layout>
);
}
}
export default Caregiver;
class RegisterationForm extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<Form onSubmit={this.handleSubmit} className="registeration-abs">
</Form>
</div>
);
}
}
const RegisterPatients = Form.create()(RegisterationForm);
export default RegisterPatients;
You can simply add in the content tag.
And add a state of showingForm, triggered by the button you add (simialr to your sidebar triggering) :
<Content>
<RegisterationForm showing={this.state.showingForm} />
</Content>
Inside, RegisterationForm render function can be defined as conditionally showing the content :
render() {
return (
!this.props.showing ? null :
....
);
}

Categories

Resources