I am trying to create a react component which internally uses ckeditor. But CKEditor component is not getting reloaded/re-rendered, can somebody please help.
const CkEditor = ({value, onChange, className = {}}) => {
return (
<div className={className}>
<CKEditor
initData={value} onChange={onChange}
/>
</div>
)}
initData always remains the same.
We were able to resolve the issue in the following way.
const CkEditor = ({
value,
className = {}}) => {
const [editor, setEditor] = useState(null);
const onBeforeLoad = (e) => {
setEditor(e.editor);
}
useEffect(() => {
if (editor) {
editor.setData(value);
}
}, [value]);
return (
<div className={className}>
<CKEditor
initData={value}
onLoaded={onBeforeLoad}
/>
</div>
);}
Related
I am new to react and I'm trying to pass a function as a prop to a child component.
In my case this is the parent component:
export default function Game() {
const [gameStarted, setGameStarted] = useState(false)
const [gameSettings, setGameSettings] = useState({})
useEffect(() => {
//setGameStarted(true);
}, [gameSettings]
)
return (
<>
{!gameStarted &&
<div className="game-form">
<GameSelection handleGameSelection={(settings)=> setGameSettings(settings)}/>
</div>}
</>
)}
My child component is:
export default function GameSelection({handleGameSelection}) {
const [labels, setLabels] = useState([])
const [gameMode, setGameMode] = useState('')
const [selectedLabels, setSelectedLabels] = useState([])
const [formError, setFormError] = useState(null)
// create label values for react-select
useEffect(() => {
if(document) {
setLabels(document.cards.map(card => {
return { value: {...card}, label: card.label}
}))
}
}, [document])
const handleSubmit = (e) => {
e.preventDefault()
try{
const gameSettings = {
mode: gameMode.value,
selected: selectedLabels.map((card) => ({...card.value})),
}
handleGameSelection(gameSettings)
}
catch(error){
console.log(error)
}
}
return (
<>
<h2 className="page-title">Please select your game</h2>
<form onSubmit={handleSubmit}>
<label>
<span>Mode:</span>
<Select
onChange={(option) => setGameMode(option)}
options={gameModes}
/>
</label>
<label>
<span>Select labels:</span>
<Select
onChange={(option) => setSelectedLabels(option)}
options={labels}
isMulti
/>
</label>
<button className="btn" >Start game</button>
{formError && <p className="error">{formError}</p>}
</form>
</>
)}
My form works but when I submit the form I keep getting the error TypeError: handleGameSelection is not a function. I tried everything. I have created a separate function in the parent component and gave that as a prop to the child. That also didn't work. I don't know what I am doing wrong. Any ideas?
Run this function inside useEffect, because currently, you are running this function before component is fully mounted and this function is propably undefined... or you can try to use if(typeof handleGameSelection === 'function') to check if its already initialized
[Solved] My input component is losing focus as soon as I press any key only when its value is controlled from outside the portal
NOTE: I am sorry. While writing this, I found the problem in my code, but I decided to post this anyway
[Reason] I was inlining the close function, so the useEffect hook got triggered every time close changed when the component was rendered again due to state changes and thus calling the activeElement.blur() on each keystroke.
Portal
const root = document.getElementById('root')
const modalRoot = document.getElementById('modal-root')
const Portal = ({ children, className, drawer = false }) => {
const element = React.useMemo(() => document.createElement('div'), [])
React.useEffect(() => {
element.className = clsx('modal', className)
modalRoot.appendChild(element)
return () => {
modalRoot.removeChild(element)
}
}, [element, className])
return ReactDOM.createPortal(children, element)
}
Modal
const Modal = (props) => {
const { children, show = false, close, className } = props
const backdrop = React.useRef(null)
const handleTransitionEnd = React.useCallback(() => setActive(show), [show])
const handleBackdropClick = React.useCallback(
({ target }) => target === backdrop.current && close(),
[]
)
const handleKeyUp = React.useCallback(
({ key }) => ['Escape'].includes(key) && close(),
[]
)
React.useEffect(() => {
if (backdrop.current) {
window.addEventListener('keyup', handleKeyUp)
}
if (show) {
root.setAttribute('inert', 'true')
document.body.style.overflow = 'hidden'
document.activeElement.blur?.() // ! CULPRIT
}
return () => {
root.removeAttribute('inert')
document.body.style.overflow = 'auto'
window.removeEventListener('keyup', handleKeyUp)
}
}, [show, close])
return (
<>
{show && (
<Portal className={className}>
<div
ref={backdrop}
onClick={handleBackdropClick}
onTransitionEnd={handleTransitionEnd}
className={clsx('backdrop', show && 'active')}>
<div className="content">{children}</div>
</div>
</Portal>
)}
</>
)
}
Custom Textfield
const TextField = React.forwardRef(
({ label, className, ...props }, ref) => {
return (
<div className={clsx('textfield', className)}>
{label && <label>{label}</label>}
<input ref={ref} {...props} />
</div>
)
}
)
I was inlining the close function, so the useEffect hook got triggered every time close changed when the component was rendered again due to state changes and thus calling the activeElement.blur() on each keystroke.
In Modal.jsx
...
React.useEffect(() => {
...
if (show) {
root.setAttribute('inert', 'true')
document.body.style.overflow = 'hidden'
document.activeElement.blur?.() // ! CULPRIT
}
...
}, [show, close]) // as dependency
...
<Modal
show={show}
close={() => setShow(false)} // this was inlined
className="some-modal"
>
...
</Modal>
TAKEAWAY
Do not inline functions
Usually there is no reason to pass a function (pointer) as dependency
I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
I'm trying to update the state of my component, but for some reason it keeps saying Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
I'm trying to have a modal that opens onclick of a <div>. Within that modal is a text input that will update the state (notes). For some reason it's saying invalid hook call - why is that?
const openTestModal = () => {
let [notes, setNotes] = useState("");
let [openModal, setOpenModal] = useState(true);
let modalBody =
<div>
<TextInput
value={notes}
onChange={(value) => setNotes(value)}
/>
</div>
return (
<Modal
open={openModal}
onCancel={() => setOpenModal(false)}
onConfirm={() => console.log('works')}
body={modalBody}
/>
)
};
const TestHooks = () => {
return (
<div onClick={() => openTestModal()}>
Test
</div>
)
};
Seems like you tried to render testModal in react as an event, which's not a way to go, at all. Instead you must render your testModal as component, like that, so click on Test div will open your modal:
const TestModal = () => {
const [notes, setNotes] = useState("");
const modalBody = (
<div>
<TextInput
value={notes}
onChange={(value) => setNotes(value)}
/>
</div>
)
return (
<Modal
open={openModal}
onCancel={() => setOpenModal(false)}
onConfirm={() => console.log('works')}
body={modalBody}
/>
)
};
const TestHooks = () => {
const [openModal, setOpenModal] = useState(false);
return (
<React.Fragment>
<TestModal openModal={openModal} setOpenModal={setOpenModal} />
<div onClick={() => setOpenModal(true)}>
Test
</div>
<React.Fragment>
)
};
Hope it helps :)
It's not working because your testHooks is calling to setState from a separate component. Add you testHooks code into your openTestModal component. It would work as is if TestHooks was a child of openTestModal as well.
I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...
const Filter = ({ setFilter }) => {
const [cycling, setCycling] = useState(true);
const [diy, setDiy] = useState(true);
useEffect(() => {
setFilter({
cycling: cycling,
diy: diy
});
}, [cycling, diy]);
return (
<Fragment>
<Row>
<Col>
<Button block onClick={() => setCycling(!cycling)}>cycling</Button>
</Col>
<Col>
<Button block onClick={() => setdIY(!DIY)}>DIY</Button>
</Col>
</Row>
</Fragment>
);
};
In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.
const ItemDashboard = () => {
const [filter, setFilter] = useState(null);
useEffect(() => {
console.log('on mount');
}, []);
useEffect(() => {
console.log('filter');
}, [filter]);
return (
<Container>..
<Filter setFilter={setFilter} />
</Container>
);
}
I'm guessing, you're looking for the way to lift state up to common parent.
In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.
The following live-demo demonstrates the concept:
const { render } = ReactDOM,
{ useState } = React
const hobbies = ['cycling', 'DIY', 'hiking']
const ChildList = ({list}) => (
<ul>
{list.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
const ChildFilter = ({onFilter, visibleLabels}) => (
<div>
{
hobbies.map((hobby,key) => (
<label {...{key}}>{hobby}
<input
type="checkbox"
value={hobby}
checked={visibleLabels.includes(hobby)}
onChange={({target:{value,checked}}) => onFilter(value, checked)}
/>
</label>))
}
</div>
)
const Parent = () => {
const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
onChangeVisibility = (hobby,visible) => {
!visible ?
setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
setVisibleHobbies([...visibleHobbies, hobby])
}
return (
<div>
<ChildList list={visibleHobbies} />
<ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:
const NOOP = () => {};
// Filter
const Child = ({ onChange = NOOP }) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
onChange(counter);
}, [counter, onChange]);
const onClick = () => setCounter(c => c + 1);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Increase</button>
</div>
);
};
// ItemDashboard
const Parent = () => {
const [value, setState] = useState(null);
useEffect(() => {
console.log(value);
}, [value]);
return <Child onChange={setState} />;
};