How to add a button to context in react - reactjs

I'm trying to store a button element globally using react context
const QuitLesson = ({goToLessons}) => {
// this is where I'm trying to create a new context
const closeBtnContext = React.createContext();
return (
<div>
<div styleName='close'>
<button
id='closeLessonBtn'
type='button'
styleName='closeIcon'
onClick={() => setQuitOverlayOpen(true)}
disabled={quitOverlayOpen}>
<img src='/resources/img/btn_close.svg'/>
</button>
</div>
{quitOverlayOpen &&
<Overlay
onClose={() => setQuitOverlayOpen(false)}
onConfirm={goToLessons}
/>
}
</div>
);
};
QuitLesson.propTypes = {
goToLessons: PropTypes.func.isRequired
};
export default CSSModules(QuitLesson, styles);
How do I add this button to context so I can use it in another component later?

You can't createContext inside a functional component.
// Declair this outside of the QuitLesson functional component.
const closeBtnContext = React.createContext();
const QuitLesson = ({goToLessons}) => {
return (
<div>
<div styleName='close'>
<button
id='closeLessonBtn'
type='button'
styleName='closeIcon'
onClick={() => setQuitOverlayOpen(true)}
disabled={quitOverlayOpen}>
<img src='/resources/img/btn_close.svg'/>
</button>
</div>
{quitOverlayOpen &&
<Overlay
onClose={() => setQuitOverlayOpen(false)}
onConfirm={goToLessons}
/>
}
</div>
);
};
QuitLesson.propTypes = {
goToLessons: PropTypes.func.isRequired
};
export default CSSModules(QuitLesson, styles);
I also think you maybe trying to use context in a way that it wasn't intended. the context is for passing data (state) and functions primarily to update that state.
from https://reactjs.org/docs/context.html
Context is designed to share data that can be considered “global” for
a tree of React components, such as the current authenticated user,
theme, or preferred language.
It sounds like you want to store the actual component in state to then reuse later. A better way of doing this that is 'React' friendly would be to create the closeBtnContext.context and wrap the app in the closeBtnContext.provider and then wrap the QuitLesson component with the closeBtnContext.consumer and then import the QuitLesson component wherever it is needed in your app.

Related

how to call child component callback event from parent component in react

