How to pass data from child to parent component using react hooks - reactjs

I have a Parent component and couple of child components. I need to disable or enable the button in the parent based on the ErrorComponent. If there is an error then I disable the button or else I enable it. I believe we can pass callbacks from the child to parent and let the parent know and update the button property. I need to know how to do the same using react hooks? I tried few examples but in vain. There is no event on error component. If there is an error (props.errorMessage) then I need to pass some data to parent so that I can disable the button. Any help is highly appreciated
export const Parent: React.FC<Props> = (props) => {
....
const createContent = (): JSX.Element => {
return (
{<ErrorPanel message={props.errorMessage}/>}
<AnotherComponent/>
);
}
return (
<Button onClick={onSubmit} disabled={}>My Button</Button>
{createContent()}
);
};
export const ErrorPanel: React.FC<Props> = (props) => {
if (props.message) {
return (
<div>{props.message}</div>
);
}
return null;
};

I'd use useEffect hook in this case, to set the disabled state depending on the message props. You can see the whole working app here: codesandbox
ErrorPanel component will look like this:
import React, { useEffect } from "react";
interface IPropTypes {
setDisabled(disabled:boolean): void;
message?: string;
}
const ErrorPanel = ({ setDisabled, message }: IPropTypes) => {
useEffect(() => {
if (message) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [message, setDisabled]);
if (message) {
return <div>Error: {message}</div>;
}
return null;
};
export default ErrorPanel;
So depending on the message prop, whenever it 'exists', I set the disabled prop to true by manipulating the setDisabled function passed by the prop.
And to make this work, Parent component looks like this:
import React, { MouseEvent, useState } from "react";
import ErrorPanel from "./ErrorPanel";
interface IPropTypes {
errorMessage?: string;
}
const Parent = ({ errorMessage }: IPropTypes) => {
const [disabled, setDisabled] = useState(false);
const createContent = () => {
return <ErrorPanel setDisabled={setDisabled} message={errorMessage} />;
};
const handleSubmit = (e: MouseEvent) => {
e.preventDefault();
alert("Submit");
};
return (
<>
<button onClick={handleSubmit} disabled={disabled}>
My Button
</button>
<br />
<br />
{createContent()}
</>
);
};
export default Parent;

Related

React Context Not Updating when clicking open modal button

I am trying to open a modal by updating the state of the component. The component is wrapped in a Context Provider.
Although the button seems to be clicking successfully, the Modal will not open
Here is the code with the container which contains the "Open Modal Button"
import { type FC, useRef } from 'react'
import infomation from '#/assets/icons/infomation.svg'
import { useModal } from '#/providers/ModalProvider'
import styles from './Instructions.module.scss'
const Instructions: FC = () => {
const card = useRef<HTMLDivElement>(null)
const { openDemoModal } = useModal()
const onOpenModalClick = () => {
openDemoModal()
console.log(openDemoModal)
}
return (
<section ref={card} className={`card ${styles.card}`}>
<div className={styles.background} />
<div>OPEN THE MODAL DOWN BELOW</div>
<button variant="outlined" fullWidth onClick={onOpenModalClick}>
Open Modal
</button>
</section>
)
}
export default Instructions
Here is the file which contains the Context for the Modal, I have tried setting up the context in INITIAL_STATE and tried updating it using the "onOpenModalClick" function - but it doesn't seem to be able to update the ShowModal.current value below.
import { type FC, type PropsWithChildren, createContext, useContext, useRef } from 'react'
import Modal from '#/components/modal/Modal'
type ContextValue = {
showModal: boolean
openDemoModal: () => void
}
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => {},
}
export const ModalContext = createContext(INITIAL_STATE)
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const showModal = useRef(INITIAL_STATE.showModal)
const openDemoModal = () => {
showModal.current = true
}
console.log(showModal.current)
return (
<ModalContext.Provider value={{ showModal: showModal.current, openDemoModal }}>
{children}
<Modal show={showModal.current} setShow={(shouldShow: boolean) => (showModal.current = shouldShow)} />
</ModalContext.Provider>
)
}
export function useModal() {
const context = useContext(ModalContext)
if (!context) {
throw new Error('useModal must be used within a ModalProvider')
}
return context
}
Is there any way to update the onOpenModalClick button to make it change the value of showModal.current in the Provider file?
Sorry if the post is unclear, this is my first Stack Overflow post. Let me know if I need to post anymore of the components.
I tried to add a button to the component which updates the Context, however the state failed to update
The useRef does not trigger a re-render when the value changes. Instead you can use a useState hook. Which would look something like this.
type ContextValue = {
showModal: boolean;
openDemoModal: () => void;
};
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => console.warn('No ModalProvider'),
};
export const ModalContext = createContext(INITIAL_STATE);
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const [showModal, setShowModal] = useState(false);
const openDemoModal = () => {
setShowModal(true)
};
console.log(showModal);
return (
<ModalContext.Provider
value={{ showModal, openDemoModal }}
>
{children}
<Modal
show={showModal}
setShow={setShowModal}
/>
</ModalContext.Provider>
);
};

React Query On Success does not trigger

