Propagate action between react components - javascript

So, I have re-used a login/register modal and added a Modal to be able to show/hide it.
So I have implementend in the index.js below:
const Form = ({ initialState = STATE_SIGN_UP, showPopUp = STATE_HIDE }) => {
const [mode, toggleMode] = useToggle(initialState);
const [display, toggleDisplay] = useToggleDisplay(showPopUp);
return (
<Modal className="modal" show={showPopUp} size="lg">
<Container pose={mode === STATE_LOG_IN ? "signup" : "login"}>
<div className="container__form container__form--one">
<FormLogin mode={mode} />
</div>
<div className="container__form container__form--two">
<FormSignup mode={mode} />
</div>
<Overlay toggleMode={toggleMode} mode={mode} />
</Container>
</Modal>
);
};
and I have added in FormLogin a close icon and added onClick. I used a console.log to verify if it's working. However, I do not understand how to be able to send and make the modal in the index.js closed when the action on close happened in the FormLogin class.
I tried to use the toggleDisplay but I have lost my track doing it. toogleDisplay is done as below:
export const STATE_SHOW = true
export const STATE_HIDE = false
const useToggleDisplay = initialState => {
const [display, setDisplay] = useState(initialState)
const toggleDisplay = () =>
setDisplay(display === STATE_SHOW ? STATE_HIDE : STATE_SHOW)
return [display, toggleDisplay]
}
export default useToggleDisplay
Any idea ?

Just pass an onClose handler(which equals toggleMode here) to your modal and call it every time the close button is clicked.

You need to call toggleDisplay in order to close your modal, pass it into your form component
<FormLogin mode={mode} toggleDisplay={toggleDisplay} />
Then inside FormLogin component wherever your click handler is for closing the modal pass in toggleDisplay(false)
<button onClick={() => toggleDisplay(false)}>Close</button>

Related

Way to render a new component onClick in react js

Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}

why does my prop cease to exist after initially existing when I pass it through to a functional component?

This is the function where I am passing the onClick prop (setShowModal is setState() from the useState hook):
<MyFunctionalComponent
onClick={() => setShowModal(true)}
...other props here
/>
This is the functional component that receives the prop:
export const MyFunctionalComponent = ({ onClick }) => {
return (
<section>
...other code here
{onClick && (<Button>{ctaText}</Button>)}
</section>
);
};
But the Button component never appears, because the prop onClick is undefined. When I console.log the prop inside the functional component, it initially prints the function in the console, but then prints two more times as undefined. Could someone explain why that would be? I got it to work by spreading ...props instead. But the console.log remains the same? I don't understand why. This is my first question on Stack Overflow, so feel free to give me feedback on how to ask better questions :)
The reason why you are receiving an 'undefined' response is because as #Zrogua mentioned, onClick is an event listener function rather than a persistent value (like state you define).
import React from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return <section>{onClick && <button>here</button>}</section>;
};
const ParentDiv = () => {
return (
<div>
<h1>Button Props</h1>
<h2>Start editing to see some magic happen!</h2>
<YourButton onClick={() => console.log("CLICK")} />
</div>
);
};
export default ParentDiv;
Result of console.log():
function onClick() // index.js:27:25
The reason why this is because props are read-only. From the React Docs:
Whether you declare a component as a function or a class, it must never modify its own props ... Such functions are called “pure” because they do not attempt to change their inputs, and always return the same result for the same inputs.
Therefore your button will only show if the onClick function is defined. For example, if you did not give onClick a function or value, the button will not appear:
import React, { useState } from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return (
<section>
{onClick && <button>This button is shown if a button is defined.</button>}
</section>
);
};
const ParentDiv = () => {
return (
<div>
<h1>Button Props</h1>
<YourButton onClick={() => console.log("CLICK")} />
<YourButton /> {/* You won't see this button because the function is not defined. */}
</div>
);
};
export default ParentDiv;
The button appears because the prop has a value that is not undefined (your onClick function), and because it is read-only, you cannot access that function in your child component.
Instead, (1) define the modal state in the parent component and (2) pass the state through props to the button like so:
import React, { useState } from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return (
<section>
{onClick && <button>This button is shown if a button is defined.</button>}
</section>
);
};
const AltButton = ({ modal }) => {
return (
<section>
{modal && (
<button>This button is shown the modal state is passed.</button>
)}
</section>
);
};
const ParentDiv = () => {
const [modal, setModal] = useState(false);
return (
<div>
<h1>Button Props</h1>
<YourButton onClick={() => console.log("CLICK")} />
<YourButton />{" "}
{/* You won't see this button because the function is not defined. */}
<section>
<button onClick={() => setModal(!modal)}>OPEN MODAL</button>
</section>
{modal && <p>this is dependent on state</p>}
<AltButton modal={modal} />
</div>
);
};
export default ParentDiv;
Working CodeSandbox: https://codesandbox.io/s/stack-66715327-passingfunctions-92pzr
Finally, if I am reading between the lines and understanding correctly that you are looking to hide a button when a modal is open, here is a little modal wrapper trick I use for buttons that open modals: https://codesandbox.io/s/stack-66715327-modalwrapper-wvl54
You can't pass onClick, onClick is just an event listener. You should pass the state
<MyFunctionalComponent onClick={() => setShowModal(!showModal)}
showModal={showModal}
...other props here />
/>
export const MyFunctionalComponent = ({ showModal }) => {
return (
<section>
...other code here
{showModal && (<Button>{ctaText}</Button>)}
</section>
);
};
I believe this should work. Let me know if this is what you were looking for.
I think that rather then passing callback you should pass variable which decide if component should show or not. Check this example.
export const MyFunctionalComponent = ({ isShow, onClick }) => {
return (
<section>
...other code here
{isShow && <div>something</div>}
</section>
);
};
export default function App() {
const [showModal, setShowModal] = useState(false);
return (
<MyFunctionalComponent
isShow={showModal}
onClick={() => setShowModal(true)}
/>
);
}
I also suppose that you can make mistake and have something other on mind .. like this:
<section>
...other code here
<button onClick={ onClick }>something</button>}
</section>