Hi I am a beginner in React, I am using Fluent UI in my project .
I am planning to use Panel control from Fluent UI and make that as common component so that I can reuse it.I use bellow code
import * as React from 'react';
import { DefaultButton } from '#fluentui/react/lib/Button';
import { Panel } from '#fluentui/react/lib/Panel';
import { useBoolean } from '#fluentui/react-hooks';
export const PanelBasicExample: React.FunctionComponent = () => {
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
return (
<div>
<Panel
headerText="Sample panel"
isOpen={isOpen}
onDismiss={dismissPanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
};
https://developer.microsoft.com/en-us/fluentui#/controls/web/panel#best-practices
I remove <DefaultButton text="Open panel" onClick={openPanel} /> from the example .
So my question is how can I open or close this panel from any other component ?
I would use React useState hook for this.
Make a state in the component that you want render the Panel like
const [openPanel, setOpenPanel] = useState({
isOpen: false,
headerText: ''
})
Lets say for example you will open it from button
<Button onClick={() => setOpenPanel({
isOpen: true,
headerText: 'Panel-1'
})
}> Open me ! </Button>
Then pass the state as props to the Panel component
<PanelBasicExample openPanel={openPanel} setOpenPanel={setOpenPanel} />
in PanelBasicExample component you can extract the props and use it.
export const PanelBasicExample(props) => {
const {openPanel, setOpenPanel} = props
const handleClose = () => {setOpenPanel({isOpen: false})}
return (
<div>
<Panel
headerText={openPanel.headerText}
isOpen={openPanel.isOpen}
onDismiss={() => handleClose}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel="Close"
>
<p>Content goes here.</p>
</Panel>
</div>
);
}

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

React functional component able to change its state, is possible?

I want to create a React component for a Dialog.
I want to use Bootstrap Modal component.
I use TypeScript.
This is the usage I plan to use:
<Dialog icon="edit" title="Edit the item" buttons={...} onSave={...} >
... put here the content of the dialog
</Dialog>
Now, I started to write the code to make the Modal visible (pseudo-code):
interface Props {
// open by:
buttonText: OpenIcons,
title: string,
isOpen?: boolean
}
export const Dialog:FC<Props> = (props) => {
let {buttonText, title, isOpen, children} = props
return <>
{button && <button className="btn btn-sm">{buttonText}}</button> }
{ isOpen && <div>
{title}
<div>{children}</div>
<buttons>...</buttons>
</div>
}
</>
}
The problem is: how can I manage to change the isOpen property to true and made the Dialog component reflect this state?
Yes, I can use another component in the page (button text, icon, link...) that manage to change that state and pass it to the Dialog component, but I don't want to.
I want the Dialog component to self-manage its icon/button/link trigger.
The only solution I can think about is create another component inside the Dialog component, a sort of wrapper.
It has to manage the DialogButton (or whatever) and a new DialogInternal component that are not exported, so that the developer just see the exorted Dialog component.
I hope this idea is clear.
There is any better way (simpler, cleaner) to achieve my goal?
Here is your code using a useState hook
export const Dialog:FC<Props> = (props) => {
const [open, setOpen] = React.useState(false);
let {buttonText, title, children} = props
const handleClick = () => setOpen(!open);
return <>
{button && <button className="btn btn-sm" onClick={handleClick}>{buttonText}}</button> }
{ open && <div>
{title}
<div>{children}</div>
<button onClick={handleClick}>Close</button>
</div>
}
</>
}
You can use react hooks like useState and useEffect for maintaining a state in functional components
Ex:
for Usestate
const [isOpen, setIsOpen] = React.useState(initial value);
for useEffect
React.useEffect(()=>{},[dependencies]);

Using a Custom React based Modal, how can I pass a dynamic triggering function so I can re-use the component?

