Detect If Function Runs on Another Component in React - reactjs

I need to detect if handleSelectProduct is being called in another component.
My problem is that if I want the child component(ProductDetailsComponent) to rerender, it still outputs the console.log('HELO'). I only want to output the console.log('HELO') IF handleSelectProduct is being click only.
const ProductComponent = () => {
const [triggered, setTriggered] = React.useState(0);
const handleSelectProduct = (event) => {
setTriggered(c => c + 1);
};
return (
<div>
Parent
<button type="button" onClick={handleSelectProduct}>
Trigger?
</button>
<ProductDetailsComponent triggered={triggered} />
</div>
);
};
const ProductDetailsComponent = ({ triggered }) => {
React.useEffect(() => {
if (triggered) {
console.log('HELO');
}
}, [triggered]);
return <div>Child</div>;
};
ReactDOM.render(
<ProductComponent />,
document.getElementById("root")
);

The simplest solution sounds to me by using an useRef to keep the old value, thus consider the console.log only when the triggered value changes.
const ProductDetailsComponent = ({ triggered }) => {
const oldTriggerRef = React.useRef(0);
React.useEffect(() => {
if (triggered !== oldTriggerRef.current) {
oldTriggerRef.current = triggered;
console.log('HELO');
}
}, [triggered]);
return <div>Child</div>;
};

Related

React Component is rerendering, even though it does not depend on the state

In my React code I have to use a legacy component, which makes a setup api call when it is first rendered. The component has a custom completion/cancelation event which I use to trigger a State update. The current Code looks like this:
export const useOneTimePassword = (
headline = "OTP anfordern",
id = "opt",
type = "sms",
businessProcess = "otp-process"
): UseOneTimePasswordReturn => {
const [otpCode, setOtpCode] = useState<undefined | string>();
const [isOtpCancelled, setIsOtpCancelled] = useState<boolean>(false);
const openOtp = () => {
const otp = document.querySelector(`otp-component#${id}`) as OtpElement;
otp.open();
};
const OtpComponent: FC = () => (
<Otp
headline={headline}
id={id}
type={type}
businessProcess={businessProcess}
setIsOtpCancelled={setIsOtpCancelled}
setOtpCode={setOtpCode}
/>
);
return {
otpCode,
isOtpCancelled,
openOtp,
OtpComponent,
removeOtp: () => {
setOtpCode(undefined);
},
};
};
and for the Component it looks like this:
const Otp: React.FC<OtpProps> = ({
headline,
businessProcess,
type,
id,
setOtpCode,
setIsOtpCancelled,
}) => {
function onOtpResponse(e: CompletedEvent) {
if (e.detail.otpCode) {
setOtpCode(e.detail.otpCode);
setIsOtpCancelled(false);
} else {
setIsOtpCancelled(true);
}
}
const ref = useRef();
useEffect(() => {
//#ts-ignore
if (ref.current) ref.current.addEventListener("completed", onOtpResponse);
}, []);
return (
<otp-component
ref={ref}
headline={headline}
id={id}
type={type}
business-process={businessProcess}
/>
);
};
export default Otp;
What I do not understand is that state changes in otpCode aswell as isOtpCancelled cause a rerender of the OtpComponent

Cant set state hook inside useImperativeHandle hook?