React render modal in list

I have a list of items and for each item in the list has an edit button to show a modal with that items details. Originally I had a single modal component in the parent and when I click the edit button it would pass the visible values up to the parent state to show the modal.
The problem is when I did that, the entire list would re render which I dont want because the list can have hundreds of items in it. So right now the solution I have is that in each item of the list I have a modal associated with it. It works but it doesnt seem right because I am duplicated code unnecessarily.
The code is too large to put on here but these are the relevant parts:
import Modal from '../Modal';
const CustomCard = ({
...omitted
}) => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const onEditModal = () => {
setEditCustomerModal(true);
};
return (
<>
<CustomerModal
onSuccess={handleUpdateCustomer}
onCancel={handleCancelModal}
visible={editCustomerModal}
title="Edit Customer"
details={{
.. ommitted
}}
/>
<Card />
....data
</Card>
</>
);
};
export default CustomCard;
import CustomCard from '../CustomerCard/index';
const CustomList = ({ dataSource }) => {
return (
<div>
{dataSource?.map(i => (
<CustomCard
...props ommitted
/>
))}
</div>
);
};
export default CustomerList;
import CustomerList from './components/CustomerList/index';
// import Modal from './components/AddCustomerModal';
const CustomersPage = () => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const [editCustomer, setEditCustomer] = useState(null);
return (
<>
// This is where I would want it ideally
<Modal
onSuccess={handleSaveCustomerEdit}
onCancel={handleCancelCustomerEdit}
visible={editCustomerModal}
details={editCustomer}
/>
<CustomerList
dataSource={data}
/>
</div>
{/* </ModalContext.Provider> */}
</>
);
};
export default CustomersPage;
You can define one selected useState atribute, that stores only the register that you want to pass to your modal.
For example:
import CustomerList from './components/CustomerList/index';
import Modal from './components/AddCustomerModal';
const CustomersPage = () => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const [editCustomer, setEditCustomer] = useState(null);
const [selected, setSelected] = useState(null);
return (
<>
<Modal
onSuccess={handleSaveCustomerEdit}
onCancel={handleCancelCustomerEdit}
visible={editCustomerModal}
details={editCustomer}
selectedCard={selected}
/>
{dataSource?.map(i => (
<CustomCard ...props ommitted setSelected={setSelected}/>
//somewere in CustomCard, trigger the event to set selected props that you want to pass for Modal
//remember to be careful with nullable object. use selectedCard?.something
))}
</>
);
};
export default CustomersPage;