I have the following component which makes up my modal:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
const { toggle, isOpen, openModal } = useModalForm();
return (
<React.Fragment>
<ModalWrapper className={className} isOpen={isOpen} toggle={toggle}>
<AppModalHeader toggle={toggle}>{modalTitle}</AppModalHeader>
{isOpen ? (
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={() => {
if (onYesAction !== undefined) {
onYesAction(toggle);
}
}}
>
{yesText != null ? yesText : 'Yes'}
</Button>
</ModalBody>
) : null}
</ModalWrapper>
{children({
triggerModal: () => openModal({ id: undefined }),
toggle
})}
</React.Fragment>
);
};
export default QuestionModal;
I want to use it as such, where I can dynamically choose the name of the trigger that opens the modal:
In use e.g. (note: the inner question modal would be repeated, used 4 or 5 times in my application):
....
<QuestionModal
//....params that match up with above
>
{({ triggerModal }) => (
<QuestionModal
//....params that match up with the component
>
{({ triggerModal2 }) => (
<>
<Button onClick={()=>triggerModal();}>Trigger Modal 1</Button>
<div>
<Button onClick={()=>triggerModal2();}>Trigger Modal 2</Button>
</div>
</>
</>
)}
</QuestionModal>
....
How could I achieve this, by extending the question modal to pass a dynamic function? Just because I keep getting stuck in having to think about duplicating the original component, I want to make this component as reusable as I can. Any help would be greatly appreciated.
Thanks in advance
I think you're overcomplicating things. The problem is you're trying to control whether or not the modal is rendered from inside the modal itself. If you really want to have reusable components, it's good to decouple presentation from logic. In your case, you want to have a modal component with all the presentation/layout/styling stuff and pass in via props the actual content.
For example:
import React from 'react';
import { ModalBody, Button, Alert } from 'bootstrap';
import { AppModalHeader } from '../../common/AppModalHeader';
import ModalWrapper from './ModalWrapper';
const QuestionModal= ({
title,
noText = 'No',
yesText = 'Yes',
questionText,
onYesAction
children
}) => {
return (
<React.Fragment>
<ModalWrapper>
<AppModalHeader toggle={toggle}>{title}</AppModalHeader>
<ModalBody>
<p>{questionText}</p>
<Button
className="float-right"
color="primary"
onClick={onYesAction}
>
{yesText}
</Button>
</ModalBody>
</ModalWrapper>
</React.Fragment>
);
};
export default QuestionModal;
Now this is a purely presentational component, it creates a skeleton in which you put the actual content. And for using it, you'll control whether or not the modal is rendered from where it is actually used, like so:
import React, {useState} from 'react';
import QuestionModal from './QuestionModal'
const SomeComponent = (props) => {
const [showModal, setShowModal] = useState(false);
const toggleModal = () => {
setShowModal(!showModal);
}
const yesActionLogic = () => {
// Your yes-action logic...
}
return (
<div>
{showModal ? (
<QuestionModal
title="Sample title",
questionText="Question?"
onYesAction={yesActionLogic}
/>
) : null}
<Button onClick={toggleModal}>Toggle Modal</Button>
{/* The rest of your stuff... */}
</div>
);
}
If you want to create reusable components, it's good practice to not put any business logic on it. Use props to pass in functions that will be triggered from inside the components, and lift all the work to the components that actually hold your business logic.
One of the SOLID principles of software engineering is called Single-responsibility principle, and you can apply it to your React components:
Your Modal component is responsible for displaying data in its correct layout and triggering some set of functions from outside, regardless of what data/logic you pass.
This Modal component will be used by some other component whose responsibility is to show the user a modal with some specific data, at the right time.
So it makes sense that you should toggle your modal from outside.
On a personal note, I like to structure a React app in components that hold only presentational logic, and are used by containers, which are more logic-dense (generally having async requests).

How to toggle class of a div element by clicking on a button that is inside another functional component (another file)?

I want to toggle class of container (file 2) by an onClick on the button that is inside another component file.
The button has already an onClick function and I want to make it so it calls on two functions. Two toggle functions for the button and two class toggles for the container.
Hope it makes sense.
Here is my code so far:
Button component (File 1)
import React, {useState} from "react";
import "./Sort.scss";
const Sort = () => {
const [toggleSortIcon, toggleSortIconSet] = useState(false);
return (
<div className="tools">
<button
onClick={() => toggleSortIconSet(!toggleSortIcon)}
className={`sort ${toggleSortIcon ? "horizontal" : "vertical"}`}>
</button>
</div>
);
}
export default Sort;
Div container component that I want to change the class of (File 2)
import React, {useState} from "react";
import "./WordContainer.scss";
import UseAnimations from "react-useanimations";
const WordContainer = ({title, definition, example}) => {
const [toggleSize, toggleSizeState] = useState(false);
return (
<div className={`container ${toggleSize ? "big" : "small"}`}>
<div className="content">
<h2>{title}</h2>
<p>{definition}</p>
<h3>Example</h3>
<p>{example}</p>
</div>
<img onClick={() => toggleSizeState(!toggleSize)} src="./resize.svg" alt="resize" className="resize"/>
<div className="bookmark">
<UseAnimations
animationKey="bookmark"
size={26}
/>
</div>
</div>
);
}
export default WordContainer;
You could either have a global state management system (redux, or with custom hooks) that you can use to store the icon size.
Or you could simply provide a callback to your component that stores the icon size in a parent component that then feeds it back to your
Something like this:
const [size, setSize] = useState(/* default value */);
render() {
<>
<Sort onSizeToggle={setSize} />
<WordContainer size={size} />
</>
}

Resources