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>
</>
);
}
Related
I have right here a component that should simply render a list of items. Also, the component includes an input that filters the list of items. If there is no items, or if the items are being loaded it should display a message.
import { useState } from "react";
export const List = ({ loading, options }) => {
const _options = options ?? [];
const [renderedOptions, setRenderedOptions] = useState(_options);
const [inputValue, setInputValue] = useState("");
function handleChange(event) {
setInputValue(event.target.value);
const filteredOptions = _options.filter((option) =>
option.toLowerCase().includes(event.target.value.toLowerCase())
);
setRenderedOptions(filteredOptions);
}
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} />
<ul>
{renderedOptions.length > 0 ? (
renderedOptions.map((option) => <li key={option}>{option}</li>)
) : loading ? (
<li>Loading...</li>
) : (
<li>Nothing to show</li>
)}
</ul>
</div>
);
};
In App.js, I did a setTimeout, to mock a fetch call. However, there is a problem. Although I'm setting the asyncOptions state to be the new list of items, in my <List /> component the options do not seem to display properly.
import { List } from "./List";
import { useState, useEffect } from "react";
const ITEMS = ["list_1", "list_2", "list_3", "list_4", "list_5"];
export default function App() {
const [asyncOptions, setAsyncOptions] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const timeoutId = setTimeout(() => {
setIsLoading(false);
setAsyncOptions(ITEMS);
}, 2000);
return () => clearTimeout(timeoutId);
}, []);
return <List options={asyncOptions} loading={isLoading} />;
}
What is this happening and what is/are the solution(s)?
Sandbox code here: https://codesandbox.io/s/async-list-j97u32
The first time when list component gets rendered, renderedOptions is initialized with []
const [renderedOptions, setRenderedOptions] = useState(options);
But when the state inside the App component changes, it triggers a re-render and henceforth it triggers re-render of List Component. So since you are passing options as argument to useState u might feel it'll update the state automatically but that's not the case
Note -> useState doesn't take into consideration whatever you are passing as argument except for the first time the component loads
.So the useState will return back the initial State which is [] every time the component re-renders
So if you want to see changes you have to add useEffect inside the List component and trigger a state update every time options changes
Change your code too this,
import { useState } from "react";
export const List = ({ options, loading }) => {
console.log("Listt", options);
const [renderedOptions, setRenderedOptions] = useState([...options]);
const [inputValue, setInputValue] = useState("");
console.log(renderedOptions);
function handleChange(event) {
setInputValue(event.target.value);
const filteredOptions = options.filter((option) =>
option.toLowerCase().includes(event.target.value.toLowerCase())
);
setRenderedOptions(filteredOptions);
}
useEffect(() => {
setRenderedOptions(options)
} , [options])
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} />
<ul>
{renderedOptions.length > 0 ? (
renderedOptions.map((option) => <li key={option}>{option}</li>)
) : loading ? (
<li>Loading...</li>
) : (
<li>Nothing to show</li>
)}
</ul>
</div>
);
};
Basically, in the beginning, the value of options in an empty array, and the value put in state is a copy of that so the component is not listening to changes on the prop.
For some reason, you have to use the useEffect hook to actively listen to changes in the prop. By using the hook, when the API call returns something, it will set the state.(BTW, if anyone knows what is going on tell us)
I would recommend moving the API call to the List component, it would better encapsulate the logic
import { useEffect, useState } from "react";
export const List = ({ loading, options }) => {
const [renderedOptions, setRenderedOptions] = useState(options);
const [inputValue, setInputValue] = useState("");
useEffect(() => {
setRenderedOptions(options);
}, [options]);
function handleChange(event) {
setInputValue(event.target.value);
const filteredOptions = options.filter((option) =>
option.toLowerCase().includes(event.target.value.toLowerCase())
);
setRenderedOptions(filteredOptions);
}
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} />
<ul>
{renderedOptions.length > 0 ? (
renderedOptions.map((option) => <li key={option}>{option}</li>)
) : loading ? (
<li>Loading...</li>
) : (
<li>Nothing to show</li>
)}
</ul>
</div>
);
};
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>
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)} />
EDIT: See the comment of O.o for the explanation of the answer and the variant in case you are using classes.
I've come across to something and I can't find the solution.
I have 4 components in my web app:
Parent
child_1
child_2
child_3
I have a button on the Parent, and different forms (with inputs, checkboxes and radiobuttons) at the children.
Each child has his own button that executes several functions, some calculations, and updates the corresponding states. (No states are passed through parent and child).
I need to replace the three buttons of the children with the parent button.
Is there a way that I can execute the functions at the three children from the parent button and retrieve the results? (the results are one state:value per child.)
function Child1(props) {
const [value, setValue] = useState("");
useEffect(() => {
calculate();
}, [props.flag]);
calculate() {
//blah blah
}
onChange(e) {
setValue(e.target.value);
props.onChange(e.target.value); // update the state in the parent component
}
return (
<input value={value} onChange={(e) => onChange(e)} />
);
}
function Parent(props) {
const [flag, setFlag] = useState(false);
const [child1Value, setChild1Value] = useState("");
return (
<div>
<Child1 flag={flag} onChange={(value) => setChild1Value(value)}/>
<button onClick={() => setFlag(!flag)} />
</div>
);
}
I didn't test this but hope this helps you. And lemme know if there is an issue.
Try the following:
create refs using useRef for child form components.
for functional components, in order for the parent to access the child's methods, you need to use forwardRef
using the ref, call child component functions on click of parent submit button (using ref.current.methodName)
See the example code. I have tested it on my local, it is working ok.
Parent
import React, { Fragment, useState, useRef } from "react";
import ChildForm1 from "./ChildForm1";
const Parent = props => {
const [form1Data, setFormData] = useState({});//use your own data structure..
const child1Ref = useRef();
// const child2Ref = useRef(); // for 2nd Child Form...
const submitHandler = e => {
e.preventDefault();
// execute childForm1's function
child1Ref.current.someCalculations();
// execute childForm2's function
// finally do whatever you want with formData
console.log("form submitted");
};
const notifyCalcResult = (calcResult) => {
// update state based on calcResult
console.log('calcResult', calcResult);
};
const handleChildFormChange = data => {
setFormData(prev => ({ ...prev, ...data }));
};
return (
<Fragment>
<h1 className="large text-primary">Parent Child demo</h1>
<div>
<ChildForm1
notifyCalcResult={notifyCalcResult}
ref={child1Ref}
handleChange={handleChildFormChange} />
{/*{do the same for ChildForm2 and so on...}*/}
<button onClick={submitHandler}>Final Submit</button>
</div>
</Fragment>
);
};
export default Parent;
ChildFormComponent
import React, { useState, useEffect, forwardRef, useImperativeHandle } from "react";
const ChildForm1 = ({ handleChange, notifyCalcResult }, ref) => {
const [name, setName] = useState("");
const [calcResult, setCalcResult] = useState([]);
const someCalculations = () => {
let result = ["lot_of_data"];
// major calculations goes here..
// result = doMajorCalc();
setCalcResult(result);
};
useImperativeHandle(ref, () => ({ someCalculations }));
useEffect(() => {
// notifiy parent
notifyCalcResult(calcResult);
}, [calcResult]);
return (
<form className="form">
<div className="form-group">
<input
value={name}// //TODO: handle this...
onChange={() => handleChange(name)}//TODO: notify the value back to parent
type="text"
placeholder="Enter Name"
/>
</div>
</form>
);
};
export default forwardRef(ChildForm1);
Also as a best practice, consider to maintain state and functions in the parent component as much as possible and pass the required values/methods to the child as props.
I'm using React right now and I'm trying to get my localstorage to update a state once the event handles a return on search and then hold that state until the next search is completed. Right now I can't figure out where to put an event handler that triggers the correct state and holds the correct value.
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = React.useState(
localStorage.getItem(localStorageKey) || ''
);
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
export default function App() {
const [value, setValue] = useStateWithLocalStorage(
'myValueInLocalStorage'
);
const onChange = event => setValue(event.target.value);
const [state, setState] = useState({
message: 'test deploy',
results: [],
value: '',
});
...
and where I'm trying to implement the event handler
export default function SearchAppBar(props) {
const classes = useStyles();
const [searchTerm, setSearchTerm] = useState('');
const { onClick } = props;
...
<InputBase
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'search' }}
/>
<Button onClick={() => onClick(searchTerm)}> Search </Button>```
Hereby my solution. I've created an useLocalStorage function that stores and gets or sets items in the local storage and holds them in its own state:
import React from "react";
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = value => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
};
export default useLocalStorage;
For the searchBar component I've used a forwardRef to access the value of the input inside our higher component App. The newSearch function and searchTerm variable are destructured off the props. The placeholder holds the stored value in localStorage, which is searchTerm:
export const SearchAppBar = React.forwardRef(
({ newSearch, searchTerm }, ref) => {
return (
<>
<input ref={ref} type="text" placeholder={searchTerm} />
<button onClick={newSearch}> Search </button>
</>
);
}
);
Inside the main App component I'm using our useLocalStorage function hook to get and set the search. Inside newSearch I'm updating the search term by calling our hook with the value of the forwarded input ref.
export default function App() {
const ref = React.createRef();
const [searchTerm, setSearchTerm] = useLocalStorage(
"search",
"Not searched yet"
);
const newSearch = () => {
setSearchTerm(ref.current.value);
};
return (
<>
<SearchAppBar ref={ref} newSearch={newSearch} searchTerm={searchTerm} />
<p>Last search: {searchTerm}</p>
</>
);
}
Hope this is a workable solution for you.
Please find a code snippet here:
https://codesandbox.io/s/cranky-sunset-8fqtm?file=/src/index.js:387-773
I like the approach used by redux to handling the states on react. I use redux with redux-persist library to save the state instead of localStorage. If your project grows and you need to work with more complex states, it could help you.