const [allCases, setAllCases] = useState([])
const [myCases, setMyCases] = useState([])
const [sharedCases, setSharedCases] = useState([])
const [favoriteCases, setFavoriteCases] = useState([])
useEffect(()=> {
getData(_id).then(res => {
const favoriteIds = res.data.find(i => i._id === _id).cases.map(x => {
return x._id
})
setAllCases(res.cases.map(x => {
if (favoriteIds.includes(x._id)) {
return { ...x, isfavorite: true }
}
return { ...x, isfavorite: false }
}))
allCases && setSharedCases(allCases.filter(x => x.creator._id !== user._id))
allCases && setMyCases(allCases.filter(x => x.creator._id === user._id))
allCases && setFavoriteCases(allCases.filter(x => x.isfavorite))
})
}, [])
Hello, I am having problems with infinite loop. If I include brackets, my filtered cases don't populate. If i add another useEffect, I run into an infinite loop even though I am not changing allCases. (wondering why)
How can i resolve this asynchronous issue?
One simple way to bypass this issue would be to call your other useState setters with a local variable instead of the state. Then you can update the allCases state with the same variable, and not have to work around async functions.
useEffect(()=> {
getData(_id).then(res => {
const favoriteIds = res.data.find(i => i._id === _id).cases.map(x => {
return x._id
})
// Save it to a local variable so that we have the value immediately
const newAllCases = res.cases.map(x => {
if (favoriteIds.includes(x._id)) {
return { ...x, isfavorite: true }
}
return { ...x, isfavorite: false }
})
// Use the local variable to update state
setSharedCases(newAllCases.filter(x => x.creator._id !== user._id))
setMyCases(newAllCases.filter(x => x.creator._id === user._id))
setFavoriteCases(newAllCases.filter(x => x.isfavorite))
// Set the state for allCases
setAllCases(newAllCases)
})
}, [])
allCases within useEffect is not set before you are setting sharedCases and others. Also, with [] in useEffect it would only call the function on load of component. So your other state values would not get set.
I would suggest to have a separate useEffect with [allCases] and set all other values there. Something like below
useEffect(()=> {
getData(_id).then(res => {
const favoriteIds = res.data.find(i => i._id === _id).cases.map(x => {
return x._id
})
setAllCases(res.cases.map(x => {
if (favoriteIds.includes(x._id)) {
return { ...x, isfavorite: true }
}
return { ...x, isfavorite: false }
}))
})
}, [])
useEffect(() => {
// do filter and set the state
}, ['allCases'])
Related
I want to pass object value dynamically as key of setState.
useEffect(() => {
inputFields.forEach((item) => {
return setState({ ...state, [item.name]: "" });
});
}, [])
You can clone, assign and then setstate
useEffect(() => {
const sItem = {...state };
inputFields.forEach((item) => {
sItem[item.name] = "";
});
setState(sItem)
}, [])
I have an array of objects in my React state. I want to be able to map through them, find the one I need to update and update its value field. The body of my request being sent to the server should look like:
{ name: "nameOfInput", value:"theUserSetValue" type: "typeOfInput" }
What I thought would be simple is causing me some heartache. My reducer function calls, and I hit the "I AM RUNNING" log where it then jumps over my map and simply returns my state (which is empty). Please note that I NEVER see the "I SHOULD RETURN SOMETHING BUT I DONT" log.
NOTE: I have learned that I could be simply handingling this with useState
function Form(props) {
const title = props.title;
const paragraph = props.paragraph;
const formBlocks = props.blocks.formBlocks
const submitEndpoint = props.blocks.submitEndpoint || "";
const action = props.blocks.action || "POST";
const formReducer = (state, e) => {
console.log("I AM RUNNING")
state.map((obj) => {
console.log("I SHOULD RETURN SOMETHING BUT I DONT")
if (obj.name === e.target.name) {
console.log("OBJ EXISTS", obj)
return {...obj, [e.target.name]:obj.value}
} else {
console.log("NO MATCH", obj)
return obj
}
});
return state
}
const [formData, setFormData] = useReducer(formReducer, []);
const [isSubmitting, setIsSubmitting] = useState(false);
=====================================================================
Where I am calling my reducer from:
<div className="form-block-wrapper">
{formBlocks.map((block, i) => {
return <FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={setFormData}
/>
})}
</div>
Issues
When using the useReducer hook you should dispatch actions to effect changes to the state. The reducer function should handle the different cases. From what I see of the code snippet it's not clear if you even need to use the useReducer hook.
When mapping an array not only do you need to return a value for each iterated element, but you also need to return the new array.
Solution
Using useReducer
const formReducer = (state, action) => {
switch(action.type) {
case "UPDATE":
const { name, value } = action.payload;
return state.map((obj) => obj.name === name
? { ...obj, [name]: value }
: obj
);
default:
return state;
}
};
...
const [formData, dispatch] = useReducer(formReducer, []);
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={e => dispatch({
type: "UPDATE",
payload: {...e.target}
})}
/>
);
})}
Using useState
const [formData, setFormData] = useState([]);
...
const changeHandler = e => {
const { name, value } = e.target;
setFormData(data => data.map(obj => obj.name === name
? { ...obj, [name]: value }
: obj
));
};
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={changeHandler}
/>
);
})}
I have come to understand my problem much better now and I'll update my question to reflect this.
As the user interacted with an input I needed to figure out if they had interacted with it before
If they did interact with it before, I needed to find that interaction in the state[] and update the value as required
If they didn't I needed to add an entirely new object to my forms state[]
I wrote two new functions, an AddObjectToArray function and an UpdateObjectInArray function to serve these purposes.
const handleFormInputChange = (e) => {
const { name, value, type } = e.target;
const addObjectToArray = (obj) => {
console.log("OBJECT TO BE ADDED TO ARRAY:", obj)
setFormData(currentArray => ([...currentArray, obj]))
}
const updateObjectInArray = () => {
const updatedObject = formData.map(obj => {
if (obj.name === name) {
//If the name matches, Update the value of the input
return ({...obj, value:value})
}
else {
//if no match just return the object as is
return obj
}
})
setFormData(updatedObject)
}
//Check if the user has already interacted with this input
if (formData.find(input => input.name === name)) {
updateObjectInArray()
}
else {
addObjectToArray({name, value, type})
}
}
I could get more complicated with this now and begin to write custom hooks that take a setState function as a callback and the data to be handled.
I use handleMouseOver and handleMouseOut functions where I change the value of a state count. However, every time the state is changed the component re-renders instead of just the state. What am I missing here? Thanks.
function foo() {
const [state, setState] = useState({ count: 0, data: {}});
useEffect(() => {
const getData = async () => {
const response = await fetch(url);
const data = await response.json();
setState(prevState => ({
...prevState,
data: data,
}));
};
return ()=>{
getData();
}
}, []);
function handleMouseOver(e) {
setState(prevState => ({
...prevState,
count: e,
}));
};
function handleMouseLeave() {
setState(prevState => ({
...prevState,
count: null,
}));
};
const { count, data } = state;
const BlockComponent = () => {
const data = data.arr;
return (
<Wrapper >
{data.map((value, index) =>
value.map((value, index) => {
return (
<Block
key={index}
val={value}
onMouseEnter={e => handleMouseOver(value)}
onMouseOut={handleMouseLeave}
></Block>
);
})
)}
</Wrapper>
);
};
return (
<Wrapper>
<BlockComponent />
</Wrapper>
);
}
export default foo;
The Issue is with your handleMouseOver function. It is getting executed everytime there is a state Update and the same value is assigned to "count".
All you have to do is place setState inside the condition that will compare the value of event received by the function and the current value of sate.
It should be something like this.
function handleMouseOver(e) {
if (count !== e) {
setState((prevState) => ({
...prevState,
count: e,
}));
}
}
React updates component if component state is changed. That's correct behaviour.
I recommend you to learn react documentation, because component state is a basic concept.
That's one of the main points of state -> component is rerendered when you change state.
I use React js hooks to fetch data from api every 10 seconds in UseEffect. The problem is that it takes 10 seconds to do the state update first.
So before the setInterval function, I need to fetch the data once the component is not rendered.
Code part is here:
function getList() {
return axios.post('http://localhost:5000/getParameters', { no: no, name: name })
.then(data => data.data)
}
function getParams() {
return axios.post('http://localhost:5000/getParametersSite', { no: no, name: name })
.then(data => data.data)
}
useEffect(() => {
let mounted = true;
let isMounted = true
const intervalId = setInterval(() => {
getParams()
.then(itemsa => {
if(mounted) {
setParams(itemsa)
}
})
getList()
.then(items => {
if(mounted) {
setMenuData(items)
}
})
}, 10000)
return () => {
clearInterval(intervalId);
isMounted = false
mounted = false;
}
}, [menuData,params])
You can use a useRefhook to know if it is the first render. like this:
const firstUpdate = useRef(true);
useEffect(() => {
let mounted = true;
let isMounted = true
if (firstUpdate.current) {
firstUpdate.current = false;
getParams()
.then(itemsa => {
if(mounted) {
setParams(itemsa)
}
})
getList()
.then(items => {
if(mounted) {
setMenuData(items)
}
})
}
const intervalId = setInterval(() => {
getParams()
.then(itemsa => {
if(mounted) {
console.log("getParams",itemsa);
setParams(itemsa)
}
})
getList()
.then(items => {
if(mounted) {
console.log("setParams",items);
setMenuData(items)
}
})
}, 10000)
return () => {
clearInterval(intervalId);
mounted = false;
}
}, [menuData,params])
like explain in react doc :
useRef returns a mutable ref object whose .current property is
initialized to the passed argument (initialValue). The returned object
will persist for the full lifetime of the component.
So no matter if your component render again.
I have the following script:
const [customers, setCustomer] = useState([]);
if(users.results){
users.results.filter(user => {
user.roles.filter(role => {
if(role.role.name === 'ADMIN'){
admins.push(user);
}
});
});
let x = []
users.results.filter(user => {
user.roles.filter(role => {
if (role.role.name === 'CUSTOMER') {
x.push(user);
}
});
});
setCustomer(x);
}
Trying to call setCustomer causes the Too many re-renders. React limits the number of renders to prevent an infinite loop. error. I can't seem to find the reason why.
How do I set the customers to the value of x without causing the above error?
UPDATED CODE
const Administration = props =>{
const { fetchUsers, users, loading_users } = props;
const usersPerPage = 9
useEffect(() => {
fetchUsers();
}, []);
let admins = [];
const [customers, setCustomer] = useState([]);
if(users.results){
users.results.filter(user => {
user.roles.filter(role => {
if(role.role.name === 'ADMIN'){
admins.push(user);
}
});
});
let x = []
users.results.filter(user => {
user.roles.filter(role => {
if (role.role.name === 'CUSTOMER') {
x.push(user);
}
});
});
setCustomer(x);
}
It sounds like you only want to update customer with setCustomer when users updates. Placing this in a useEffect with users as a "only call this when this changes" option can do that for you. It also looks like admins is supposed to be state:
const [admins, setAdmins] = useState([]);
useEffect(() => {
if(users.results){
let admins = [];
users.results.filter(user => {
user.roles.filter(role => {
if(role.role.name === 'ADMIN'){
admins.push(user);
}
});
});
setAdmins(admins);
let x = []
users.results.filter(user => {
user.roles.filter(role => {
if (role.role.name === 'CUSTOMER') {
x.push(user);
}
});
});
setCustomer(x);
}
}, [users]);
Now this will only run on mount and when the users state changes.