I'm trying to create a Switch component in react that uses a custom hook. I want other components to use that same custom hook so that they are updated when the Switch is clicked. To do that I've created this custom hook:
function useToggle() {
const [isToggled, setIsToggled] = useState(false);
const toggle = React.useCallback(
() => setIsToggled(state => !state),
[setIsToggled],
);
return [isToggled, toggle];
}
Then in the Switch component I want to subscribe to this custom hook I do:
const Switch = () => {
const [isToggled, toggle] = useToggle();
return (
<Switch
onChange={toggle as any}
checked={isToggled as boolean}
...
/>
);
}
Then in the components that change value depending on whether the Switch is toggled, I have:
const PricingHeader = () => {
// Subscribe to switch's event
const [isToggled, toggle] = useToggle();
return (<Price showSpecialPrice={isToggled} />);
}
Problem? The components update independently. I can click the switch and I see it render differently as its value is toggled, but I don't see Price show a different price when switch is toggled. Price is not affected at all whenever I click the Switch.
Not sure what I'm doing wrong? I imagine the isToggled state returned is different every time useToggle is used. Is what I'm trying to do even possible?
Its possible, you can share your toggle state by Context API:
const SwitchContext = createContext();
function useToggle() {
return useContext(SwitchContext);
}
const Switch = () => {
const [isToggled, toggle] = useToggle();
return <button onClick={toggle}>{isToggled ? "ON" : "OFF"}</button>;
};
const Price = () => {
const [isToggled] = useToggle();
return <>The price {isToggled ? "IS TOGGLED" : "IS NOT TOGGLED"}</>;
};
export default function App() {
const [isToggled, toggle] = useReducer((p) => !p, false);
return (
<SwitchContext.Provider value={[isToggled, toggle]}>
<Switch />
<Price />
</SwitchContext.Provider>
);
}
I might recommend you use React Context for this. You can create a ToggleContext using createContext and use it with useContext -
const { createContext, useContext, useState } = React
const ToggleContext = createContext(false)
function useToggle (initialState) {
const [isToggle, setToggle] = useState(initialState)
const toggle = () => setToggle(value => !value)
return [isToggle, toggle]
}
function MyApp () {
const [isToggle, toggle] = useToggle(false)
return (
<ToggleContext.Provider value={isToggle}>
<p>Click any switch</p>
<Switch onClick={toggle} />
<Switch onClick={toggle} />
<Switch onClick={toggle} />
</ToggleContext.Provider>
)
}
function Switch ({ onClick }) {
const isToggle = useContext(ToggleContext)
return <input type="checkbox" onClick={onClick} checked={isToggle} />
}
ReactDOM.render(<MyApp />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
Related
It is necessary for me at a choice chekboksom that the Page2 component was drawn. I created a useState where I keep track of the value of the checkbox, but I don't know how to navigate programmatically when the checkbox is selected. How can I do this?
export default function Home() {
const [checked, setChecked] = useState(false);
const navigate = useNavigate();
const handleChange = () => {
setChecked(!checked);
};
return (
<>
<input type="checkbox" onChange={handleChange}></input>
</>
);
}
const handleChange = () => {
setChecked(!checked);
if (!checked) {
navigate('/')
}
};
You don't need any state for this, just use the onChange event object. React state updates are asynchronously processed, so trying to enqueue a state update in the handler and navigate based on the updated state won't work.
export default function Home() {
const navigate = useNavigate();
const handleChange = (e) => {
const { checked } = e.target;
if (checked) {
navigate("targetPath");
}
};
return (
<>
<input type="checkbox" onChange={handleChange}></input>
</>
);
}
I have a React HOC that hides a flyover/popup/dropdown, whenever I click outside the referenced component. It works perfectly fine when using local state. My HOC looks like this:
export default function withClickOutside(WrappedComponent) {
const Component = props => {
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const handleClickOutside = event => {
if (ref?.current && !ref.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => setOpen(false);
}, [ref]);
return <WrappedComponent open={open} setOpen={setOpen} ref={ref} {...props} />;
};
return Component;
}
When in use I just wrap up the required component with the HOC function
const TestComponent = () => {
const ref = useRef();
return <Wrapper ref={ref} />;
}
export default withClickOutside(TestComponent);
But I have some flyover containers that are managed from Redux when they are shown, or when they are hidden. When the flyover is shown, I want to have the same behavior, by clicking outside the referenced component to hide it right away. Here's a example of a flyover:
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
return (
<div>
<Wrapper>
<LeftFlyoverToggle
onClick={() => dispatch({ type: 'LEFT_FLYOVER_OPEN' })}
>
...
</Wrapper>
{leftFlyoverOpen && <LeftFlyover />}
{rightFlyoverOpen && <RightFlyover />}
</div>
);
Flyover component looks pretty straightforward:
const LefFlyover = () => {
return <div>...</div>;
};
export default LefFlyover;
Question: How can I modify the above HOC to handle Redux based flyovers/popup/dropdown?
Ideally I would like to handle both ways in one HOC, but it's fine if the examples will be only for Redux solution
You have a few options here. Personally, I don't like to use HOC's anymore. Especially in combination with functional components.
One possible solution would be to create a generic useOnClickOutside hook which accepts a callback. This enables you to dispatch an action by using the useDispatch hook inside the component.
export default function useOnClickOutside(callback) {
const [element, setElement] = useState(null);
useEffect(() => {
const handleClickOutside = event => {
if (element && !element.contains(event.target)) {
callback();
}
};
if (element) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [element, callback]);
return setElement;
}
function LeftFlyOver() {
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
const dispatch = useDispatch();
const setElement = useOnClickOutside(() => {
dispatch({ type: 'LEFT_FLYOVER_CLOSE' });
});
return (
<Dialog open={leftFlyoverOpen} ref={ref => setElement(ref)}>
...
</Dialog>
)
}
Say, I have this toggle:
<IonToggle id="IonToggleDarkMode" slot="end" checked={vars.darkMode} onChange={darkModeToggle}></IonToggle>
vars.darkMode is the saved value of the toggle, so the state is set when loading the page.
So, know I want to write a function that gets called onChange and I can't figure out how to pass or access the "checked" attribute here...
Let's do this for example:
function darkModeToggle() {
togglestate = // ???
console.log( togglestate )
}
How do I do that?
I also read something about onChange={e => darkModeToggle(e)} but to be honest, I don't get it ... e doesn't seem to transport the checked-attribute anywhere. I thought about running a selector for the toggles id and then reading its value but the API reference clearly states that 'value' is not to be used but 'checked' is.
Code for context:
import React from 'react';
//other import statements
const { useState } = React;
const DarkModeSwitch = () => {
// here you set the initial state using the useState hook:
const [isChecked, setIsChecked] = useState(false);
const darkModeToggle = () => {
console.log(isChecked);
setIsChecked(!isChecked);
}
}
//other logic, calculations, JS functions, etc
//UI content
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
return (
<IonContent>
<IonList>
<IonItem>
<IonLabel>DarkMode</IonLabel>
<IonToggle id="IonToggleDarkMode" slot="end" checked={isChecked} onChange={() => darkModeToggle())} />
</IonItem>
</IonList>
</IonContent>
)
}
Since you have a functional component you have to use the useState hook to handle the state of your darkMode. In your JSX you use the state to handle the IonToggle (or the checkbox) by setting the isChecked state to the checked prop.
Here is an example how you could do this with a simple checkbox:
const { useState } = React;
const DarkModeSwitch = () => {
// here you set the initial state using the
// useState hook:
const [isChecked, setIsChecked] = useState(false);
const darkModeToggle = () => {
// toggle the state 'isChecked'
// this makes it true if false and vice versa
setIsChecked(!isChecked);
}
return (
<div>
<input
type="checkbox"
checked={isChecked}
onChange={() => darkModeToggle()}
/>
</div>
)
}
Here is a working example:
Codepen
Edit:
Using your context-code it could look like this:
import React, { useState } from 'react';
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
// here you set the initial state using the useState hook:
const [isChecked, setIsChecked] = useState(false);
const darkModeToggle = () => {
setIsChecked(!isChecked);
}
return (
<IonContent>
<IonList>
<IonItem>
<IonLabel>DarkMode</IonLabel>
<IonToggle id="IonToggleDarkMode" slot="end" checked={isChecked} onChange={() => darkModeToggle())} />
</IonItem>
</IonList>
</IonContent>
)
}
code
const [checked, setChecked] = useState(false);
template
<IonToggle checked={checked}
onIonChange={(e) => setChecked(e.detail.checked)} />
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} />;
};
I got a switch button component that is exported to another component (audience manager) inside the audience manager component I am returning the switch component and a div component that says OFF. How do I change the text of the div to ON when the switch component toggles/changes its state.
type Props = {
children: any;
color?: string;
};
const Switch = (props:Props) => {
const [change, setChange] = useState(false)
let SwitchClass = ''
if(props.color === 'primary') SwitchClass = ' switch-primary'
if(props.color === 'success') SwitchClass = ' switch-success'
if(props.color === 'info') SwitchClass = ' switch-info'
return (
<div className={'switch-box ' + (change ? SwitchClass : '')} onClick={() => {
setChange(!change)
}} >
<div className={'switch-inner-box' + (change ? ' switch-inner-box-move': '')}> </div>
</div>
);
};
export default Switch
const AudienceManage = (props:Props) => {
const [change, setChange] = useState(false)
function doSomething () {
let x = document.getElementById('myDiv');
if (x.innerHTML === "OFF") {
x.innerHTML = "ON";
} else {
x.innerHTML = "OFF";
}
}
return (
<PageContainer>
<h1>
AudienceManage
</h1>
<div id='myDiv'>OFF</div>
<Switch onClick={doSomething} color='primary'/> <br/>
</PageContainer>
);
};
export default AudienceManage;
You can have a look at my sample, replicating your desire behavior. Basically you pass a function from parent component to the <Switch /> component.
Within this <Switch /> component, you call this passed prop to update in the parent scope.
import React, { useCallback, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Switch = ({ onChange }) => {
const [state, setState] = useState(false);
const onClickHandler = useCallback(() => {
setState(!state);
}, [state]);
useEffect(() => {
onChange(state);
}, [state]);
return <button onClick={onClickHandler}>Switch {state.toString()}</button>;
};
function App() {
const [value, setValue] = useState(false);
const onSwitchClicked = useCallback(switchState => {
console.log("switch: ", switchState);
setValue(switchState);
}, []);
return (
<div className="App">
<h1>Switch state: {value.toString()}</h1>
<Switch onChange={onSwitchClicked} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo link: https://codesandbox.io/s/epic-hugle-29nwb?fontsize=14&hidenavigation=1&theme=dark
Hope this help,
There are a lot of things gone wrong in your code. In this example AudienceManage should keep the state and change handler of your toggle component and pass both of them through props. The Switch component can be stateless and use the prop to display it's state and the handler prop to change the state in the parent component. This state change will trigger a re-render and your Switch component will reflect the correct state.
This link could be helpful:
Lifting State Up - React Docs