I want to call child function from parent and set state of a child's hook ,but I cant able to success it ,simple code is below ,setstate isnt working inside useImperativeHandle.Any help is appreciated ,thx..
const child = forwardRef((props,ref) => {
const [pagerTotalCount, setPagerTotalCount] = useState(0);
const [customerData, setCustomerData] = useState([]);
useImperativeHandle(ref, () => ({
childFunction1: updatePagerTotalCount;
}));
})
const updatePagerTotalCount = (param) => {
setPagerTotalCount(param); // this IS working now...
const pagerInputModel = {
"pageNumber": 1,
"pageSize": 3,
};
myservice.listCustomerList(pagerInputModel).then((res) => {
const { isSuccess, data: customers} = res;
if (isSuccess) {
console.log("api result:" + JSON.stringify(customers)); // this IS working,api IS working
setCustomerData(customers); // this IS NOT working , cant SET this.
console.log("hook result:" + JSON.stringify(customerData)); //EMPTY result.I tested this WITH another buttonclick even IN ORDER TO wait FOR async,but still NOT working
}
});
};
const parent= () => {
const childRef = React.useRef(null)
const handleClick = () => {
childRef.current.childFunction1(11); //sending integer param to child
};
RETURN(
<>
<Button variant="contained" endIcon={<FilterAltIcon />} onClick={handleClick}>
Filtrele
</Button>
<child ref={childRef}/>
</>
)
}
You should define a function to update the state and return that function via useImperativeHandle.
const updatePagerTotalCount = (param) => {
setPagerTotalCount(param);
};
useImperativeHandle(ref, () => ({
childFunction1: updatePagerTotalCount;
}));
Now with above when childRef.current.childFunction1(11); is invoked via parent component, you can see the state is being set correctly.

How to refactor this call to refresh state

I want to know how improve this calls in order to not repeat always the same sentence to refresh the state...
I don't need any huge refactor, only inputs like: you need to put this call inside a function and call it when you want... something like this...
export const CategoriesPage = () => {
const [categories, setCategories] = useState<Category[]>([]);
const [showModal, setShowModal] = useState(false);
const handleCreateCategory = (newCategory: CategoryCreate, file: File) => {
createCategoryHelper(newCategory, file)
.then(() => {
getCategoriesHelper().then(setCategories);
})
.finally(() => handleClose());
};
const handleDeleteCategory = (categoryId: Id) => {
SwalHelper.delete().then(() => {
deleteCategoryHelper(categoryId).then(() =>
getCategoriesHelper().then(setCategories)
);
});
};
const handleClose = () => {
setShowModal(false);
};
const handleModal = () => {
setShowModal(true);
};
useEffect(() => {
getCategoriesHelper().then(setCategories);
}, []);
return (
<>
<PageTitle title="Categories" />
<FilterBar>
<Button type="button" background="green" onClick={handleModal}>
+ Add new
</Button>
</FilterBar>
{showModal && (
<ModalPortal onClose={handleClose}>
<CreateCategoryForm
createCategory={(category, file: File) => {
handleCreateCategory(category, file);
}}
/>
</ModalPortal>
)}
<ListGrid columns={3}>
{categories.map((category) => {
const { id: categoryId } = category;
return (
<CategoryCard
key={categoryId}
{...category}
onClick={() => handleDeleteCategory(categoryId)}
/>
);
})}
</ListGrid>
</>
);
};
When component is mounting, on useEffect, fills the state with response in order to create a list.
When a category is created, I call to setState again to refresh the list.
Same on delete, on then, refresh again to update the list.
Three times calling the same sentence
getCategoriesHelper().then(setCategories)
This is getCategoriesHelper:
export const getCategoriesHelper = async () => {
const service = new CategoryServiceImplementation(apiConfig);
const uploadImageService = new AmplifyS3Service();
const repository = new CategoryRepositoryImplementation(
service,
uploadImageService
);
const useCase = new GetCategoriesUseCaseImplementation(repository);
return await useCase.getCategories();
};
Is there any way to make this code much cleaner and reusable?
Thanks in advance!
Everything is write, and all calls are made as they are designed to do

Run event handler functions synchronously after a React state change

