I have 2 dropdowns, I need to update the values of the second dropdown based on the first. On page load, I need to initialize the dropdown values based on conditions, like so:
const getInitialState= () => {
//defaultSelected has a value if saved in DB, if not then I set the 0 option value in type, based on this value option value will be populated in the dropdown. I get all the values from parent component
let updatedType= defaultSelected !== ""
? defaultType
: Region.RegionType;
console.log('typeeeee', type)
return updatedType;
}
useEffect(() =>{
//Here in options dropdown values object is set based on type selected
let updatedOptions = !_.isEmpty (type)
? "a" //value from db if save
: "b"; //If not 0th value of the object
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
},[type]);
const [type, setType] = useState(getInitialState);
const [options, setOptions] = useState('');
const changeCrTypeHandler = (event) =>{
const { name, value } = event.target;
setType(type =>({
...type,
[name]: value}));
}
return (
<>
<select
onChange={changeCrTypeHandler}
style={{ width: "150px" }}
value={!_.isEmpty(type)}
name= "type"
>
{_.map(customMap, (crId) => { //I get this cusomMap from Parent component
return (
<option
id={crId.customType}
value={crId.customType}
key={crId.customType}
>
{crId.customType}
</option>
);
})}
</select>{" "}
<select
onChange={changeCrHashHandler} //second dropdown onchange
style={{ width: "250px" }}
value={crHashId}
name= "options"
>
{_.map(!_.isEmpty(options), (o) => { //This options are created based on type, which is currently not getting set
return (
<option
id={o.customId}
value={o.customId}
key={o.customId}
>
{o.name}
</option>
);
})}
</select>
Based on type value then I need to set the value of the option.
But my useEffect hook doesn't get called upon changes in type. How can I call the useEffect method whenever there is any changes in the type?
Any help for this would be much appreciated.
You are calling setState before it is defined. For the initial value, you just return it without calling setState. For having the second dropdown state depending on the first one's state, you could use the useEffect hook, like so:
const [firstDropdown, setFirstDropdown] = useState(getInitialState({type:"", options:""}));
const [secondDropdown, setScondDropdown] = useState(0); // you could use whatever initial value you want here
const getInitialState= () => {
//some logic to get the value in type
//just return that inital value without calling setState
}
// the function inside useEffect runs every time firstDropdown changes
useEffect(()=>{
// some logic on firstDropdown
let value = firstDropdown+25
setScondDropdown(value)
},[firstDropdown]);
Update:
In your useEffect, change this :
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
to this :
setOptions(updatedOptions)
Use the useState as below:
const [state, setState] = useState({})
setState((prev) => {
// do whatever you want to do with the previous state
})
And for the name, please do not use setState as your setter function of useState. because setState is another method in the class based components in React
Related
I'm loading data with a custom hook from the session storage into an input, and if I delete the whole field, the onChange() function doesn't trigger. If I only add or delete one character, it works fine, but if I select all (or if the input had only one character), then delete it doesn't seem to do anything.
This only applies, when I delete the content after render, without doing anything else in the input beforehand.
//this works fine
const [test, setTest] = useState('test')
<input value={test} onChange={(e) => setTest(e.target.value)} />
//this doesn't trigger, when deleting all content after rendering the default value
const [test2, setTest2] = useSessionStorage({key: 'test2', defaultValue: 'test2'})
<input value={test2} onChange={(e) => setTest2(e.target.value)} />
Here is my custom hook:
export const useSessionStorage = (hookProps) => {
const { key, defaultValue } = hookProps
const [sessionItem, setSessionItem] = useState(() => {
if (typeof window != 'undefined') {
const item = sessionStorage.getItem(key)
return item ? JSON.parse(item) : defaultValue
}
})
useEffect(() => {
sessionStorage.setItem(key, JSON.stringify(sessionItem))
}, [key, sessionItem])
return [sessionItem, setSessionItem]
}
I'm sure it has to do something with the session storage loading in the server first, or just after the first render, but I have no solution.
Your code is working fine. In fact, I did not detect the render when test value changes. I added this useEffect inside the component,
useEffect(() => {
console.log("I am rerendering because test value is changing");
}, [test]);
inside useSessionStorage useEffect, add this
useEffect(() => {
console.log("I am rerendering becasue test2 value is changing");
sessionStorage.setItem(key, JSON.stringify(sessionItem));
}, [key, sessionItem]);
Now test it
SOLVED
The session storage loads from the server side, so it gave undefined (empty string) at first, and the actual value after. The input got the empty string, and when trying to change it to '', the onChange() didn't trigger, due to no changes.
Writing the input in a component and disabling SSR at import works.
import dynamic from 'next/dynamic'
const DynamicInput = dynamic(() => import('../components/Input'), {
ssr: false
})
export default function Page(){
const [test, setTest] = useSessionStorage({ key: 'test', defaultValue: null })
return (
<DynamicInput value={test} onChange={(e) => setTest(e.target.value)} />
)
}
I've spent a few days on this and it is driving me crazy now.
I have a state in a parent component containing an Array[string] of selected squares which is passed to the child component (a map) along with the set function from the hook. The issue is that when I set the new squares they are changed in the parent, but on selection of another square it is not taking into account the already selected squares.
function Parent(props){
const [selectedSquares, setSquares] = useState([]);
useEffect(() => {
console.log('parent useEffect', selectedSquares);
}, [selectedSquares]);
return (
<Child selectedSquares={selectedSquares}
handleSquaresChange={setSquares}
/>
)
}
function Child(props){
const {selectedSquares, handleSquaresChange} = props;
useEffect(() => {
console.log('child useEffect', selectedSquares)
}, [selectedSquares]);
const handleSelect = evt => {
if(evt.target){
const features = evt.target.getFeatures().getArray();
let selectedFeature = features.length ? features[0] : null;
if (selectedFeature) {
console.log('select (preadd):', selectedSquares);
const newTile = selectedFeature.get('TILE_NAME');
const newSquares = [...selectedSquares];
newSquares.push(newTile);
const newTest = 'newTest';
handleSquaresChange(newSquares);
console.log('select (postadd):', newSquares);
}
}
return(
<Map>
<Select onSelect={handleSelect}/>
</Map>
)
}
On the first interactionSelect component I get this output from the console:
parent useEffect: [],
child useEffect: [],
select (preadd):[],
child useEffect:['NX'],
parent useEffect: ['NX'],
select (postadd): ['NX'].
Making the second selection this is added to the console:
select (preadd):[],
select (postadd): ['SZ'],
child useEffect:['SZ'],
parent useEffect: ['SZ'].
Turns out there is an addEventListener in the library I am using that is going wrong. Thanks to everyone who responded but turns out the issue was not with React or the state stuff.
Consider something like the code below. Your parent has an array with all your options. For each option, you render a child component. The child component handles the activity of its own state.
function Parent(props){
// array of options (currently an array of strings, but this can be your squares)
const allOptions = ['opt 1', 'opt 2', 'opt 3', 'etc'];
return (
<>
// map over the options and pass option to child component
{allOptions.map((option) => <Child option={option}/>)}
</>
)
}
function Child({ option }){
const [selected, setSelected] = useState(false); // default state is false
return (
<>
// render option value
<p>{option}</p>
// shows the state as selected or not selected
<p>Option is: {selected ? "selected" : "not selected"}</p>
// this button toggles the active state
<button onClick={() => setSelected(!selected)}>Toggle</button>
</>
)
}
I have a page wherein there are Listings.
A user can check items from this list.
Whenever the user checks something it gets added to a globally declared Set(each item's unique ID is added into this set). The ID's in this set need to be accessed by a seperate Component(lets call it PROCESS_COMPONENT) which processes the particular Listings whose ID's are present in the set.
My Listings code roughly looks like:
import React from "react";
import { CheckBox, PROCESS_COMPONENT } from "./Process.jsx";
const ListItem = ({lItem}) => {
return (
<>
//name,image,info,etc.
<CheckBox lId={lItem.id}/>
</>
)
};
function Listings() {
// some declarations blah blah..
return (
<>
<PROCESS_COMPONENT /> // Its a sticky window that shows up on top of the Listings.
//..some divs and headings
dataArray.map(item => { return <ListItem lItem={item} /> }) // Generates the list also containing the checkboxes
</>
)
}
And the Checkbox and the PROCESS_COMPONENT functionality is present in a seperate file(Process.jsx).
It looks roughly like:
import React, { useEffect, useState } from "react";
let ProcessSet = new Set(); // The globally declared set.
const CheckBox = ({lID}) => {
const [isTicked, setTicked] = useState(false);
const onTick = () => setTicked(!isTicked);
useEffect( () => {
if(isTicked) {
ProcessSet.add(lID);
}
else {
ProcessSet.delete(lID);
}
console.log(ProcessSet); // Checking for changes in set.
}, [isTicked]);
return (
<div onClick={onTick}>
//some content
</div>
)
}
const PROCESS_COMPONENT = () => {
const [len, setLen] = useState(ProcessSet.size);
useEffect( () => {
setLen(ProcessSet.size);
}, [ProcessSet]); // This change is never being picked up.
return (
<div>
<h6> {len} items checked </h6>
</div>
)
}
export { CheckBox, PROCESS_COMPONENT };
The Set itself does get the correct ID values from the Checkbox. But the PROCESS_COMPONENT does not seem to be picking up the changes in the Set and len shows 0(initial size of the set).
I am pretty new to react. However any help is appreciated.
Edit:
Based on #jdkramhoft
's answer I made the set into a state variable in Listings function.
const ListItem = ({lItem,set,setPSet}) => {
//...
<CheckBox lID={lItem.id} pset={set} setPSet={setPSet} />
)
}
function Listings() {
const [processSet, setPSet] = useState(new Set());
//....
<PROCESS_COMPONENT set={processSet} />
dataArray.map(item => {
return <ListItem lItem={item} set={processSet} setPSet={setPSet} />
})
}
And corresponding changes in Process.jsx
const CheckBox = ({lID,pset,setPSet}) => {
//...
if (isTicked) {
setPSet(pset.add(lID));
}
else {
setPSet(pset.delete(lID));
}
//...
}
const PROCESS_COMPONENT = ({set}) => {
//...
setLen(set.size);
//...
}
Now whenever I click the check box I get an error:
TypeError: pset.add is not a function. (In 'pset.add(lID)', 'pset.add' is undefined)
Similar error occurs for the delete function as well.
First of all, the set should be a react state const [mySet, setMySet] = useState(new Set()); if you want react to properly re-render with detected changes. If you need the set to be available to multiple components you can pass it to them with props or use a context.
Secondly, React checks if dependencies like [ProcessSet] has been changed with something like ===. Even though the items in the set are different, no change is detected because the object is the same and there is no re-render.
Update:
The setState portion of [state, setState] = useState([]); is not intended to mutate the previous state - only to provide the next state. So to update your set you would do something like:
const [set, setSet] = useState(new Set())
const itemToAdd = ' ', itemToRemove = ' ';
setSet(prev => new Set([...prev, itemToAdd]));
setSet(prev => new Set([...prev].filter(item => item !== itemToRemove)));
As you might notice, this makes adding and removing from a set as slow as a list. So unless you need to make a lot of checks with set.has() I'd recommend using a list:
const [items, setItems] = useState([])
const itemToAdd = ' ', itemToRemove = ' ';
setItems(prev => [...prev, itemToAdd]);
setItems(prev => prev.filter(item => item !== itemToRemove));
I have data coming from getTasks() and I store it on tasks
I created a component for each task with their data with a checkbox for each task.
I want to click a checkbox and display only the one I clicked, not them all.
I am using React Native. How can I do this?
thanks.
export default () => {
const [tasks, setTasks] = React.useState([]);
const [checked, setChecked] = React.useState(false);
React.useEffect(() => {
(async () => {
const response = await getTasks();
setTasks(response);
})();
}, []);
return tasks.map(({id, title, description, date}) => (
<View key={id} style={styles.task}>
<CheckBox
value={checked}
onValueChange={() => {
setChecked((prevState) => !prevState);
}}
/>
<View style={styles.taskTextContainer}>
<Text>{title}</Text>
<Text>{description}</Text>
<Text>{date}</Text>
</View>
</View>
));
};
You're passing the same value={checked} to all checkboxes. You can create an array of booleans to accomplish that.
You have two problems in that code, first is the problem you described, the second one is the way you're changing the state of that checkbox.
About the first problem, in order to display the respective state for each task, you should also include a property that defines if the task is marked or not.
First, verify the name of the property thats set if the task is done or not, I'll give the name here as done and done is of type boolean.
// notice the done property
return tasks.map(({id, title, description, date, done}) => (
Then, you should use done to change the checked state of your Checkbox like this:
<Checkbox
value={done}
...
/>
Now you should change a little how you change that checked state.
First, you need to write a function to change the value of done, to fase or true, according by the Checkbox component's state and that's really simple:
const onCheckboxChange = (taskId) => {
// first, let's find the index of the changed task
const index = tasks.findIndex(({ id }) => id === taskId)
// next, let's get the data by the index
const data = tasks[index]
// now, we can just toggle done value like this
data.done = !data.done
// then, let's assign updated data to its own index in tasks array
tasks[index] = data
// finally, we can update the tasks state using a copy of changed tasks
setTasks([...tasks])
}
I have the following situation, i provide the a codesandbox demo to demonstrate my existing problem.
A bit of explanation about and how to reproduce the case
I am here in the component League.js, any league in the list has a link
<a href={`#${item.league_id}`} onClick={(e) => onClick(e, item.league_id)}>
const onClick = (evt, id) => {
evt.preventDefault();
getDetail(id)
};
The onclick function populates in the Details.js component the select options with the names of the teams. You can see below.
if (teamsDetail.length) {
details = (
<select value={selectedOption} onChange={selectTeamStat}>
{teamsDetail && teamsDetail.length > 0
? teamsDetail.map(item => (
<option key={`${item.team_id}`} value={item.team_id}>
{item.name}
</option>
))
: null}
</select>
);
}
This is now the problem i have in Detail.js component, when i select the team name in my select i want send a get request getStats where i have to set two parameters ( team_id and league_id )
Here the relevant code
const [selectedOption, setSelectedOption] = useState("");
const selectTeamStat = evt => {
const { value } = evt.target;
setSelectedOption(value);
getStats(357, value);
};
At the moment i can only pass the team_id which is the select value selectedOption
How can i also pass the league_id parameter?
I have hardcoded now putting 357 but i need to use the parameter league_id. How can i pass it from League.js Component?
On the top of my head, here's two solutions you may want to try:
Set each of you select's options as a tuple:
value={[item.league_id, item.team_id]}
or set each of you select's options' team_id and league_id as data-attributes
I have figured it out how to pass the leagueId state
These the steps i have done
In the reducers/index.js i have created an initial state
RECEIVE_LEAGUE
league:[],
case RECEIVE_LEAGUE:
return {...state, leagueId: action.json};
In the actions/index.js
export const receivedLeague = json => ({
type: RECEIVE_LEAGUE,
json: json
});
I have added a dispatch in the getTeamsDetailById(id)
dispatch(receivedLeague(id));
In the Details.js component
I have added the state leagueId on top
and edited my selectTeamStat function
const [selectedOption, setSelectedOption] = useState('');
const selectTeamStat = (evt) => {
const { value } = evt.target;
setSelectedOption(value)
getStats(leagueId, value);
};