Okay, I am experiencing some behaviour I don't really understand.
I have this useState hook
const [permanent, setPermanent] = useState(false)
and this useEffect hook
useEffect(() => {
if (permanent) {
dispatch({ value: 'Permanent booth', key: 'period' })
} else {
dispatch({ value: '0', key: 'period' })
}
}, [permanent])
It triggers a rerender on initial render, and I do not call setPermanent upon rendering my component, I have checked this both by commenting every single setPermanent call out in my application. And I have also tried replacing it with a function that logs to the console.
//const [permanent, setPermanent] = useState(false)
const permanent = false
const setPermanent = () => {
console.log('I am called') //does not get called on initial render
}
I know it triggers a rerender because when I comment one of the second dispatch call in it out, it does not trigger the rerender.
useEffect(() => {
if (permanent) {
dispatch({ value: 'Permanent booth', key: 'period' })
} else {
//dispatch({ value: '0', key: 'period' })
}
}, [permanent])
Is there a reason for this, because I cannot seem to find documentation explaining this behaviour?
EDIT --------------
const shopOptions = (() => {
const options = [
{ label: 'Choose a shop', value: '0' },
]
Object.keys(stores).forEach(store => {
options[options.length] = { label: store, value: options.length }
})
return options
})()
const genderOptions = [
{ label: 'Choose a gender', value: '0' },
{ label: 'Female', value: '1' },
{ label: 'Male', value: '2' }
]
const periodOptions = [
{ label: 'Choose a period', value: '0' },
{ label: '1 week', value: '1' },
{ label: '2 weeks', value: '2' },
{ label: '3 weeks', value: '3' },
{ label: '4 weeks', value: '4' }
]
const initialState = {
shop: shopOptions[0],
gender: genderOptions[0],
period: periodOptions[0],
}
function reducer(prevState, { value, key }) {
const updatedElement = { ...prevState[key] }
updatedElement.value = value
return { ...prevState, [key]: updatedElement }
}
//form
const [state, dispatch] = useReducer(reducer, initialState)
useEffect hooks run both after the first render and after every update of variables passed to the dependency array (in your case [permanent]).
Because you have a boolean value that triggers the effect, it's hard to know whether it's the first render or a re-render within the effect. In your case I would consider not using a useEffect here, and instead dispatching what you need while updating the state. For example:
const [permanent, setPermanent] = useState(false)
const makePermanent = () => {
setPermanent(true)
dispatch({ value: 'Permanent booth', key: 'period' })
}
const makeTemporary = () => {
setPermanent(false)
dispatch({ value: '0', key: 'period' })
}
This is my solution. Just a boolean property and ordering hook will do the job.
const _isMounted = React.useRef(false)
const [filter, setFilter] = React.useState('')
React.useEffect(() => {
if (_isMounted.current) {
getData(filter) // Now it will not get called very first time
}
}, [filter])
/* Order is important. [] or so called componentDidMount() will be the last */
React.useEffect(() => {
_isMounted.current = true
return () => {
_isMounted.current = false
}
}, [])
Related
I am using the NHL api to try to grab players stats for a given season. I have a utility function with these season values :
export const seasonOptions = [
{ value: "19861987", label: "1986/1987" },
{ value: "19871988", label: "1987/1988" },
{ value: "19881989", label: "1988/1989" },
{ value: "19891990", label: "1989/1990" },
{ value: "19901991", label: "1990/1991" },
{ value: "19911992", label: "1991/1992" },
{ value: "19921993", label: "1992/1993" },
{ value: "19931994", label: "1993/1994" },
{ value: "19941995", label: "1994/1995" },
{ value: "19951996", label: "1995/1996" },
];
... and so on. In my component I have this state to setSelect on what was selected:
const [select, setSelect] = useState(seasonOptions[seasonOptions.length - 1]);
const handler = (selected) => {
setSelect((select) => select);
handlePlayerStats(
`https://statsapi.web.nhl.com/api/v1/people/${props.playerId}/stats?stats=statsSingleSeason&season=${selected.value}`
);
}
};
<Select
id="select"
instanceId={"select"}
options={seasonOptions}
placeholder={select.label}
autoFocus
value={select.value}
onChange={handler}
/>
Which calls this custom hook:
const handlePlayerStats = async (url) => {
try {
const req = await fetch(url).then((response) => response.json());
console.log(req);
if (req.messageNumber) {
setFetchedData([]);
} else if (req.stats[0].splits.length > 0) {
setFetchedData(req);
}
} catch (error) {
console.log(error);
}
};
I'm not really sure how to go about looping through all the seasonOptions dynamically and filtering out each season where req.stats[0].splits.length === 0?
Here is the codesandbox link for anyone curious: https://codesandbox.io/s/friendly-kapitsa-c97rzy?file=/components/PlayerStats.js:357-855
To Answer The first parst of your question you can map over this Array of Objects with this method for example using the .map method
React Code SandBox
MDN-.map() method JS
export const seasonOptions = [
{ value: 8471214, label: "198601987" },
{ value: 8471215, label: "198701988" },
{ value: 8471216, label: "198801989" },
{ value: 8471217, label: "198901990" },
{ value: 8471218, label: "199001991" },
{ value: 8471219, label: "199101992" },
{ value: 8471220, label: "199201993" },
{ value: 8471221, label: "199301994" },
{ value: 8471222, label: "199401995" },
{ value: 8471223, label: "199501996" },
];
//MAKE SURE TO MAP OVER THE <option></option> tag not the <Select/>
//CHECK CODE SANDBOX FOR EXAMPLE IN PURE HTML5 AND REACT
{seasonOptions.map((option,index)=>
<Select
key={index}
id="select"
instanceId={"select"}
options={option?.value}
placeholder={option?.label}
autoFocus
value={select.value}
onChange={handler}
/>
)}
Check Out my Answer Here or for other examples here how to map over an array and access it values this method is just 1 of many .
How to find a value from array object in JavaScript? Stack Over Flow Question
For the Second Part Of the Question you can use the new SandBox
Steps
Change the value property here from a string to a number by removing the quotation marks export const seasonOptions = [{value: 8471214, label: "198601987"},]
Assign a useState hook to handle the active filtered item
const [activeFilter, setActiveFilter] = useState([]);
3.Assign an arrow function to handle Filtering the Seasons
using the setTimeOut() method setTimeout()-MDN-DOCS Where 500 is the time the function is executed for that time and
const handleSeasonsFilter = (item) => {
setActiveFilter(item);
setTimeout(() => {
if (!item) {
setFilterSeasons(item);
} else {
setFilterSeasons(
seasonOptions.filter((season) =>
seasonOptions?.includes(season?.label)
)
);
}
}, 500);
};
Pass that to url that searches the API url = `https://statsapi.web.nhl.com/api/v1/people/${activeFilter}/stats?stats=statsSingleSeason&season=200402005 Like in Line 65 in The Sand Box
Display Them using useEffect() hook also add in the values in the dependency array or leave it empty to avoid infinite loops.
useEffect(() => {
//debug data
console.log(stringDataDisplay);
setActiveFilter(activeFilter);
//DEBUG IF VALUE IF PASSED
//setDatasearched(activeFilter);
}, [
/**EMPTY DEPENDENCY ARRAY TO AVOID INFINITE LOOPS */
stringDataDisplay,
activeFilter
]);
in My Example i Displayed Them using another useState Hook and State action
const [datasearched, setDatasearched] = useState([])
& Finally Just Assigned a new const stringDataDisplay = JSON.stringify([datasearched]);
To Stringify the [datasearched] Array Here.
Note
Make sure to pass the handleSeasonsFilter to OnClick as an empty arrow function and pass the option.value property as a String so the API Accepts the request.
Hope this helps with your Example and Ill try to Check the code sandbox also with your method.
Bear in Mind i still i am developing this and i understand you want the values of the seasons to be shown when no player id is selected Am i correct?
I have a question I am making React app. The thing is that in useEffect I loop through six items every time when only one thing changes. How to solve it to change only one variable which was changed in reducer function not looping for 6 items when only one was changed, or is it okay to keep code like this?
const initialReducerValue = {
name: {
val: '',
isValid: false,
},
lastName: {
vaL: '',
isValid: false
},
phoneNumber: {
val: '',
isValid: false
},
city: {
val: '',
isValid: false,
},
street: {
val: '',
isValid: false
},
postal: {
val: '',
isValid: false
},
}
const OrderForm = () => {
const orderReducer = (state, action) => {
if (action.type === 'HANDLE TEXT CHANGE') {
return {
...state,
[action.field]: {
val: action.payload,
isValid: true
}
}
}
}
const [formState, formDispatch] = useReducer(orderReducer, initialReducerValue)
const [formIsValid, setFormIsValid] = useState(false)
const changeTextHandler = (e) => {
formDispatch({
type: 'HANDLE TEXT CHANGE',
field: e.target.name,
payload: e.target.value
})
}
useEffect(() => {
const validationArray = []
for (const key of Object.keys(formState)) {
validationArray.push(formState[key].isValid)
}
const isTrue = validationArray.every(item => item)
setFormIsValid(isTrue)
}, [formState])
This code
const validationArray = []
for (const key of Object.keys(formState)) {
validationArray.push(formState[key].isValid)
}
const isTrue = validationArray.every(item => item)
is equivalent to
const isTrue = Object.values(formState).every(item => item.isValid);
This still iterates over all items when only one was changed, but with a temporary array less.
For six items, I would not spend time trying to optimize this code further, but that's your choice.
I have React Component with state:
this.state={
items: [
{
label: '1 Some text',
active: true
},
{
label: '2 Some text',
active: false
},
{
label: '3 Some text',
active: false
},
{
label: '4 Some text',
active: false
}
]
}
I have a menu, when I click on it, I would like to change the state:
this.state.items.map(item => {
return <li onClick={}>{item.label}</li>
})
If I click on any of the menu items, I want to make it active: true, and set all others to active: false.
Update:
manuOnClick=(e, label)=>{
let newState = this.state.items.map(item=>{
if(label === item.label)
{
let newItem={
label:label,
active:true
}
return newItem
}
else
{
let newItem={
label:item.label,
active:false
}
return newItem
}
})
this.setState({
items:newState
})
}
Sorry,
onClick={(e,item.label)=>this.manuOnClick(e,item.label)
is not correct, it should be
onClick={(e)=>this.manuOnClick(e,item.label)
Try this :
manuOnClick=(e, label)=>{
let newState = this.state.items.map(item=>{
if(label === item.label)
{
let newItem={
label:label,
active:true
}
return newItem
}
else
{
return item
}
})
this.setState({
items:newState
})
}
this.state.items.map(item => {
return <li onClick={(e)=>this.manuOnClick(e,item.label)}>{item.label}</li>
})
You'll want to use this.setState:
https://reactjs.org/docs/react-component.html
I would highly recommend considering splitting this component out into different components, one per item, each with it's own state management. Is there a reason the state for all of them need to be managed at this parent level? Simpler is better than complex.
That being said, if you do move forward and decide that you need it at this level, I'd recommend some thing like the following for each onClick:
this.setState((state) => {
const itemsCopy = state.items.slice();
itemsCopy[itemIndex].active = true;
return { items: itemsCopy };
});
I want to access state data.
How do I copy an array object I have into a data array?
-code
function TableList() {
const [state, setState] = React.useState({
columns: [
{ title: '로트번호', field: 'lote_id' },
{ title: '중량(kg)', field: 'item_weight' },
{ title: '수량', field: 'item_quantity' },
{ title: '제품코드', field: 'item_code' },
{ title: '제품명', field: 'item_name' },
{ title: '규격', field: 'standard' },
{ title: '재질', field: 'material' },
{ title: '공정명', field: 'process_name' },
{ title: '단중', field: 'unitweight' },
{ title: '납기일시', field: 'duedate' },
{ title: '단가', field: 'item_cost' },
{ title: '금액', field: 'item_price' },
],
data: [],
});
useEffect(() =>{
console.log("실행")
console.log(state.data);
axios.get('http://localhost:8080/api/item/1')
.then( response => {
//I want to copy the data of my output.list to state.data.
})
.catch( response => {
console.log(response);
})
});
I want to copy my axios response data into the data array declared as useState.
Please tell me the most effective way.
How do I access the second argument, state.data?
I want to access and put my output.list response data.
Not totally clear on what you're looking for, but if you just want to update the data prop on your state object, you can do it like this:
const arrayOfObjects = [{ id: 1 }, { id: 2 }];
const newState = Object.assign({}, state);
newState.data = arrayOfObjects;
setState(newState);
This is not a great way to use useState.
const [columns,setColumns] = React.useState([bla,bla]);
const [data, setData] = React.useState([bla, bla]);
You should declare your state variables separately like above.
useEffect(() =>{
console.log("실행")
console.log(state.data);
axios.get('http://localhost:8080/api/item/1')
.then( response => {
//console.log(response);
var output = response && response.data;
const newState = Object.assign({}, state);
newState.data = output.list;
setState(newState);
})
.catch( response => {
console.log(response);
})
});
I want to call it just once like the mounted function
How would I update a title of the specific id with hooks state setup. Here:
const NotesContainer = ({
}) => {
const [notesDummyData, setNotesDummyData] = useState([
{
id: '5',
title: 'Sauna',
},
{
id: '7',
title: 'Finland',
},
]);
const onChangeItemName = (newTitle, oldTitle, itemId) => {
//Update a new title here for id 7
};
Could not find any example for setState hooks.
Just map through the items and if the id is equal to the selected id you modify only the value:
const onChangeItemName = (newTitle, oldTitle, itemId) => {
setNotesDummyData(notesDummyData.map(x => {
if(x.id !== itemId) return x
return {...x, title: newTitle}
}))
}
You can use Array.map(). For each item check if the id is equal to itemId. If it this, spread the item, and replace the title. If not, return the original item:
const onChangeItemName = (title, itemId) => {
setNotesDummyData(notesDummyData.map(o => o.id === itemId ? ({
...o,
title
}) : o));
};
It's easy to update data using React Hook, but there is not working setState(), so there will be working [notThis, thisOne(setNotesDummyDate)] to update your data.
const [notesDummyData, setNotesDummyData] = useState([
{
id: '5',
title: 'Sauna',
},
{
id: '7',
title: 'Finland',
},
]);
Using React Hook method to Update data:
const onChangeItemName = (newTitle, oldTitle, itemId) => {
setNotesDummyDate = useState([
{
id: itemId, // Update
title: newTitle,
},
{
id: itemId, // Update
title: newTitle,
},
]);
}
Still Curious, study here about useState()
Cheer you!