I'm currently having a problem that I can't explain. I work with React, typescript and react query.
The code on which I work is a double modal and when clicking on the refuse button of the second one launches a call via react query then we execute an onSuccess.
If I move the hook call into the first modal the onSuccess fires.
If I move the OnSuccess from the second modal into the hook it works too.
But I don't understand why in this case it doesn't work...
Does anyone have an idea/explanation please?
Thanks in advance.
Here is the code below of the first modal
import React from 'react'
import Button from '../Button'
import SecondModal from '../SecondModal'
interface FirstModalProps {
isOpen: boolean
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
id?: string
}
const FirstModal = ({ id, isOpen, setIsOpen }:
FirstModalProps) => {
const [openRefuse, setOpenRefuse] = React.useState<boolean>(false)
return (
<>
<SecondModal isOpen={openRefuse} setIsOpen={setOpenRefuse} id={id} />
<Button
onClick={() => {
setIsOpen(false)
setOpenRefuse(true)
}}
text="refuse"
/>
</>
)}
export default FirstModal
Then the code of the second modal
import React from 'react'
import ConfirmModal from '../../../../../shared/styles/modal/confirm'
import useUpdate from '../../hooks/use-update'
interface SecondModalProps {
isOpen: boolean
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
id?: string
}
const SecondModal = ({ isOpen, setIsOpen, id }: SecondModalProps) => {
const { mutate: update } = useUpdate()
const updateT = () => {
update(
{
id
},
{
onSuccess: () => {
console.log('OnSuccess trigger')
}
}
)
}
return (
<ConfirmModal
close={() => {
setIsOpen(false)
}}
isOpen={isOpen}
validate={updateT}
/>
)}
export default SecondModal
Then the hook
import { useMutation } from 'react-query'
interface hookProps {
id?: string
}
const useUpdate = () => {
const query = async ({ id }: hookProps) => {
if (!id) return
return (await myApi.updateTr(id))()
}
return useMutation(query, {
onError: () => {
console.log('error')
}
})}
export default useUpdate
callbacks passed to the .mutate function only execute if the component is still mounted when the request completes. On the contrary, callbacks on useMutation are always executed. This is documented here, and I've also written about it in my blog here.

Why is my react.memo not working? How to properly memoize a child's child?

According to my understanding, when CIsTrue state is updated, the ToolbarButton with "SecondButton" should not re-render. However, when I checked console logs, it does re-render. What am I missing? Does it have something to do with the Icon prop being a React components? I tried to add a check in the React.memo function to compare them but I'm not sure if that's enough.
function App() {
const [AIsTrue] = useState(true);
const [BIsTrue] = useState(true);
const [CIsTrue] = useState(true);
return (
<ToolbarComponent
AIsTrue={AIsTrue}
BIsTrue={BIsTrue}
CIsTrue={CIsTrue}
/>
);
}
export default App;
import { ReactComponent as MyIcon } from 'somepath.svg';
import { ReactComponent as MyOtherIcon } from 'somepath2.svg';
export interface ToolbarComponentProps {
AIsTrue: boolean;
BIsTrue: boolean;
CIsTrue: boolean;
}
export const ToolbarComponent: React.FunctionComponent<ToolbarComponentProps> = (props) => {
const { AIsTrue, BIsTrue, CIsTrue } = props;
return (
<Stack>
<ToolbarButton
text="FirstButton"
icon={<MyIcon />}
visible={AIsTrue || BIsTrue || CIsTrue }
/>
<ToolbarButton
text="SecondButton"
icon={<MyOtherIcon />}
visible={BIsTrue}
/>
</Stack>
);
};
export interface IToolbarButtonProps {
text?: String;
icon: React.ReactNode;
visible?: Boolean;
}
export const ToolbarButton: React.FunctionComponent<IToolbarButtonProps> = React.memo(
(props) => {
const { text, icon, visible } = props;
if (visible) {
buttonClass = blah
}
console.log('I rendered - ' + text);
return (
<Button className={buttonClass}>
<Stack horizontal>
{icon}
<Text>{text}</Text>
</Stack>
<Button>
);
},
(prevProps, nextProps) => {
if (prevProps.icon === nextProps.icon) {
return true; // props are equal
}
return false; // props are not equal -> update the component
},
);
I am not sure but maybe it is re-rendering because of this:
icon={<MyOtherIcon />}
Try commenting it for a while and check if it fixes the problem.
It could be whatever that JSX returns is a different reference on each render.
To fix it try passing as
icon={MyOtherIcon}
Then you can render it inside your component as
let Icon = props.icon;
<Icon/>

React how to clear input and fire onChange event

I'm creating react component library with own basic components and some logic on the top of that. I have wrapped classic input field with clearable button. I need implement _onClearInput, but idk how to fire onChange event with empty string value. Is there anyone who is able to help me?
import React, { ComponentPropsWithoutRef, useState } from 'react';
export interface MyInputProps extends ComponentPropsWithoutRef<'input'> {
}
const MyInput = React.forwardRef<HTMLInputElement, MyInputProps>(({
value,
onChange,
...rest
}, ref) => {
const [_value, _setValue] = useState(value || '');
const _onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
_setValue(event.target.value);
if(onChange) {
onChange(event);
}
}
const _onClearInput = () => {
_setValue('');
// I need trigger onChange event somehow with event.target.value = ''
}
return(
<div className={`input-${_value === '' ? 'is-not' : 'is'}-set`}>
<input ref={ref} value={_value} onChange={_onChange} {...rest}/>
<button onClick={_onClearInput}>ClearInput</button>
</div>
);
});
export default MyInput;

UIkit's modals in React: integration

I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html

Resources