Remove stuff through props

So basically I want when I press on the Button I want to Icon to disappear as well as Button. I have tried something but clearly, it is not working, so I would appreciate some help if possible
const Button = (props) => {
const[toggleIcon, setToggleIcon]=React.useState('true')
function Icon() {
toggleIcon(false)
}
return(
<div>
<button onClick={()=> setToggleIcon('false')}></button>
</div>
)
}
export default Button
const Icon = (props) => {
return(
<div>
<Icon>{props.Icon('false')}</Icon>
</div>
export default Icon
You can prevent a component from rendering by returning null. Use conditional rendering with the toggleIcon state as the condition. The onClick handler changes the state.
E.G.:
const Button = () => {
const [toggleIcon, setToggleIcon] = React.useState(true)
return toggleIcon ? (
<button onClick={() => setToggleIcon(false)}><Icon /></button>
) : null
}
export default Button
Take a look at the Conditional Rendering section of the React docs.

React Hooks: Transition don't work with Node as Prop

I have a Dropdown made with React Hooks. The button should have Arrow, which rotate. My first Version works fine:
const DropdownMenu= (props) => {
const [open, setOpen] = useState(false);
const openDropdown = (): void => setOpen(prevState => !prevState);
return (
<div>
<Button
onClick={openDropdown}
dropdownIsOpen={open}
>
Text </Button>
<DropdownContent isOpen={isOpen} />
</div>
);
};
export default DropdownMenu;
const Button = (props) => {
return (
<Button Click={props.onClick}>
Text
<Arrow rotate={props.dropdownIsOpen} color={designTheme.color.primary} />
</Button>
);
};
export default Button;
But now I have multiple Dropdowns and want to use them with the same component. I gave the toggleNode as prop. Now the Arrow rotate but without the transition:
const DropdownButton = (props) => {
const [isOpen, setOpen] = useState(false);
const onToggle = (): void => setOpen(prevState => !prevState);
return (
<Dropdown
isOpen={props.isOpen}
onToggle={onToggle}
toggleNode={
<Button dropdownIsOpen={isOpen}>
Text
</Button>
}
/>
);
};
export default DropdownButton;
Have someboy an idea?
Thank you!
my guess is, you are exporting different Button
How can you use Button inside Button, possibly by importing Button from somewhere else. And then you are again exporting Button. This will be confuse for transpiler, as which Button to be exported.
You might want to rename your custom Button to something else
const Button = (props) => {
return (
<Button Click={props.onClick}>
Text
<Arrow rotate={props.dropdownIsOpen} color={designTheme.color.primary} />
</Button>
);
};
export default Button;
Given your Button component takes an onClick prop:
const Button = (props) => {
return (
<Button Click={props.onClick}>
Text
<Arrow rotate={props.dropdownIsOpen} color={designTheme.color.primary} />
</Button>
);
};
NOTE: As #SagarMore points out, there my also be a naming collision between some imported Button component and your Button component.
You may just need to pass a callback to Click (terrible name, BTW, should be onClick, hopefully it was just a typo):
const DropdownButton = (props) => {
const [isOpen, setOpen] = useState(false);
const onToggle = (): void => setOpen(prevState => !prevState);
return (
<Dropdown
isOpen={props.isOpen}
onToggle={onToggle}
toggleNode={
<Button onClick={onToggle} dropdownIsOpen={isOpen}>
Text
</Button>
}
/>
);
};
Passing onToggle to the inner button's onClick handler should now toggle the isOpen state of the DropDown.
This was my fault.
In my Dropdown component I render two different states conditional. So it renders the start or the end state and don't use the animation. I have to render a animation and don't render new when I click so the animation works.

Categories