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.
Related
I have a very basic react component like this
const Message = (props) => {
const [show, setShow] = useState(false);
return (
<p show={show}>My Message</p>
);
};
I want to use this component from another one, and I want to be able to show the first one by clicking on a button in the second one
const OtherComponent = (props) => {
return (
<>
<Message />
<Button onClick={setShow(true)}>Open Message</Button>
</>
);
};
of course this code does not work, is there a way to achieve this or is Redux my only option?
Move state to parent
const Message = ({ show }) => {
return (
<p show={show}>My Message</p>
);
};
const OtherComponent = (props) => {
const [show, setShow] = useState(false);
return (
<>
<Message show={show} />
<Button onClick={setShow(true)}>Open Message</Button>
</>
);
};
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>
);}
I want to get the state of child component when the button is clicked in the parent component.
child component handles its own states. but when an action is triggered in the parent component I want the data of a child component
the code snippet in the simplest form as this and I can't change the component architecture
const Child = (props) => {
const [name, setName] = useState("")
return (
<input value={name} onChange={(e) => { setName(e.target.value) }} />
)
}
const parent = (props) => {
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child />
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
As with most problems, there are multiple ways to solve this one. Each solution will be more appropriate and readable for a different use case.
One option would be to move the state into the parent component.
const Child = (props) => {
return (
<input value={props.name} onChange={(e) => { props.setName(e.target.value) }} />
)
}
const parent = (props) => {
const [name, setName] = useState("")
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child name={name} setName={setName} />
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
Another method would be to use useRef. More documentation on this use case
const Child = (props) => {
const [name, setName] = useState("")
props.nameRef.current = name
return (
<input value={name} onChange={(e) => { setName(e.target.value) }} />
)
}
const parent = (props) => {
const nameRef = useRef("");
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child nameRef={nameRef}/>
<button onClick={()=>{abc()}} >Abc</button>
</React.Fragment>
)
}
IMHO most of the time you should use the first one. However, if it makes the code more readable for the state to live in the child component, or it would take too much time to refactor, then the second example works as well.
const Child = (props) => {
return (
<input value={name} onChange={e => setName(e.target.value) } />
)
}
const parent = (props) => {
const [name, setName] = useState("")
const abc=()=>{
// i want the name value here
}
return (
<React.Fragment>
<Child setName={setName}/>
<button onClick={()=>{abc()}}>Abc</button>
</React.Fragment>
)
}
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} />;
};
Just started to learn about Reack hooks but I cannot figure out if it is possible to write a simple hook (or should I use some other approach, e.g. useEffect along with useState) in order to control visibility of multiple elements by clicking on different buttons on page.
Let's say I have a simple app with 2 buttons and 2 "modal" windows:
const App = () => {
const [firstModalOpen, toggleFirstModal] = useState(false);
const [secondModalOpen, toggleSecondModal] = useState(false);
return (
<div>
<button onClick={() => toggleFirstModal(true)}>Open First Modal</button>
<button onClick={() => toggleSecondModal(true)}>Open Second Modal</button>
<FirstModal
{...props}
show={firstModalOpen}
toggleModal={toggleFirstModal}
/>
<SecondModal
{...props}
show={secondModalOpen}
toggleModal={toggleSecondModal}
/>
</div>
)
}
const FirstModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={firstModalOpen}
onHide={() => props.toggleModal(false)}
>
First modal content...
</Modal>
)
}
const SecondModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={secondModalOpen}
onHide={() => props.toggleModal(false)}
>
Second modal content...
</Modal>
)
}
// state hook attempt
const useToggleModal = () => (init) => {
const [show, setToggleModal] = useState(init);
const toggleModal = () => setToggleModal(!show);
return { show, toggleModal };
};
Since those are react-bootstrap modal windows, they use show and onHide properties to determine/handle visibility and I have to pass rest prop to avoid some side-effects.
If I'd use my hook attempt in my app, I'd handle both modals on any button click so I came up with the idea to pass a string (to both, buttons and modals) which would tell which modal exactly to handle, but that approach for some reason looked a bit wrong.
Is there a "smarter" way in React to handle this internally instead of passing strings around?
If you have multiple modals and only one of them needs to open at once, then you must use a single state which stores which modal is opened, kind of like a string having the id of the modal. However if you want to open multiple modals, you would store the isOpen prop differently
For the first case you would write your code like
const App = () => {
const [openModal, toggleModal] = useState('');
return (
<div>
<button onClick={() => toggleModal('first')}>Open First Modal</button>
<button onClick={() => toggleModal('second')}>Open Second Modal</button>
<FirstModal
{...props}
show={openModal === 'first'}
toggleModal={toggleModal}
/>
<SecondModal
{...props}
show={secondModalOpen}
toggleModal={toggleModal}
/>
</div>
)
}
const FirstModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={firstModalOpen}
onHide={() => props.toggleModal('first')}
>
First modal content...
</Modal>
)
}
const SecondModal = (props) => {
const { toggleModal, ...rest } = props;
return (
<Modal
{ ...rest }
show={secondModalOpen}
onHide={() => props.toggleModal('second')}
>
Second modal content...
</Modal>
)
}
For the second case it would be as you have written in your example, the only optimisation you can do for the second case is to store an array of modal objects and render them dynamically or let each modal handle its own toggle states and use useImperativeHandle to provide methods which parent can call to child modals like
const App = () => {
const firstRef = useRef(null);
const secondRef = useRef(null);
return (
<div>
<button onClick={() => this.firstRef.current.toggleModal()}>Open First Modal</button>
<button onClick={() => this.secondRef.current.toggleModal()}>Open Second Modal</button>
<FirstModal
{...props}
ref={firstRef}
/>
<SecondModal
{...props}
ref={secondRef}
/>
</div>
)
}
const FirstModal = forwardRef((props, ref) => {
const { showModal, toggleModal } = useToggleModal(false, ref);
return (
<Modal
{ ...rest }
show={showModal}
onHide={toggleModal}
>
First modal content...
</Modal>
)
})
const SecondModal = forwardRef((props, ref) => {
const { showModal, toggleModal } = useToggleModal(false, ref);
return (
<Modal
{ ...props }
show={showModal}
onHide={toggleModal}
>
Second modal content...
</Modal>
)
})
// state hook attempt
const useToggleModal = (init, ref) => {
const [show, setToggleModal] = useState(init);
const toggleModal = () => setToggleModal(!show);
useImperativeHandle(ref, () => ({
toggleModal
}))
return { show, toggleModal };
};