I have a component that uses a prop that recieves data from its parent. I need to edit the value of the prop Im not sure if its possible. I tried using state to copy
const Form = ({ details, form }) => {
useEffect(() => {
details.name = 'greg'
}, [])
}
Is it possible to do something like this? I get an error something like object is not extensible.
Assuming, details is a state variable, you can't update state that way. State is supposed to be immutable. To update state, you can do something like:
import React, {useState, useEffect} from "react";
const Parent = () => {
const [form, setForm] = useState({});
const [details, setDetails] = useState({ name: "bill" });
return (
<Form details={details} form={form} setDetails={setDetails} />
);
};
const Form = ({ details, form, setDetails }) => {
useEffect(() => {
setDetails({
...details,
name: "greg",
});
}, []);
return (
<div>Hello</div>
);
}
Related
I have a problem with updating the state of parent component with a props from child component. It seems the following code is not working, however it looks fine
setUsersList(prevState => {
return [...prevState, data];
});
My parent component receives an object. Console.log(data) outputs the object received from child component. However, when console logging updated state (console.log(usersList)) it returns an empty array
Parent component:
import React, { useState } from "react";
import AddUser from "./components/Users/AddUser";
import UsersList from "./components/Users/UsersList";
function App() {
const [usersList, setUsersList] = useState([]);
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
return [...prevState, data];
});
console.log(usersList);
};
return (
<div>
<AddUser onAddUser={addUserHandler}></AddUser>
<UsersList users={usersList}></UsersList>
</div>
);
}
export default App;
Child component:
import React, { useState } from "react";
import Button from "../UI/Button";
import Card from "../UI/Card";
import styles from "./AddUser.module.css";
const AddUser = props => {
const [inputData, setInputData] = useState({ name: "", age: "" });
const addUserHandler = event => {
event.preventDefault();
if (
inputData.age.trim().length === 0 ||
inputData.name.trim().length === 0
) {
return;
}
if (+inputData.age < 1) {
return;
}
props.onAddUser(inputData);
console.log(inputData.name, inputData.age);
setInputData({ name: "", age: "" });
};
const usernameChangeHandler = event => {
setInputData({ ...inputData, name: event.target.value });
};
const ageChangeHandler = event => {
setInputData({ ...inputData, age: event.target.value });
};
return (
<Card className={styles.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
onChange={usernameChangeHandler}
value={inputData.name}
></input>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
onChange={ageChangeHandler}
value={inputData.age}
></input>
<Button type="submit">Add User</Button>
</form>
</Card>
);
};
export default AddUser;
Due to the way react re-renders components, your console may not log with the expected state change. Instead you can use useEffect for debugging purposes:
parent component
useEffect(() => {
console.log("usersList", usersList);
}, [usersList])
alternatively, having a console.log statement in the body of your functional component should log the correct 'usersList'.
const [usersList, setUsersList] = useState([]);
console.log("usersList", usersList);
The state variable won't change right away when you call setState function from the useState hook. Since it is an asynchronous event.
So you might need to write your code like this to see the right console.log
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
const temp = [...prevState, data];
console.log(temp); // like this
return temp;
});
};
If the state is not updating in the UI. Please paste the error or the warning message.
Since setUserList is async function, you can not see the changes on console in the addUserHandler function.
function App() {
const [usersList, setUsersList] = useState([]);
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
return [...prevState, data];
});
};
console.log(usersList);
return (
<div>
<AddUser onAddUser={addUserHandler}></AddUser>
<UsersList users={usersList}></UsersList>
</div>
);
}
export default App;
This will work. Thanks.
having an issue, when the when nav to the comp the items state is empty, if I edit the code and page refreshes its shows up and if I add the state to the useEffect "[itemCollectionRef, items]" it's an inf loop but the data is their anyone have a better idea or way to fetch the data for display from firestore.
import React, { useState, useEffect } from "react";
import { Grid, Box, Button, Space } from "#mantine/core";
import { ItemBadge } from "../../components/NFAItemBadge";
import { useNavigate } from "react-router-dom";
import { db, auth } from "../../firebase";
import { getFirestore, query, getDocs, collection, where, addDoc } from "firebase/firestore";
import { useAuthState } from "react-firebase-hooks/auth";
const ItemTrack = () => {
const [user, loading, error] = useAuthState(auth);
const navigate = useNavigate();
const [items, setItems] = useState([]);
const itemCollectionRef = collection(db, "items");
useEffect(() => {
//if(!user) return navigate('/');
//if(loading) return;
const q = query(itemCollectionRef, where("uid", "==", user.uid));
const getItems = async () => {
const data = await getDocs(q);
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
console.log("Fetched Items: ", items);
};
getItems();
}, []);
if (loading) {
return (
<div>
<p>Initialising User....</p>
</div>
);
}
if (error) {
return (
<div>
<p>Error: {error}</p>
</div>
);
}
if (user) {
return (
<Box sx={{ maxWidth: 1000 }} mx="auto">
</Box>
);
} else {
return navigate("/");
}
};
export default ItemTrack;
It will depend how you will render the data from the useEffect. setState does not make changes directly to the state object. It just creates queues for React core to update the state object of a React component. If you add the state to the useEffect, it compares the two objects, and since they have a different reference, it once again fetches the items and sets the new items object to the state. The state updates then triggers a re-render in the component. And on, and on, and on...
As I stated above, it will depend on how you want to show your data. If you just want to log your data into your console then you must use a temporary variable rather than using setState:
useEffect(() => {
const newItems = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
console.log(newItems)
// setItems(newItems)
}, [])
You could also use multiple useEffect to get the updated state object:
useEffect(() => {
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })))
}, [])
useEffect(() => { console.log(items) }, [items])
If you now want to render it to the component then you have to call the state in the component and map the data into it. Take a look at the sample code below:
useEffect(() => {
const q = query(itemCollectionRef, where("uid", "==", user.uid));
const getItems = async () => {
const data = await getDocs(q);
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
getItems();
}, []);
return (
<div>
<p>SomeData: <p/>
{items.map((item) => (
<p key={item.id}>{item.fieldname}</p>
))}
</div>
);
I am beginner in Reactjs. I was building an form application using the same. There I was asked to set value of input field from the server, which can be updated by user i.e. an controlled input component.
I fetched the value in parent state then I passed the value to the child state and from there I set value of input field. Now the problem arises when I update the value in parent state then the value isn't getting updated in the child state.
See the code below -
App.jsx
import { useEffect, useState } from "react";
import { Child } from "./child";
import "./styles.css";
export default function App() {
const [details, setDetails] = useState({});
useEffect(() => {
fetch("https://reqres.in/api/users/2")
.then((res) => res.json())
.then((data) => setDetails(data));
}, []);
useEffect(() => {
console.log("data of details", details?.data);
}, [details]);
return (
<div className="App">
<h1>Testing</h1>
<Child details={details} setDetails={setDetails} val={details?.data} />
</div>
);
}
Child.jsx
import { useState } from "react";
export const Child = ({ details, setDetails, val }) => {
const [value, setValue] = useState({
save: true,
...val
});
const handleChange = (e) => {
setValue({ ...value, email: e.target.value });
};
const handleSave = () => {
setDetails({
...details,
data: { ...details.data, email: value.email }
});
console.log("Data",value);
};
const handleDelete = () => {
setDetails({ ...details, data: { ...details.data, email: "" } });
console.log("Data",value);
};
return (
<div className="cont">
<input type="text" value={value.email} onChange={handleChange} />
{value.save && <button onClick={handleSave}>save</button>}
<button onClick={handleDelete}>Delete</button>
</div>
);
};
Codesandbox Link:
https://codesandbox.io/s/testing-m3mc6?file=/src/child.jsx:0-801
N.B. I have googled for solution I saw one stackoverflow question also but that wasn't helpful for me as I am using functional way of react.
Any other method of accomplishing this would be appreciated.
Try this in child component:
useEffect(()=>{
setValue({
value,
...val
});
}, [val])
import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])
When I try to get reference on formik, I get null in current field of ref object.
const formikRef = useRef();
...
<Formik
innerRef={formikRef}
initialValues={{
title: '',
}}
onSubmit={(values) => console.log('submit')}
>
And next:
useEffect(() => {
console.log(formikRef);
}, []);
Get:
Object {
"current": Object {
"current": null,
},
}
How can I fix this problem?
Help please.
If you want to call submit function outside Formik, you can use useImperativeHandle. Document
// Children Component
const Form = forwardRef((props, ref) => {
const formik = useFormik({
initialValues,
validationSchema,
onSubmit,
...rest // other props
})
useImperativeHandle(ref, () => ({
...formik
}))
return ** Your form here **
})
and using:
// Parent Component
const Parent = () => {
const formRef = useRef(null)
const handleSubmitForm = (values, actions) => {
// handle logic submit form
}
const onSubmit = () => {
formRef.current.submitForm()
}
return (<>
<Form ref={formRef} onSubmit={handleSubmitForm} />
<button type="button" onClick={onSubmit}>Submit</button>
</>)
}
Read the ref only when it has value, and add the dependency in useEffect on the ref.
useEffect(() => {
if (formikRef.current) {
console.log(formikRef);
}
}, [formikRef]);
Remember, that refs handle it's actual value in .current property.
What worked for me was adding variables inside useEffect's [].
For my case, it was [ref.current, show].
Add an if(ref.current) {...} before any ref.current.setFieldValue in useEffect body as well or ref.current?.setFieldValue.
This cost me several hours, I hope you're better off.