I want to open a modal from a parent component with props, and then close it when everything it's done and notify to the parent in case he wants to open again.
const ModalChild = (props) => {
const [Activate, setActivate] = useState(props.Activate);
const toggle = () => setActivate(!Activate);
useEffect(() => {
setActivate(props.Activate)
}
}, []);
<Modal isOpen={Activate} toggle={false} >
<text>hello {props.hey}<text>
</Modal>
}
and the parents component, something like this:
const Accountlist= () => {
const [Activate, setActivate] = useState(false);
const toggle = (value) => {
setActivate(true)
}
render(
<button onClick={() => toggle(value)} />
<ModalEdit props={Activate}/>
)}
Please, anyone have anyidea?
If it were me, I will do it like this.
const Accountlist= () => {
const [activate, setActivate] = useState(false);
const toggleActivate = (value) => {
setActivate(value);
}
return [
<button onClick={() => toggleActivate(!activate)} />,
<ModalEdit isActivated={activate} toggleActivate={toggleActivate} />
]}
And at the child component.
const ModalChild = ({ isActivated, toggleActivate }) => {
useEffect(() => {
toggleActivate(!isActivated);
}, []);
<Modal isOpen={isActivated} toggle={() => toggleActivate(false)} >
<text>hello {props.hey}<text>
</Modal>
}
I'm not sure what you are trying to do but the rest with the logic I'll leave it to you.
Related
Here's the scenario. I have a app and a search component.
const App = () => {
const [search, setSearch] = useState("initial query");
// ... other code
return (
<Search search={search} setSearch={setSearch} />
...other components
);
};
const Search = ({ search, setSearch }) => {
const [localSearch, setLocalSearch] = useState(search);
const debouncedSetSearch = useMemo(() => debounce(setSearch, 200), [setSearch]);
const handleTextChange = useCallback((e) => {
setLocalSearch(e.target.value);
debouncedSetSearch(e.target.value);
}, [setLocalSearch]);
return (
<input value={localSearch} onChange={handleTextChange} />
);
}
It's all good until this point. But I want to know what's the best way to change the search text on an external event. So far, the best approach I've found is using events.
const App = () => {
const [search, setSearch] = useState("initial query");
// ... other code
useEffect(() => {
onSomeExternalEvent((newSearch) => {
setSearch(newSearch);
EventBus.emit("updateSearch", newSearch);
});
}, []);
return (
<Search search={search} setSearch={setSearch} />
...other components
);
};
const Search = ({ search, setSearch }) => {
const [localSearch, setLocalSearch] = useState(search);
const debouncedSetSearch = useMemo(() => debounce(setSearch, 200), [setSearch]);
const handleTextChange = useCallback((e) => {
setLocalSearch(e.target.value);
debouncedSetSearch(e.target.value);
}, [setLocalSearch]);
useEffect(() => {
EventBus.subscribe("updateSearch", (newSearch) => {
setLocalSearch(newSearch);
});
}, []);
return (
<input value={localSearch} onChange={handleTextChange} />
);
}
Is there a better (correct) way of doing this?
Probably I'm missing something really simple, but:
I have an array:
const [weight, setWeight] = useState([]);
And I want to map over it to dynamically render a component:
const renderWeight = () => {
weight.map((e) => {
<Weight />;
});
};
I want this to happen when I click on a submit button, and I want the components to render in a specific div.
I can't find a way to do this.
Thank you!
You can use a show variable as toggler to show/hide the weight component like this
import React, { useState } from "react";
const App = () => {
const [weight, setWeight] = useState([1, 2, 3]);
const [show, setShow] = useState(false);
return (
<div className="container">
<div className="weight">{show && weight.map((e) => <Weight />)}</div>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
</div>
);
};
export default App;
I thing you need to return the mapped array
const renderWeight = () => {
return weight.map((e) => {
<Weight />;
});
};
note: Make sure to assign a key to each component rendered
You have to return it.
const [show ,setShow] = useState(false)
const renderWeight = () => {
return weight.map((e) => {
return <Weight />;
});
};
return (
<>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
{
show?
renderWeight():''
}
</>
);
const Parent = ({list}) => {
const closeAll = () => {
// What should be in here?
}
return (
<>
<button onClick={() => closeAll()}>Close All</button>
{list.map(item => <Accordion item={item}/>)}
</>
)
}
const Accordion = ({item}) => {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(o => !o)}>
<p>item.name</p>
{open && <p>item.detail</p>
</div>
)
}
Basically, as above, there is the Accordion components and a parent component that hosts all of them. Each Accordion component has a state called open. I want to change state of each child from parent component. How can I send an order to a child component to change its state?
Lift your state up into Parent.
closeAll can just map over the list and set all the open properties to false.
Have a handleClick callback that you pass down to Accordion which sets the state of the clicked item's open property to the inverse in Parent
Take a look at the react docs for lifting state up.
import { useState } from "react";
const data = [
{
detail: "foo",
name: "bar"
},
{
detail: "foo1",
name: "bar1"
}
];
const Parent = ({ defaultList = data }) => {
const [list, setList] = useState(
defaultList.map((i) => ({
...i,
open: false
}))
);
const closeAll = () => {
setList(
list.map((i) => ({
...i,
open: false
}))
);
};
const handleClick = (i) => {
const newList = [...list];
newList[i].open = !list[i].open;
setList(newList);
};
return (
<>
<button onClick={() => closeAll()}>Close All</button>
{list.map((item, i) => (
<Accordion item={item} handleClick={() => handleClick(i)} />
))}
</>
);
};
const Accordion = ({ item, handleClick }) => {
return (
<div>
<button onClick={handleClick}>{item.name}</button>
{item.open && <p>{item.detail}</p>}
</div>
);
};
export default Parent;
If you are unable to lift your state there is an alternative approach using react refs.
Create ref (initially an empty array) that each Accordion will push its own close state setting function into when it first renders.
In Parent, loop over the the array of close state settings functions inside the ref and execute each.
const Parent = ({ list = data }) => {
const myRef = useRef([]);
const closeAll = () => {
myRef.current.forEach((c) => c());
};
return (
<>
<button onClick={() => closeAll()}>Close All</button>
{list.map((item, i) => (
<Accordion item={item} myRef={myRef} />
))}
</>
);
};
const Accordion = ({ item, myRef }) => {
const [open, setOpen] = useState(false);
useEffect(() => {
myRef.current.push(() => setOpen(false));
}, [myRef]);
return (
<div>
<button onClick={() => setOpen((o) => !o)}>{item.name}</button>
{open && <p>{item.detail}</p>}
</div>
);
};
export default Parent;
Using an internal state for the component is not recommended, at least from my point of view for what you are doing.
you can control the open state of each list item from its properties like the example here:
const Parent = ({ list }) => {
const [isAllClosed, setIsAllClosed] = useState(false);
const closeAll = () => {
setIsAllClosed(true)
};
return (
<>
<button onClick={closeAll}>Close All</button>
{list.map((item) => (
item.open = isAllClosed != null ? (!isAllClosed) : true;
<Accordion item={item} />
))}
</>
);
};
const Accordion = ({ item }) => {
return (
<div onClick={() => console.log('item clicked')}>
<p>item.name</p>
{item.open ? <p>item.detail</p> : null}
</div>
);
};
I also replaced you short circuit evaluation open && <p>item.detail</p> to a ternary. The reason for that is that you will get a string false being printed if not true, does it make sense?
You will need somehow control the state of the whole list whether an item is open or not from whoever is using the parent.
But avoid using internal state when you can.
I think you can try creating a state variable inside the parent and passing it as a prop to the child to control the behavior of the child.
const Parent = ({ list }) => {
const [masterOpen, setMasterOpen] = useState(true);
<>
<button onClick={() => setMasterOpen(false)}>Close All</button>
{list.map((item) => (
<Accordion item={item} parentOpen={masterOpen} />
))}
</>
);
};
const Accordion = ({ item, parentOpen }) => {
const [open, setOpen] = useState(false);
if (!parentOpen) {
setOpen(false);
}
return (
<div onClick={() => setOpen((o) => !o)}>
<p>{item.name}</p>
{open && <p>item.detail</p>}
</div>
);
};
Normally this is how you use MUI Dialog. The code below is taken from the docs:
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
<Dialog open={open} onClose={handleClose}>
{...}
</Dialog>
</div>
);
}
But I want it to create the Dialog imperatively, sort of like fire and forget. I do not want to embed the Dialog component in other components whenever I need to create them. Ideally I'd want to call it like this
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
So my component definition'd look like this
const createDialog = () => {
// ???
}
const closeDialog = () => {
// ???
}
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => {
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
};
return (
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
);
}
You can reuse dialogs using React's Provider pattern. The official React document has explained in good detail so I won't cover it again here.
First create a custom Provider component in this case I'll call DialogProvider. This component will manage a list of Dialogs in local state.
const DialogContext = React.createContext();
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
</DialogContext.Provider>
);
}
As you can see, we have an array of dialogs here, it contains the dialog props that will be mapped to the actually <Dialog /> component when rendering.
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
{dialogs.map((dialog, i) => {
return <DialogContainer key={i} {...dialog} />;
})}
</DialogContext.Provider>
);
}
The <DialogContainer/> is the parent component of the <Dialog/>. Put anything that you want to be reusable in there. Here is a minimum example to get you started.
function DialogContainer(props: DialogContainerProps) {
const { children, open, onClose, onKill } = props;
return (
<Dialog open={open} onClose={onClose} onExited={onKill}>
{children}
</Dialog>
);
}
We can create and remove the dialog using setState as normal.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {
const dialog = { ...option, open: true };
setDialogs((dialogs) => [...dialogs, dialog]);
};
const closeDialog = () => {
setDialogs((dialogs) => {
const latestDialog = dialogs.pop();
if (!latestDialog) return dialogs;
if (latestDialog.onClose) latestDialog.onClose();
return [...dialogs].concat({ ...latestDialog, open: false });
});
};
But how do we call them in other components when we defined them here? Well, remember we're using Provider component here, which means we can pass the context data down so other components can reference, in this case we want to pass the createDialog and closeDialog down.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {/*...*/};
const closeDialog = () => {/*...*/};
const contextValue = React.useRef([createDialog, closeDialog]);
return (
<DialogContext.Provider value={contextValue.current}>
{children}
{dialogs.map((dialog, i) => ...)}
</DialogContext.Provider>
);
We're almost done here, now we need to add the DialogProvider to the component tree.
export default function App() {
return (
<DialogProvider>
<App {...} />
</DialogProvider>
);
}
But before we can use them, we should create a hook to easily access the context from the parent. So in your DialogProvider.jsx
export const useDialog = () => React.useContext(DialogContext);
Now we can use it like this.
import { useDialog } from "./DialogProvider";
export default function Content() {
const [openDialog, closeDialog] = useDialog();
const onOpenDialog = () => {
openDialog({
children: (
<>
<DialogTitle>This dialog is opened imperatively</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button color="primary" onClick={closeDialog}>Close</Button>
</DialogActions>
</>
)
});
};
return (
<Button variant="contained" onClick={onOpenDialog}>
Show dialog
</Button>
);
}
Live Demo
You can play around in the live demo here
I started using the <AutoSave/> component created by Jared Palmer:
const AutoSave = ({debounceMs}) => {
const formik = useFormikContext()
const debouncedSubmit = useCallback(
debounce(formik.submitForm, debounceMs),
[formik.submitForm, debounceMs]
)
useEffect(() => debouncedSubmit, [debouncedSubmit, formik.values])
return <>{!!formik.isSubmitting && 'saving...'}</>
}
The main problem is when I enter the page, <AutoSave/> submits the form once the page is mounted, how to prevent this behavior?
Example:
<Formik onSubmit={values => callMyApi(values)} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
Well, I didn't get a normal idea. Decided to use flag with hook usePrevious:
import {useRef} from 'react'
const usePrevious = value => {
const ref = useRef()
const prev = ref.current
ref.current = value
return prev
}
Now it looks like:
const MyForm = () => {
const [shouldUpdate, setShouldUpdate] = useState(false)
const previousShouldUpdate = usePrevious(shouldUpdate)
useEffect(() => {
setShouldUpdate(true)
return () => {setShouldUpdate(false)}
}, [])
<Formik onSubmit={values => {
if (previousShouldUpdate) {
return callMyApi(values)
}
}} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
}
Any ideas to make it better?