I have a following state declared
const [state, setState] = useState({
title: "",
subtitle: "",
products: [],
state.products is an array of objects e.g.
products = [{a: "", b: "", c: ""}]
On form submit I want to take the input values and append them to the products array as an object.
My submit handler doesn't do what I want. Where have I gone wrong?
function handleSubmit(e) {
const newProduct= {"a": e.target[0].value, "b": e.target[2].value, "c": e.target[1].value}
setState({...state, products: [...state.products, newProduct]})
}
You could do something like this
function handleSubmit(e) {
const newProduct = {
a: e.target[0].value,
b: e.target[2].value,
c: e.target[1].value
};
setState(prevState => ({
...prevState,
products: [...prevState.products, newProduct]
}));
}
If you console.log outside of the state setter, you will not get the latest copy of the state, i.e.,
const Component = () => {
const [state, setState] = useState(...);
function handler(e) {
...
setState(...);
console.log(state.products); // This will print the previous version of the state.
}
}
This is the expected behavior as state setters are asynchronous.
So instead I log like this,
function handler(e) {
const newProduct = ...;
...
setState(prevState => {
const newState = {...prevState, products: [...prevState.products, newProduct]}; // update state as you did before.
console.log(newState);
return newState;
});
}
You can also use
useEffect(() => {
console.log(state)
}, [state, state.products]);
Related
I'm trying to filter items by their category using useReducer
context.jsx
const initialState = {
categoryName: "all item",
};
const [state, dispatch] = useReducer(reducer, initialState);
const fetchUrl = async () => {
const resp = await fetch(url);
const respData = await resp.json();
const item = respData.item;
const category = respData.category;
const promo = respData.promo;
dispatch({ type: "CATEGORY_ITEM", payload: category });
};
I want to display the category name that matched the data.
reducer.jsx
if (action.type === "FILTER_NAME") {
if (action.payload === "all menu") {
return { ...state, categoryName: "all menu" };
//return { ...state, categoryName: state.categoryName};
} else {
return { ...state, categoryName: action.payload };
}
}
I cant set the categoryName back to the state value because it's been changed when I do else.
Is there a way for me to set a default value in reducer? Because if I use useState the setState won't overwrite the state default value.
Thanks before
These are my states using hooks:
const [adminProfile, setProfile] = useState({
locations: [],
});
const [location, setLocation] = useState({
locationName: "",
location: {},
locationPhone: "",
locationEmail: "",
staff: [],
multipleKitchens: false,
kitchens: [],
});
const [locationList, setLocationList] = useState([]);
const [locationAddress, setAddress] = useState({
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA"
});
I have a bunch of fields with onChange handlers and an onClick handler that needs to update 3 states in order. First, LocationAddress has to become the state of the location property within the location state. Second, the location state has to be updated with a unique ID, and then that unique ID is inserted into the array in the locationList state. Finally, the entire array from locationList state is added to the locations property of adminProfile state. These are all in one component.
const handleClickLocation = () => {
setLocation(prevValue => ({
...prevValue,
locationID: uuidv4(),
location: locationAddress
}));
setLocationList(prevValue => [...prevValue, location.locationID]);
setProfile(prevValue => ({
...prevValue,
locations: locationList
}))
The first time the click handler is triggered, it sets only the first state in the handler and sends "undefined" into the second state. When the click handler is clicked a second time, it then behaves normally. I want all the states to update simultaneously. I've tried forceUpdate(), but couldn't figure out the syntax. I've tried using ReactDOM.unstable_batchedUpdates but it still behaved the same.
How can I get this to work? I want to keep this within one component. Is that possible?
Here is the entire code updated with the useEffect hook:
import React, {useState, useEffect} from "react";
import axios from "axios";
const { v4: uuidv4 } = require('uuid');
const CompanyProfileInfo = (props) => {
const todayDate = () => {
let today = new Date();
let day = today.getDate();
let month = today.getMonth() + 1;
let year = today.getFullYear();
if (day < 10) day = '0' + day;
if(month < 10) month = '0' + month;
return (month + "/" + day + "/" + year);
}
const [adminProfile, setProfile] = useState({
companyID: props.companyInfo.companyID,
firstName: "",
lastName: "",
phonePrimary: "",
phoneSecondary: "",
emailSecondary: "",
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA",
multipleLocations: false,
locations: [],
membershipLevel: "Basic",
joinedDate: todayDate(),
});
const [location, setLocation] = useState({
locationName: "",
locationPhone: "",
locationEmail: "",
staff: [],
multipleKitchens: false,
kitchens: [],
});
const [locationAddress, setAddress] = useState({
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA"
});
const [locationList, setLocationList] = useState([]);
useEffect(() => {
setLocationList(prevValue => [...prevValue, location.locationID]);
}, [location.locationID]);
useEffect(() => {
if (locationList[0] === undefined) {
{locationList.shift()}
}
setProfile(prevValue => ({
...prevValue,
locations: locationList
})
)
}, [locationList])
const handleChange = (event) => {
const {name, value} = event.target;
setProfile(prevValue => ({
...prevValue,
[name]: value
}))
}
const handleChangeLocations = (event) => {
const {name, value} = event.target;
setLocation(prevValue => ({
...prevValue,
[name]: value
}));
};
const handleChangeLocations1 = (event) => {
const {name, value} = event.target;
setAddress(prevValue => ({
...prevValue,
[name]: value
}));
};
const handleClickLocation = () => {
setLocation(prevValue => ({
...prevValue,
locationID: uuidv4(),
location: locationAddress,
}));
};
const handleClick = () => {
axios.post('http://localhost:3001/profileinfo', adminProfile)
.then(res => {
props.supportFunctions.setUpLocations(res);
})
.catch(function (error) {
console.log(error);
})
}
return (
<div>
</div>
)
}
export default CompanyProfileInfo;
setState is asynchronous, it means that when it is called, its state won't update at the same time, it takes some time to perform its action.
You can make use of useEffect to do that.
useEffect will perform an action only when the specified state (inside brackets) changes
useEffect(() => {
setLocation({
...location,
location: locationAddress,
locationID: uuidv4()
})
}, [locationAddress]) //When locationAddress changes, setLocation
useEffect(() => {
setLocationList([
...locationList,
location.locationID
])
}, [location]) //When location changes, insert Id
Ps: You can have multiple useEffects in your code.
Updating of the state is asynchronous behavior, because of that you are getting locationID undefined for setLocationList.
Inside class component, we can use a callback to setState call like this -
this.setState({ data: newData }, () => { console.log("This will get printed after setState") })
But in your case, you are using function component so you have to use useEffect react hook to listen for changes in your data and then update other data in the state.
Take a look at this question - How to use `setState` callback on react hooks
The challenge I came across is using global store slice, namely 'genres', which is an array of objects, in a local state to manipulate check/uncheck of the checkboxes. The problem occurs when I'm trying to use props.genres in the initial state. Looks like I'm getting an empty array from props.genres when the local state is initialized.
const Filters = (props) => {
const { genres, getSelected, loadGenres, getGenres, clearFilters } = props
const [isChecked, setIsChecked] = useState(() =>
genres.map(genre => (
{id: genre.id, value: genre.name, checked: false}
))
)
const optionsSortBy = [
{name: 'Popularity descending', value: 'popularity.desc'},
{name: 'Popularity ascending', value: 'popularity.asc'},
{name: 'Rating descending', value: 'vote_average.desc'},
{name: 'Rating ascending', value: 'vote_average.asc'},
]
const d = new Date()
let currentYear = d.getFullYear()
let optionsReleaseDate = R.range(1990, currentYear + 1).map(year => (
{name: year + '', value: year}
))
useEffect(() => {
const url = `${C.API_ENDPOINT}genre/movie/list`
loadGenres(url, C.OPTIONS)
}, [])
const handleCheckbox = (e) => {
let target = e.target
getGenres(target)
}
const handleSelect = (e) => {
let target = e.target
let action = isNaN(target.value) ? 'SORT_BY' : 'RELEASE_DATE'
getSelected(action, target)
}
const handleSubmitBtn = (e) => {
e.preventDefault()
clearFilters()
}
return (
<form className={classes.FiltersBox}>
<Submit submited={handleSubmitBtn} />
<Select name="Sort By:" options={optionsSortBy} changed={handleSelect} />
<Select name="Release date:" options={optionsReleaseDate} changed={handleSelect} />
<Genres genres={isChecked} changed={handleCheckbox} />
</form>
)
}
const mapStateToProps = (state) => {
return {
genres: state.fetch.genres,
}
}
const mapDispatchToProps = (dispatch) => {
return {
loadGenres: (url, options) => dispatch(A.getApiData(url, options)),
getGenres: (targetItem) => dispatch({
type: 'CHECK_GENRES',
payload: targetItem
}),
getSelected: (actionType, targetItem) => dispatch({
type: actionType,
payload: targetItem,
}),
clearFilters: () => dispatch({type: 'CLEAR_FILTERS'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);
import * as R from 'ramda';
import fetchJSON from '../utils/api.js';
export const getApiData = (url, options) => async (dispatch) => {
const response = await fetchJSON(url, options)
const data = response.body
const dataHas = R.has(R.__, data)
let actionType = dataHas('genres') ? 'FETCH_GENRES' : 'FETCH_MOVIES'
dispatch({
type: actionType,
payload: data
})
}
export const fetchReducer = (state = initialState, action) => {
const { payload } = action
if (action.type === 'FETCH_GENRES') {
return {
...state,
isLoading: false,
genres: [...payload.genres]
}
}
if (action.type === 'FETCH_MOVIES') {
return {
...state,
isLoading: false,
movies: [...payload.results]
}
}
return state
}
What you are trying to do of setting initial value for state from props, is possible but isn't react best practice. Consider initial your data as empty array and through useEffect manipulate state
// didn't understand if its array or bool
const [isChecked, setIsChecked] = useState([])
useEffect(()=>genres&& { setIsChecked(... perform action...)
} ,[genres])
You approach is almost correct.
I am not sure how the state should look like, when you have fetched your data.
I can see in the mapStateToProps is trying to access a value which is not defined at the beginning. If state.fetch is undefined you can not access genres.
Attempt 1:
You can solve it by using lodash.get https://lodash.com/docs/#get
It will catch up for the undefined problem.
Attempt 2:
You can defined an initial state where your values are defined with mock data.
const initialState = {fetch: {genres: []}}
and use it your reducer
i'm trying to update the user info on my database
on the handleChange function every time there is a new change but the problem that im facing is that i have to to wait for the setdata till it finish then updateUserInfo how i can solve that
const [mydata, setData] = useState({
user_gender: "",
user_relationship: "",
user_birth_day: "",
user_birth_month: "",
user_gender_interest: "",
user_birth_year: "",
user_interests: {
dancing: false,
family: false,
art: false,
photography: false,
friends: false,
travel: false
}
});
const handleChange = event => {
setData({
...mydata,
[event.target.name]: event.target.value
});
async function update() {
await updateUserInfo(mydata[event.target.name], stableDispatch);
}
update();
};
Call updateUserInfo() as a callback.
You can pass a function as a 2nd parameter to setState() which will automatically be called when the state is set.
useEffect(() => {
updateUserInfo(mydata[event.target.name], stableDispatch));
}, [mydata]);
The solution here is to copy the state in a variable which you can use to update state and the userInfo
const handleChange = event => {
const data = {
...mydata,
[event.target.name]: event.target.value
}
setData(data);
async function update() {
await updateUserInfo(data[event.target.name], stableDispatch);
}
update();
};
I try to use react with hooks. I have this state:
const [state, setState] = useState<State>({
titel: "",
somethingElse: "",
tabledata: [],
});
I have two useeffect:
// componentDidMount, Initial variable deklaration
useEffect(() => {
//Do something
//Set initial states
setState({
...state,
titel: props.titel,
somethingElse: props.somethingElse,
})
}, []);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
//Do something and generate tabledata
let tabledata ....
//Set tabledata
setState({
...state,
tabledata: tabledata,
})
}, [props.taenzer]);
Now I have the behavior, that the second useeffect is overwriting the first useeffect setState command.
My variable titel and somethingElse is always empty.
Now I could change my deklaration of state, something in this way:
const [titel, setTitel] = useState<>({
titel = ""
});
const [somethingElse, setSomethingElse] = useState<>({
somethingElse = ""
});
But this makes the whole unclear and it is not so easy to set the state of several variables in one time like we could with setState(...state, titel="Hello", somethingElse="Else")
Any other possibility?
the second useeffect is overwriting the first useeffect setState
useState function doesn't automatically merge the state. So you would need to make use of the previous state accessible via callback.
useEffect(
() => {
const tabledata = ...
setState(prevState => ({ // use previous state
...prevState,
tabledata
}))
}, [props.taenzer]
)
Any other possibility?
You can have lazy initialization of your state via props and remove the first effect (without dependency)
const [{ title, somethingElse, tabledata }, setState] = useState<State>({
...props,
tabledata: [],
});