How can I update the state in a class component? - javascript

I have these codes to show all of the pending orders in a table. Once the button "confirmed" was clicked, the state will be updated where only the pending orders will be loaded. The only problem that I have is that, the state won't be updated not unless I'll go to another page and then I have to go back to the pending orders page to view the updated state.
class PendingOrders extends Component {
constructor() {
super();
this.state = { orders: [] };
}
columns = [
"Order ID",
{
name: "Confirm",
options: {
filter: true,
sort: false,
empty: true,
customBodyRender: (
value,
tableMeta,
) => {
return (
<FormControlLabel
value={value}
control={
<Button value={value} color="secondary" variant="primary">
confirm
</Button>
}
onClick={(e) => {
try {
firestore.collection("orders").doc(tableMeta.rowData[0]).set(
{
orderStatus: "Confirmed",
},
{ merge: true }
);
} catch (err) {
console.log(err);
}
}}
/>
);
},
},
},
componentDidMount() {
try {
firestore
.collection("orders")
.where("orderStatus", "==", "Pending")
.get()
.then((snapshot) => {
const orders = [];
snapshot.docs.map((doc) => {
const items = [];
orders.push({
const items = [];
doc.data().items.forEach((item) => {
items.push(`${item.productName}(${item.qty}),`);
});
const data = doc.data();
orders.push({
"Order ID": doc.id,
"Items":items,
});
});
this.setState({ orders: orders });
// console.log(this.state.orders);
});
} catch (err) {
console.log(err);
}
}
render() {
return (
<div>
<MUIDataTable
title={"Pending Orders"}
columns={this.columns}
data={this.state.orders}
options={this.options}
/>
</div>
);
}
}
I found the error already. Instead of using the .get() and .then(), I changed it into onSnapshot and it worked already.
componentDidMount() {
try {
firestore
.collection("orders")
.where("orderStatus", "==", "Pending")
.onSnapshot((snapshot) => {
const orders = [];
snapshot.docs.map((doc) => {
const items = [];
orders.push({
const items = [];
doc.data().items.forEach((item) => {
items.push(`${item.productName}(${item.qty}),`);
});
const data = doc.data();
orders.push({
"Order ID": doc.id,
"Items":items,
});
});
this.setState({ orders: orders });
// console.log(this.state.orders);
});
} catch (err) {
console.log(err);
}
}

You are not appending the data to your new order array. Your code:
snapshot.docs.map((doc) => {
const items = [];
const data = doc.data();
orders.push({
});
});
What should be:
snapshot.docs.map((doc) => {
const items = [];
const data = doc.data();
// Append data to the array, or the right keys if you don't want the whole data
orders.push(data);
});

You are suppose to update the state directly...
this.setState(prevState => {orders: [...prevState.orders, data]})

Related

React Native Firestore: How do I listen for database changes at the same time as using the .where() query?

I have made a FlatList that gets populated from a firestore database. I can currently do all the CRUD operations, but when I edit an entry, it doesn't change in the FlatList. It does change in the firestore database.
I suspect it's because I'm not using .onSnapshot(). My problem is that I need to filter the data using .where() and I haven't been able to find out how to combine the two operations.
My code looks like this:
export const Coach = () => {
const navigation = useNavigation();
const [user, setUser] = useState();
const [userName, setUserName] = useState('');
const [workoutIds, setWorkoutIds] = useState([]);
const [workouts, setWorkouts] = useState([]);
const userRef = firestore().collection('Users');
const workoutRef = firestore().collection('Workouts');
// Setting the user state
auth().onAuthStateChanged(userInstance => {
if (userInstance) {
setUser(userInstance);
}
});
// Getting coach id's from firestore - Started out at individual workout id's
useEffect(() => {
if (user) {
const subscriber = userRef.doc(user.uid).onSnapshot(userSnap => {
if (userSnap) {
setUserName(userSnap.data().Name);
setWorkoutIds(userSnap.data().Workouts);
}
});
return () => subscriber();
}
}, [user]);
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
The problem should lie in the last useEffect block.
So how do I get it to listen for changes and update the FlatList, while still using the .where()?
----------------------------------------- Edit -----------------------------------------
I have tried to add an onSnapshot to my query:
Before:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
// using the list of coach id's to get workouts
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
It still doesn't update the view straight away and now I get an error about encountering two of the same keys.
To solve the issue I had to add .onSnapshot() to my query for it to listen to changes in the database. On top of that I accidentally put the temporary list that I added objects to, outside the onSnapshot(), so it just kept adding on. After moving the temporary list into the onSnapshot(), it now updates.
Before:
useEffect(() => {
if (workoutIds.length != 0) {
let workoutList = [];
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.get()
.then(query => {
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);
After:
useEffect(() => {
if (workoutIds.length != 0) {
workoutRef
.where(firestore.FieldPath.documentId(), 'in', workoutIds)
.onSnapshot(query => {
let workoutList = [];
query.forEach(snap => {
workoutList.push({...snap.data(), key: snap.id});
});
setWorkouts(workoutList);
});
}
}, [workoutIds]);

Why can't I access data after fetching?

I'm trying to keep session stayed logged in after refreshing the browser. The user data that is being fetched is not rendering after being fetched. The console is saying "Cannot read properties of undefined (reading 'user'). This is my code for the login/sign up page.
The data I'm trying to access is in the picture below:
(Auth.js)
const Auth = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [isSignup, setIsSignup] = useState(false);
const [inputs, setInputs] = useState({
name: "",
username: "",
email: "",
password: ""
})
const handleChange = (e) => {
setInputs(prevState => {
return {
...prevState,
[e.target.name]: e.target.value
}
})
}
const sendRequest = async (type = '') => {
const res = await axios.post(`/user/${type}`, {
name: inputs.name,
email: inputs.email,
username: inputs.username,
password: inputs.password,
}).catch(error => console.log(error))
const data = await res.data;
console.log(data)
return data;
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(inputs)
if (isSignup) {
sendRequest("signup")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
} else {
sendRequest("login")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
}
}
Redux store file
const authSlice = createSlice({
name: "auth",
initialState: { isLoggedIn: false },
reducers: {
login(state) {
state.isLoggedIn = true
},
logout(state) {
state.isLoggedIn = false
}
}
})
export const authActions = authSlice.actions
export const store = configureStore({
reducer: authSlice.reducer
})
Chaining promises using .then() passes the resolved value from one to the next. With this code...
sendRequest("...")
.then(() => dispatch(authActions.login()))
.then(() => navigate("/posts"))
.then(data => localStorage.setItem('token', data.user))
You're passing the returned / resolved value from navigate("/posts") to the next .then() callback. The navigate() function returns void therefore data will be undefined.
Also, your redux action doesn't return the user so you can't chain from that either.
To access the user data, you need to return it from sendRequest()...
const sendRequest = async (type = "") => {
try {
const { data } = await axios.post(`/user/${type}`, { ...inputs });
console.log("sendRequest", type, data);
return data;
} catch (err) {
console.error("sendRequest", type, err.toJSON());
throw new Error(`sendRequest(${type}) failed`);
}
};
After that, all you really need is this...
sendRequest("...")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
Since you're using redux, I would highly recommend moving the localStorage part out of your component and into your store as a side-effect.

React - State is not updating when it is supposed to, why is react doing this? (not retaining)

Hi so I'm trying to grab some json from an api and then populate a table, pretty simple stuff.
What's happening is that I can see the "tableData" state being updated as each new row comes in, I'm also logging every time "tableData" is updated, yet maybe .5 seconds after its all done my "tableData" is empty again (check console screenshots)
const [bigChartData, setbigChartData] = React.useState("data1");
const [tableData, setTableData] = React.useState([]);
const setBgChartData = (name) => {
setbigChartData(name);
};
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.forEach(item => {
setTableData(oldData => [...oldData, {
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
}]);
});
})
.catch(err => console.log(err));
setTimeout(function () {
console.log(tableData);
}, 3000);
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
console.log("Table data updated:");
console.log(tableData);
}, [tableData]);
I think you should not iterate through each row inside getData() method instead try following code
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.map(item => {
return{
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
};
});
setTableData(data)
}).catch(err => console.log(err));
}
or if you have already some data in tableData then
setTableData([...tableData, data])

Check Box Update Request,ReactJS

I'm making simple To Do List app,Everything is working.I just want to make sure I'm doing it right without any mistakes.
I'm concerned about Check box update part,Please check the code and tell me if I'm doing anything wrong.
Here is the put method for Checkboxes
checkBoxRouteUpdate = () => {
let {todos} = this.state
let newArray = [...todos]
axios
.put(`http://localhost:8080/checkEdit/`, {
checked: newArray.every(todo => todo.checked)
}).then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
checking all of them
checkAllCheckBox = () => {
let {todos} = this.state
let newArray = [...todos]
if (newArray.length !==0) {
newArray.map(item => {
if (item.checked === true) {
return item.checked = false
} else {
return item.checked = true
}
})
this.checkBoxRouteUpdate()
this.setState({todos: newArray})
}
}
Checking single Check Box
checkSingleCheckBox = (id) => {
let {todos} = this.state
let newArray = [...todos]
newArray.forEach(item => {
if (item._id === id) {
item.checked = !item.checked
axios
.put(`http://localhost:8080/edit/${id}`,{
checked:item.checked
})
.then(res => {
this.setState({todos: newArray})
console.log('res',res)
})
.catch((err) => {
console.log("err", err);
});
} else {
}
})
}
Deleting Only Checked Items
deleteAllChecked = () => {
const todos = this.state.todos.filter((item => item.checked !== true))
axios
.delete('http://localhost:8080/deleteAllChecked')
.then((res) => {
this.setState({ todos,
pageCount: Math.ceil(todos.length / 10)})
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
You can check/uncheck them another way
this.checkBoxRouteUpdate()
this.setState(state => ({
...state,
todos: state.todos.map(todo => ({
...todo,
checked: !item.checked
}))
}))
I think you should delete after api returns ok status
.then((res) => {
this.setState(state => {
const todos = state.todos.filter((item => item.checked !== true));
return {
...state,
todos,
pageCount: Math.ceil(todos.length / 10)
}
})
I add a lot of comments, some of these some just another way to do what you do and others are personal preferences, but the most important is that you can see alternatives ways to do things :).
checkBoxRouteUpdate = () => {
const todos = [...this.state.todos] // Better use const and initialize the array of objects directly
/*since you will use this array just in one place, is better if you iterate in
the [...todos] directly without save it in a variable
let newArray = [...todos]
*/
axios
.put(`http://localhost:8080/checkEdit/`, {
checked: todos.every(({checked}) => checked) // here you can use destructuring to get checked
}).then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
```
checking all of them
```
checkAllCheckBox = () => {
const todos = [...this.state.todos] // Better use const and initialize the array of objects directly
// let newArray = [...todos] same as in the first function,
// isn't neccesary this if because if the array is empty, the map doesn't will iterate
// if (newArray.length !==0) {
/* this is optional, but you can write this like
const modifiedTodos = [...todos].map(({checked}) => checked = !checked)
*/
/* In general, is better use const when possible because in this way
you will reassign a variable just when is necessary, and this is related with
avoid mutate values. */
const modifiedTodos = todos.map(item => {
if (item.checked === true) {
return item.checked = false
} else {
return item.checked = true
}
})
this.checkBoxRouteUpdate()
this.setState({ todos: modifiedTodos })
}
// Checking single Check Box
checkSingleCheckBox = (id) => {
// since you need be secure that the todos is an array, you can do this instead of the destructuring
const todos = [...this.state.todos]
// same as in the above function
// let newArray = [...todos]
// Here is better to use destructuring to get the _id and checked
[...todos].forEach(({checked, _id}) => {
/* this is totally personal preference but I try to avoid put a lot of code inside an if,
to do this, you can do something like:
if(_id !== id) return
and your code doesn't need to be inside the if
*/
if (_id === id) {
/* this mutation is a little difficult to follow in large codebase, so,
is better if you modified the value in the place you will use it*/
// checked = !item.checked
axios
.put(`http://localhost:8080/edit/${id}`, {
checked: !checked
})
.then(res => {
this.setState({ todos: todos }) // or just {todos} if you use the object shorthand notation
console.log('res', res)
})
.catch((err) => {
console.log("err", err);
});
}
// this else isn't necessary
// else {
// }
})
}
// Deleting Only Checked Items
deleteAllChecked = () => {
const todos = this.state.todos.filter((item => item.checked !== true))
/* Another way to do the above filtering is:
const todos = this.state.todos.filter((item => !item.checked))
*/
axios
.delete('http://localhost:8080/deleteAllChecked')
.then((res) => {
this.setState({
todos,
pageCount: Math.ceil(todos.length / 10)
})
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}

storing persistent object in asyncStorage

I do not know if this is possible, but I am trying to store persistent data in async storage, so whenever the array data in the storage key is fetched, that persistent data is always at the top of the array. Also, persistent data cannot be deleted. below is a sample code, and I have shown only the functions for better clarity.
componentWillMount() {
this.saveData();
}
componentDidMount() {
this.getDataSync();
}
getDataSync = async () => {
try {
const list = await AsyncStorage.getItem(LIST_STORAGE_KEY);
const parsedList = JSON.parse(list);
this.setState({
isDataReady: true,
list: parsedList || []
});
console.log(parsedList, this.state.list);
} catch (e) {
Alert.alert('Failed to load list.');
}
}
saveData = () => {
const data = [
{
firstname: 'John',
lastname: 'Doe',
image: null,
email: 'john#doe.com',
key: 0,
phone: '19191919191',
},
];
this.setState(
prevState => ({
list: [data, ...prevState.list]
}),
() => this.saveItems(this.state.list)
);
}
handleDelete(item) {
if (item.key === 0) {
Alert.alert('You cannot delete this user');
} else {
this.setState(({ list }) => ({
list: list.filter(o => o.key !== item.key)
}),
() => this.saveItems(this.state.list)
);
console.log('deleted: ', item);
}
}
handleAdd() {
const { firstname, lastname, email, phone } = this.state;
const ID = uuid();
const newItemObject = {
key: ID,
firstname: firstname,
lastname: lastname,
email: email,
phone: phone,
image: null,
};
this.setState(
prevState => ({
list: [...prevState.list, newItemObject]
}),
() => this.saveItems(this.state.list)
);
}
saveItems = list => {
AsyncStorage.setItem(LIST_STORAGE_KEY, JSON.stringify(list));
};
Add another property to the objects, you don't want to be deleted. Asyncstorage is persistent by default.
{
firstname: 'John',
lastname: 'Doe',
image: null,
email: 'john#doe.com',
key: 0,
phone: '19191919191',
delete: false, // won't be deleted
},
In your getDataSync sort the list by delete property.
getDataSync = async () => {
try {
const list = await AsyncStorage.getItem(LIST_STORAGE_KEY);
const parsedList = JSON.parse(list);
// items with .delete property set to false, will be at the top of the list
parsedList.sort((x, y) => (x.delete === y.delete ) ? 0 : x ? 1 : -1);
this.setState({
isDataReady: true,
list: parsedList || []
});
console.log(parsedList, this.state.list);
} catch (e) {
Alert.alert('Failed to load list.');
}
}

Categories