How can I catch the click on some buttom from a modal, to return true or false to the component that is calling the modal?
handleSubmitSaveConfigurations = async (row) => {
const { scadaValidation } = this.props;
const result = await scadaValidation(11);
if (result.statusCode !== 200) {
// Opens the modal to ask if you really want to save
this.setState({openSimpleModal: true});
this.setState({contentSimpleModal: this.warningModal()});
// Here I have to catch if the modal click yes or no.
// In case yes, do nothing and continue with the code
// But in case "no" returns false and stops
}
// If result.statusCode === 200 returns true
return true;
}
warningModal = () => (
<div>
Do you want to save?
<Button id="btnClose" onClick={() => this.handleModalClickClose()}>No</Button>
<Button id="btnSave" onClick={() => this.handleModalClickClose()}>Yes</Button>
</div>
);
handleModalClickClose = () => this.setState({ openSimpleModal: false });
You could pass a handler to be executed inside your modal.
const Modal = ({ callback }) =>{
const handleClick = arg => callback(arg)
return(
<div>
<button onClick={() => handleClick('button1')}>A</button>
<button onClick={() => handleClick('button2')}> B</button>
</div>
)
}
And expect to receive this value inside the component which is calling Modal
const TheOneWhoCalls = () =>{
const onModalClick = arg => console.log(arg)
return <Modal callback={onModalClick} />
}
You can create a function on the parent component, and inside the modal, u only use it.
https://reactjs.org/docs/lifting-state-up.html#lifting-state-up
Parent:
constructor () {
this.state: {test: false}
}
setStateTest (value) {
this.setState(value)
}
render () {
return <modal handlerSetParentStateTest = {setStateTest}></modal>
}
Modal:
// this will set the parent state
this.props.handlerSetParentStateTest(true);
I want to share my solution, for sure I will need it in the future. it the implementation of #Dupocas
const Modal = ({ callback }) => {
const handleClick = arg => callback(arg)
return (
<div>
Wanna save?
<Button id="btnCloseModal" onClick={() => handleClick(0)}>No</Button>
<Button id="btnGuardarConfirm" onClick={() => handleClick(1)}>Sí</Button>
</div>)
};
class TableDisplayReportRecord extends Component<Props, State> {
constructor {...}
handleValidate = async (row) => {
const { scadaValidation } = this.props;
const verify = await scadaValidation();
if (verify.statusCode !== 200) {
this.setState({openSimpleModal: true});
const onModalClick = arg => {
this.setState({openSimpleModal: false});
//do nothing
if (arg === 0) return false;
//if user selected "Yes", call the function that I need
else this.handleSubmitSave(row);
};
this.setState({contentSimpleModal:
<Modal
callback={onModalClick}
/>
})
}
}
handleSubmitSave = async (row) => {...}
...
}
Related
CodeSandbox
function disabledRow on click does not send changed data to child component.
I don't see any error in the code. Can't figure out how to display data in child component.
const UsersPage = () => {
const [dataState, setDataState] = useState<DataType[]>(dataInitial);
const [idState, setIdState] = useState<number[]>();
const disabledRow = () => {
if (idState) {
dataInitial.forEach((item) => {
if (idState.some((id) => id === item.id)) {
item.activeStatus = false;
item.date = <StopOutlined />;
item.status = "Not active";
}
});
return setDataState(dataState);
}
};
const idRow = (id: number[]) => {
return setIdState(id);
};
console.log("Hello", dataState);
return (
<div>
<div className={"wrapper"}>
<Button onClick={disabledRow}>
<StopOutlined /> Deactivate
</Button>
</div>
<UsersTable data={dataState} idRow={idRow} />
</div>
);
};
I have referred other questions asked here by OPs but none seems to work for me. I have one layout and in that layout there is one toolbar which I am using to submit forms. Now to make that happen I using one FormProvider which is a Context.Provider (wraps layout component) with a state variable which stores function callback to submit a form. Now in the form component (which is loaded conditionally) I am using this setState func from context to assign form submit callback and in the toolbar using state variable from context to call that as a function. The problem I am facing is I always get state variable from context undefined. These are the snippets from my code.
FormProvider
type FormContextProps = {
setOnSubmit: (callable: Function | undefined) => void
assignOnSubmit: Dispatch<any>
setOnCancel: (callable: Function | undefined) => void
submit: (e: any) => void
cancel: () => void
}
const initAuthContextPropsState = {
setOnSubmit: () => { },
assignOnSubmit: () => { },
setOnCancel: () => { },
submit: (e: any) => { },
cancel: () => { },
}
const FormContext = createContext<FormContextProps>(initAuthContextPropsState)
const useTForm = () => {
return useContext(FormContext)
}
const FormProvider: FC = ({ children }) => {
const [onSubmit, assignOnSubmit] = useState<Function | undefined>()
const [onCancel, assignOnCancel] = useState<Function | undefined>()
const setOnSubmit = (callable: Function | undefined) => {
console.log('setOnSubmit', callable)
assignOnSubmit(callable)
console.log('setOnSubmit after', onSubmit)
}
const setOnCancel = (callable: Function | undefined) => {
assignOnCancel(callable)
}
useEffect(() => {
console.log("changed onSubmit"); // this hook is called only on first render
}, [onSubmit])
const submit = (e: any) => {
console.log('form submited', onSubmit) // this is always undefined when I click on save button on toolbar
if (onSubmit) onSubmit(e)
}
const cancel = () => {
if (onCancel) onCancel()
}
return (
<FormContext.Provider value={{ setOnSubmit, assignOnSubmit, setOnCancel, submit, cancel }}>
{children}
</FormContext.Provider>
)
}
Toolbar
const FormToolbar: FC = () => {
const {classes} = useLayout()
const {submit, cancel} = useTForm()
const submitForm = (e: any) => submit(e)
return (
<div className='toolbar' id='kt_toolbar'>
{/* begin::Container */}
<div
id='kt_toolbar_container'
className={clsx(classes.toolbarContainer.join(' '), 'd-flex flex-stack')}
>
<DefaultTitle />
{/* begin::Actions */}
<div className='py-1 d-flex align-items-center'>
{/* begin::Button */}
<button
className='btn btn-sm btn-primary me-4'
onClick={submitForm}
>
Save
</button>
<button
className='btn btn-sm btn-primary'
onClick={cancel}
>
Cancel
</button>
{/* end::Button */}
</div>
{/* end::Actions */}
</div>
{/* end::Container */}
</div>
)
}
EditForm.tsx
const EditForm: React.FC<Props> = () = {
const form = useRef() as React.MutableRefObject<HTMLFormElement>
const { setOnSubmit, assignOnSubmit } = useTForm()
useLayoutEffect(() => {
setOnSubmit(() => { form?.current.dispatchEvent(new Event('submit')) });
console.log('Form changed', form)
}, [form])
return (
<form onSubmit={formik.handleSubmit} ref={form}>
// ...
</form>
);
}
Main Component
function StaffManagement({ user, selectedLanguageId, idMenu }: Props) {
const [editing, setEditing]: [any, Function] = useState(null)
return (
<div className='row'>
<div className="col-lg-4">
<ModuleItemList
className='card-xxl-stretch mb-xl-3'
edit={setEditing}
/>
</div>
<div className="col-lg-8">
{editing && <EditForm
userId={user.id}
menuId={idMenu}
/>}
</div>
</div>
)
}
When using setState, we can do it in two ways,
setState(newState); // directly pass the new state
setState((currentState) => newState); // return the new state from a callback fn
That mean setState can accept a plain state value or a callback function which will return the new state that need to be set as the new state.
When you say,
const setOnSubmit = (callable: Function | undefined) => {
// callable => () => { form?.current.dispatchEvent(new Event('submit')) }
assignOnSubmit(callable)
}
Here React thinks you used the setState(assignOnSubmit) in the 2nd way I mentioned above, so react will call your callback and execute the form?.current.dispatchEvent(new Event('submit')). Since your callable function returns nothing, undefined will assigned to your onSubmit state.
So if you really need to store this function in a state, you have to do it as,
const setOnSubmit = (callable: Function | undefined) => {
assignOnSubmit(() => callable) // React will call the callback and return callable
}
Few Other Tips
Also do not use useLayoutEffect for this task. You can use useEffect and imrpove the performance of your application.
memoize the provider data, otherwise you will trigger unwanted re renders.
const data = useMemo(() => ({
setOnSubmit, assignOnSubmit, setOnCancel, submit, cancel
}), [submit, cancel, setOnCancel, assignOnSubmit, setOnSubmit])
return (
<FormContext.Provider value={data}>
{children}
</FormContext.Provider>
)
State updates are asynchronous. So you can't expect to console.log log the latest state
const setOnSubmit = (callable: Function | undefined) => {
console.log('setOnSubmit', callable)
assignOnSubmit(() => callable)
console.log('setOnSubmit after', onSubmit) // this won't log the latest as assignOnSubmit is async
}
you need to make <FormProvider as a parent of your Toolbar and EditForm to make your context working properly. Based on your code I don't see where you put the <FormProvider, so I'm guessing that you need to put it on your Main Component
Main Component
function StaffManagement({ user, selectedLanguageId, idMenu }: Props) {
const [editing, setEditing]: [any, Function] = useState(null)
return (
<FormProvider>
<div className='row'>
<div className="col-lg-4">
<ModuleItemList
className='card-xxl-stretch mb-xl-3'
edit={setEditing}
/>
</div>
<div className="col-lg-8">
{editing && <EditForm
userId={user.id}
menuId={idMenu}
/>}
</div>
</div>
</FormProvider>
)
Just wondering the best way to pass the letterSelected into the useLazyQuery fetchMovies query, so that I don't have to use the static variable of "A". I was hoping there was a way to pass it directly into fetchMovies. useLazyQuery is an apollo query.
const BrowseMovies = () => {
const [fetchMovies, { data, loading}] = useLazyQuery(BROWSE_MOVIES_BY_LETTER, {
variables: {
firstLetter: "A"
}
})
return (
<div className="browserWrapper">
<h2>Browse Movies</h2>
<AlphabetSelect
pushLetterToParent={fetchMovies}
/>
{
data && !loading &&
data.browseMovies.map((movie: any) => {
return (
<div className="browseRow">
<a className="movieTitle">
{movie.name}
</a>
</div>
)
})
}
</div>
)
}
export default BrowseMovies
const AlphabetSelect = ({pushLetterToParent}: any) => {
const letters = ['A','B','C','D','E','F','G','H','I','J','K','L', 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
const [selectedLetter, setSelectedLetter] = useState("A")
const onLetterSelect = (letter: string) => {
setSelectedLetter(letter.toUpperCase())
pushLetterToParent(letter.toUpperCase())
}
return (
<div className="alphabetSelect">
{
letters.map((letter: string) => {
return(
<div
className={selectedLetter === letter ? 'letterSelectActive' : 'letterSelect'}
onClick={() => onLetterSelect(letter)}
>
{letter}
</div>
)
})
}
</div>
)
}
export default AlphabetSelect
This appears to be a problem solved by Lifting State Up. useLazyQuery takes a gql query and options and returns a function to execute the query at a later time. Sounds like you want the child component to update the variables config parameter.
BrowseMovies
Move firstLetter state BrowseMovies component
Update query parameters/options/config from state
Add useEffect to trigger fetch when state updates
Pass firstLetter state and setFirstLetter state updater to child component
const BrowseMovies = () => {
const [firstLetter, setFirstLetter] = useState('');
const [fetchMovies, { data, loading}] = useLazyQuery(
BROWSE_MOVIES_BY_LETTER,
{ variables: { firstLetter } } // <-- pass firstLetter state
);
useEffect(() => {
if (firstLetter) {
fetchMovies(); // <-- invoke fetch on state update
}
}, [firstLetter]);
return (
<div className="browserWrapper">
<h2>Browse Movies</h2>
<AlphabetSelect
pushLetterToParent={setFirstLetter} // <-- pass state updater
selectedLetter={firstLetter} // <-- pass state
/>
{
data && !loading &&
data.browseMovies.map((movie: any, index: number) => {
return (
<div key={index} className="browseRow">
<a className="movieTitle">
{movie.name}
</a>
</div>
)
})
}
</div>
)
}
AlphabetSelect
Attach pushLetterToParent callback to div's onClick handler
const AlphabetSelect = ({ pushLetterToParent, selectedLetter }: any) => {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#';
return (
<div className="alphabetSelect">
{
letters.split('').map((letter: string) => {
return(
<div
key={letter}
className={selectedLetter === letter ? 'letterSelectActive' : 'letterSelect'}
onClick={() => pushLetterToParent(letter.toUpperCase())}
>
{letter}
</div>
)
})
}
</div>
)
}
In my functional component I'm calling two functions, which are doing nearly the same thing:
const App = () => {
const handleOnClickFirst = () => {
setValue('first')
}
const handleOnClickSecond = () => {
setValue('second')
}
return (
<div>
{anything === true
? <Button onClick={handleOnClickFirst} />
: <Button onClick={handleOnClickSecond} />}
</div>
)
}
So there should be simply only
const handleOnClick = (value) => {
setValue(value)
}
But how do I pass the value in onClick?
As we know with JSX you pass a function as the event handler. So we can wrap our handler with another function and call handler with arguments in this wrapper function
onClick={(event) => handleOnClick(value)}
or for old versions we can do
onClick={function(event){ handleOnClick(value) }}
If you don't need events you can just pass it like this
onClick={() => handleOnClick(value)}
Also if it is a Class base component we use bind to pass method context and arguments
class App extends React.Component {
handleOnClick(val) {
console.log(`${val}`);
}
render() {
return (
<button onClick={this.handleOnClick.bind(this, "test")}>
click me
</button>
);
}
}
You could do
onClick={() => handleOnClick(value)}
edit:
More information here: https://reactjs.org/docs/faq-functions.html
You can do this :
<button onClick={(event)=>handleOnClick(<pass your paramter here>)}></button>
Try this:
const App = () => {
const handleOnClick = (passedInValue) => {
setValue(passedInValue)
}
return (
<div>
{anything === true
? <Button onClick={() => handleOnClick("first")} />
: <Button onClick={() => handleOnClick("second")} />}
</div>
)
}
I am trying to pass down the functions setDir and handleSort() down from SearchAppointments (parent) to Hooks(child), but I keep getting errors saying that they are not functions.
I tried to debug it by looking the typeof handleSort in the useEffecthook of the child component, though it console.logged two statements: 1) underfined 2) function. Not sure what is wrong.
const SearchAppointments = React.memo(() => {
const [orderDir, setDir] = useState("");
const handleSort = (e) => {
let value = e.target.value;
setDir(value);
let order;
let filterData = data;
if (orderDir === 'asc') {
order = 1;
} else {
order = -1;
}
};
return (
<>
<div>
<Hooks handleSort={handleSort} setDir={setDir} />
</div>
</>
);
});
const Hooks = React.memo(({ handleSort, setDir }) => {
useEffect(() => {
console.log(typeof handleSort);
}, []);
return (
<div>
<div>
<button type="button" onClick={() => setDir("success")}>
Set Dir
</button>
<button type="button" value='asc' onClick={handleSort}>
Handle Sort (Asc)
</button>
<button type="button" value='dsc' onClick={handleSort}>
Handle Sort (Dsc)
</button>
</div>
</div>
);
});
try and use memo like this:
const comparator = (previous, next) => {
if (something) {
return true
}
return false
}
const Hooks = React.memo(({ handleSort, setDir }) => {
useEffect(() => {
console.log(typeof handleSort);
}, []);
return (
<div>
<div>
<button type="button" onClick={() => setDir("success")}>
Set Dir
</button>
<button type="button" value='asc' onClick={handleSort}>
Handle Sort (Asc)
</button>
<button type="button" value='dsc' onClick={handleSort}>
Handle Sort (Dsc)
</button>
</div>
</div>
);
}, comparator);
define a function and pass that as the second argument. memo function (I've called it comparator, then returns true or false depending on when you want the component to update. think of it as the new shouldComponentUpdate in the lifecycle methods. but use with care as your application might not update if you use carelessly