I know useEffect allows you to run a function after state is updated.
However, I want to run different logic after a state change based on which different event handler causes a state change.
Context
I have a Parent component that shows or hides a child DialogModal component based on the [isDialogShown, setIsDialogShown] = useState(false) in Parent.
When isDialogShown
The Parent passes setIsDialogShown and 2 event handler callbacks to DialogModal: onDismiss (which adds focus to some element) and onConfirm (which adds focus to another element).
When onDismiss or onConfirm on the DialogModal is pressed, setIsDialogShown(false) should run first to hide the DialogModal, then run the respective callbacks to focus on differing elements of the page.
const Parent = () => {
const [isDialogShown, setIsDialogShown] = useState(false);
// These need to run after Dialog is closed.
// In other words, after isDialogShown state is updated to false.
const focusOnElementA = () => { .... };
const focusOnElementB = () => { .... };
const handleDismiss = () => {
setIsDialogShown(false);
focusOnElementA() // Needs to run after state has changed to close the modal
}
const handleConfirm = () => {
setIsDialogShown(false);
focusOnElementB() // Needs to run after state has changed to close the modal
}
return (
<>
<Button onClick={() => { setIsDialogShown(true) }>Open dialog</Button>
<DialogModal
isOpen={isDialogShown}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
What's the right pattern for dealing with this scenario?
I would use a separate state for the elements A and B to trigger them by in an additional effect. Enqueueing the toggle A/B state ensures the effect handles the update to call the focus A/B handles on the next render after the modal has closed.
const Parent = () => {
const [isDialogShown, setIsDialogShown] = useState(false);
const [toggleA, setToggleA] = useState(false);
const [toggleB, setToggleB] = useState(false);
useEffect(() => {
if (toggleA) {
focusOnElementA();
setToggleA(false);
}
if (toggleB) {
focusOnElementB();
setToggleB(false);
}
}, [toggleA, toggleB]);
const focusOnElementA = () => { .... };
const focusOnElementB = () => { .... };
const handleDismiss = () => {
setIsDialogShown(false);
setToggleA(true);
}
const handleConfirm = () => {
setIsDialogShown(false);
setToggleB();
}
return (
<>
<Button onClick={() => { setIsDialogShown(true) }>Open dialog</Button>
<DialogModal
isOpen={isDialogShown}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
A slight difference to Drew's answer but achieved using the same tools (useEffect).
// Constants for dialog state
const DIALOG_CLOSED = 0;
const DIALOG_OPEN = 1;
const DIALOG_CONFIRM = 2;
const DIALOG_CANCELLED = 3;
const Parent = () => {
// useState to keep track of dialog state
const [dialogState, setDialogState] = useState(DIALOG_CLOSED);
// Set dialog state to cancelled when dismissing.
const handleDismiss = () => {
setDialogState(DIALOG_CANCELLED);
}
// set dialog state to confirm when confirming.
const handleConfirm = () => {
setDialogState(DIALOG_CONFIRM);
}
// useEffect that triggers on dialog state change.
useEffect(() => {
// run code when confirm was selected and dialog is closed.
if (dialogState === DIALOG_CONFIRM) {
const focusOnElementB = () => { .... };
focusOnElementB()
}
// run code when cancel was selected and dialog is closed.
if (dialogState === DIALOG_CANCELLED) {
const focusOnElementA = () => { .... };
focusOnElementA()
}
}, [dialogState])
return (
<>
<Button onClick={() => { setDialogState(DIALOG_OPEN) }}>Open dialog</Button>
<DialogModal
isOpen={dialogState === DIALOG_OPEN}
onDismiss={handleDismiss}
onConfirm={handleConfirm}
/>
</>
)
}
You should add another state for which element was triggered and then trigger the effect when the states change:
const [action, setAction] = useState('');
// ...code
const handleDismiss = () => {
setAction('dismiss');
setIsDialogShown(false);
}
const handleConfirm = () => {
setAction('confirm');
setIsDialogShown(false);
}
// Add dependencies to useEffect and it will run only when the states change
useEffect(() => {
if(!isDialogShown) {
if(action === 'dismiss') {
focusOnElementA()
} else {
focusOnElementB()
}
}
}, [action, isDialogShown])

Using React hook form getValues() within useEffect return function, returns {}

I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}

Resources