React useEffect to obtain Autocomplete options (material UI) from Cloud Firestore - google-cloud-firestore

I'm trying to use react useEffect hook to get data from firestore and give it to the options attribute on a Material UI autocomplete select menu.
I have a collection in my firestore called "organisations". That document has an attribute called "shortName".
I'm trying to get the data from the collection and then use it to set the state on a property called orgList, which I can then use in the in the select menu.
This is what I'm trying.
import React, { useState, useEffect } from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '#material-ui/icons/CheckBox';
import firebase from "../../../../../firebase";
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export default function CheckboxesTags() {
const [orgList, setOrgList] = useState();
const [selectedOrgList, setSelectedOrgList] = useState();
useEffect(() => {
firebase
.firestore()
.collection("organisations")
.onSnapshot(snapshot => {
const orgList = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setOrgList(orgList)
})
}, [orgList])
return (
<div>
<Autocomplete
multiple
id="checkboxes-tags-demo"
options={orgList}
disableCloseOnSelect
getOptionLabel={(option) => option.shortName}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.shortName}
</React.Fragment>
)}
style={{ width: 500 }}
renderInput={(params) => (
<TextField {...params}
variant="outlined"
label="Select Organisation"
placeholder="Acme Inc"
/>
)}
/>
</div>
);
}
The error message I'm getting says:
TypeError: Cannot read property 'shortName' of undefined
NEXT ATTEMPT
Using the suggestion from gdh below, this is the next attempt.
import React, { useState, useEffect } from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '#material-ui/icons/CheckBox';
import firebase from "../../../../../firebase";
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export default function CheckboxesTags() {
const [orgList, setOrgList] = useState([]);
const [selectedOrgList, setSelectedOrgList] = useState();
const [loading, setLoading ] = useState(true);
const [ error, setError ] = useState(false);
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("organisations")
.onSnapshot((snapshot) => {
const orgList = snapshot.docs.map((doc) => ({
id: doc.id,
shortName: doc.shortName
}));
console.log(orgList)
setOrgList(orgList);
}, () => {
setError(true)
});
setLoading(false);
return() => unsubscribe();
}, [orgList]);
useEffect(() => {
firebase
.firestore()
.collection("organisations")
.get()
.then((snapshot) => {
const orgList = snapshot.docs.map((doc) => ({
id: doc.id,
shortName: doc.shortName
}));
setOrgList(orgList);
});
}, []);
return (
<div>
<Autocomplete
multiple
id="checkboxes-tags-demo"
options={orgList}
disableCloseOnSelect
getOptionLabel={(orgList) => orgList.shortName}
renderOption={(orgList, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{orgList.shortName}
</React.Fragment>
)}
style={{ width: 500 }}
renderInput={(params) => (
<TextField {...params}
variant="outlined"
label="Select Organisation"
placeholder="Acme Inc."
/>
)}
/>
</div>
);
}
The console log prints both orgList ids, but the shortName is undefined.
I get an error that says:
TypeError: Cannot read property 'toLowerCase' of undefined .
In an attempt to solve this error, I added:
ignoreCase = {false}
to the Autocomplete head tag, but the same error persists. The console logs an error that says:
Warning: React does not recognize the ignoreCase prop on a DOM
element. If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase ignorecase instead. If you
accidentally passed it from a parent component, remove it from the DOM
element
I tried renaming shortName in firestore to 'short' to see if I could avoid the case sensitivity issue, but the same error persists (console logs short as undefined when it has a value in the firestore console).
I know the form is reading from firestore because when I try setting the option as the id of the document, the form loads and the id prints as the value.

you don't have initial values for orgList. Provide a blank array.
you are only registering onSnapshot callback which only executes when organisations collection is changed but you are not fetching any data on mount. This means that your autocomplete will only have values when someone make change in organisation collection. So maintain another useEffect and fetch the data.
refactored code
export default function CheckboxesTags() {
const [orgList, setOrgList] = useState([]);
const [selectedOrgList, setSelectedOrgList] = useState();
useEffect(() => {
firebase
.firestore()
.collection("organisations")
.onSnapshot((snapshot) => {
const orgList = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setOrgList(orgList);
});
}, []);
useEffect(() => {
firebase
.firestore()
.collection("organisations")
.get()
.then((snapshot) => {
const orgList = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setOrgList(orgList);
});
}, []);
//.... rest of code...

I found what worked - eventually. There seems to be an issue limiting reading single attribute from the firebase document. In my snapshot, I was trying to read: doc.shortName / doc.short. It needs to be:
shortName: doc.data().shortName,
I didn't end up needing the second .get useEffect as proposed by gdh.
Here is what worked for me:
import React, { useState, useEffect } from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '#material-ui/icons/CheckBox';
import firebase from "../../../../../firebase";
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export default function CheckboxesTags() {
const [orgList, setOrgList] = useState([]);
const [selectedOrgList, setSelectedOrgList] = useState();
const [loading, setLoading ] = useState(true);
const [ error, setError ] = useState(false);
useEffect(() => {
// if (doc.exists) {
const unsubscribe = firebase
.firestore()
.collection("organisations")
.onSnapshot((snapshot) => {
const orgList = snapshot.docs.map((doc) => ({
id: doc.id,
shortName: doc.data().shortName
}));
console.log(orgList)
setOrgList(orgList);
}, () => {
setError(true)
});
setLoading(false);
return() => unsubscribe();
}, [orgList]);
// useEffect(() => {
// firebase
// .firestore()
// .collection("organisations")
// .get()
// .then((snapshot) => {
// const orgList = snapshot.docs.map((doc) => ({
// id: doc.id,
// ...doc.data()
// }));
// setOrgList(orgList);
// });
// }, []);
return (
<div>
<Autocomplete
multiple
id="checkboxes-tags-demo"
options={orgList}
disableCloseOnSelect
getOptionLabel={(option) => option.shortName}
renderOption={(orgList, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{orgList.shortName}
</React.Fragment>
)}
style={{ width: 500 }}
renderInput={(params) => (
<TextField {...params}
variant="outlined"
label="Select Organisation"
placeholder="Acme Inc."
/>
)}
/>
</div>
);
}
As for the toLowerCase error - there is a filterOptions attribute that can be used on Autocomplete. I couldn't figure out how to get that working - but for now, that's a problem for another day.

Related

How to stop getting an undefined value when changing a select value using useState and useEffect

I have the following code in my first component, where I fetch some data from which I need to pull an array of strings and send it as values to my Material UI select component:
const Logs = () => {
const [data, setData] = useState({})
const [usersList, setUsersList] = useState([])
const [user, setUser] = useState(null)
const getData = async () => {
try {
const { data } = await axios.request({
method: "GET",
baseURL: 'some url',
url: `/logger`,
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
setData(data);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
( async () => {
await getData();
})()
}, []);
const isEmpty = obj => Object.keys(obj).length === 0;
useEffect(() => {
if(isEmpty(data) === false) {
setUsersList(data.userNames)
}
console.log('Data', data) //{userNames: ['Admin', 'User1', 'User2']
})
useEffect(() => {
setUser(usersList[0]) //setting the user to Admin
})
const handleUserChange = (event) => {
const value = event.target.value;
setUser(value);
};
return (
<div>
<CssBaseline />
{<LogsSelect
labelId="logs-user-select-label"
selectId="logs-user-select"
handleChange={handleUserChange}
value={user}
list={usersList}
label='User'
/>
}
)
}
My select is defined as a separate (generic) component:
const LogsSelect = (labelId, selectId, handleChange, value, list, label) => {
return (
<FormControl
className={classes.form}
style={{ minWidth: "140px"}}
>
<InputLabel id={labelId}>{label}</InputLabel>
<Select
className={classes.select}
labelId={labelId}
id={selectId}
onChange={handleChange}
value={value}
>
{value && list?.map((el, index) => (
<MenuItem value={el} key={index}>
{el}
</MenuItem>
))}
</Select>
</FormControl>
)}
I get the following error:
Material-UI: You have provided an out-of-range value undefined for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are "". This is happening when rendering the components. I haven't even managed to get my list of values as options in my select, and then try and change the value.
Pass defaultValue="" to the <Select />
...
<Select
defaultValue=""
>
...
</Select>
...

access RTK-query data in createSlice action

what woud be the correct way to access RTK-query data inside createSlice reducers?
Like, for example 'select all' functionality in the code below.
is it possible to access current useGetOrdersQuery() data inside toggleSelectAll() action?
or the only/best way to implement 'select/deselect all' would be to pass useGetOrdersQuery() data to toggleSelectAll() action as action payload?
dashboardSlice.js
const initialState = {
selectedIds: [],
};
export const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
toggleSelectAll: (state, action) => {
//get "useGetOrdersQuery" data id's and assign to state.selectedIds
}
}
});
export const { toggleSelectAll } = dashboardSlice.actions;
export const selectSelectedIds = state => state.dashboard.selectedIds;
orders-lsit.js
import { useGetOrdersQuery } from './api'
import { toggleSelectAll, selectSelectedIds } from './dashboardSlice';
const OrdersList = () => {
const {data} = useGetOrdersQuery(123);
const dispatch = useDispatch();
const selectedIds = useSelector(selectSelectedIds);
return (
<div>
<button onClick={() => dispatch(toggleSelectAll())}>
select/deselect all
</button>
{data.map(o => (
<div>
<h2>{o.name}</h2>
<input
type="checkbox"
checked={selectedIds.includes(o.id)}
/>
</div>
))}
</div>
)
}
api.js
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getOrders: builder.query({
query: (userId) => `${userId}/orders`,
providesTags: [{ type: 'Orders', id: 'LIST' }]
})
})
});
export const { useGetOrdersQuery } = api;
if you just want to save the ids you can do this :
const initialState = {
selectedIds: [],
};
export const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
toggleSelectAll: (state, action) => {
//get "useGetOrdersQuery" data id's and assign to state.selectedIds
const { ids } = action.payload;
state.selectedIds = ids;
return state;
}
}
});
export const { toggleSelectAll } = dashboardSlice.actions;
export const selectSelectedIds = state => state.dashboard.selectedIds;
and
import { useGetOrdersQuery } from './api'
import { toggleSelectAll, selectSelectedIds } from './dashboardSlice';
const OrdersList = () => {
const {data} = useGetOrdersQuery(123);
const dispatch = useDispatch();
const selectedIds = useSelector(selectSelectedIds);
// you can pass the list of ids as a payload to your action and store it for later use.
return (
<div>
<button onClick={() => dispatch(toggleSelectAll({ids: data.map(item => item.id}))}>
select/deselect all
</button>
{data.map(o => (
<div>
<h2>{o.name}</h2>
<input
type="checkbox"
checked={selectedIds.includes(o.id)}
/>
</div>
))}
</div>
)
}
you have something like this too:
check out this link
const store = useStore();
const allDataOfAllQueries = store.getState().api.queries;
console.log(Object.values(allDataOfAllQueries);

may i use this code for finduserDetail in react using redux-thunk dispatch to calling function

This code for finduserdetail by hitting function with help of dispatch method and passing (match.params.id) not working, even function not called as I know on dispatch method it should be called how may I call this so that our stat could be update in root.js file then I can show detail on my ui by using useselector
in productDetail.js
import { Fragment } from "react";
import Carousel from "react-material-ui-carousel";
import {useParams} from "react-router-dom";
import './ProductDetails.css';
import {useDispatch} from "react-redux";
import { useEffect } from "react";
import { getProductDetail } from "../../actions/productAction";
const ProductDetails = () =>{
console.log("hello this is detail")
const dispatch= useDispatch();
let params = useParams()
const id= params.id;
// const {product,error} = useSelector(
// (state) =>state.product)
useEffect(()=>{
dispatch(getProductDetail(id))
}, [dispatch, id])
return(
<Fragment>
<div className="ProductDetails">
<div>
<Carousel>
{/* {product.images &&
product.images.map((item,i)=>{
<img
className="CarouselImage"
key={item.url}
src={item.url}
alt={`${i} Slide`}
/>
})} */}
</Carousel>
</div>
</div>
</Fragment>
);
};
export default ProductDetails
and using for calling api for findproductDetail using getProductDetail in productAction.js
export const getProductDetail =(id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILES_REQUEST });
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: PRODUCT_DETAILES_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILES_FAIL,
payload: error.response.data.message,
});
}
};
and one other file productReducer.js
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILES_REQUEST:
return {
loading: true,
...state,
};
case PRODUCT_DETAILES_SUCCESS:
return {
loading: false,
product: action.payload.product,
};
case PRODUCT_DETAILES_FAIL:
return {
loading: false,
error: action.payload,
};
and another file store.js
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { productDetailsReducer, productReducer } from './reducers/productReducer';
const reducer = combineReducers({
products: productReducer,
product: productDetailsReducer,
});
let initialState ={};
const middleware = [thunk];
const store = createStore(reducer, initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
These stuff not working. I am quite beginner in React programming
import { Fragment } from "react";
import Carousel from "react-material-ui-carousel";
import {useParams} from "react-router-dom";
import './ProductDetails.css';
import {useDispatch} from "react-redux";
import { useEffect } from "react";
import { getProductDetail } from "../../actions/productAction";
const ProductDetails = () =>{
console.log("hello this is detail")
const dispatch= useDispatch();
let params = useParams()
const id= params.id;
// const {product,error} = useSelector(
// (state) =>state.product)
useEffect(()=>{
dispatch(getProductDetail(id))
}, [dispatch, id])
return(
<Fragment>
<div className="ProductDetails">
<div>
<Carousel>
{/* {product.images &&
product.images.map((item,i)=>{
<img
className="CarouselImage"
key={item.url}
src={item.url}
alt={`${i} Slide`}
/>
})} */}
</Carousel>
</div>
</div>
</Fragment>
);
};
export default ProductDetails
and using for calling api for findproductDetail using getProductDetail in productAction.js
export const getProductDetail =(id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILES_REQUEST });
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: PRODUCT_DETAILES_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILES_FAIL,
payload: error.response.data.message,
});
}
};
and one other file productReducer.js
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILES_REQUEST:
return {
loading: true,
...state,
};
case PRODUCT_DETAILES_SUCCESS:
return {
loading: false,
product: action.payload.product,
};
case PRODUCT_DETAILES_FAIL:
return {
loading: false,
error: action.payload,
};
and another file store.js
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { productDetailsReducer, productReducer } from './reducers/productReducer';
const reducer = combineReducers({
products: productReducer,
product: productDetailsReducer,
});
let initialState ={};
const middleware = [thunk];
const store = createStore(reducer, initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
The most important stuff my redux state is not updating because of id matching condition once if
state will get update I am checking using redux tool

How to resolve Error " Too many re-renders. React limits the number of renders to prevent an infinite loop." in react native?

I have develop form to submit complaint by selecting product prom picker. So I externally implement the picker and then use it in complaint submission form. For that I was needed to take selectedValue state into complaint submission form by using function handle change. After i implement that faction and change appropriate places regarding to the tutorial link : [https://github.com/reactjs/reactjs.org/issues/1689][1]
But when I navigate to the complaint submission form it says the error as " Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."So help me to resove and please confirm am I follow the above tutorial correctly.
Complaint submission form:
import * as React from 'react';
import {Button, View, Text, ScrollView, StyleSheet, Alert} from 'react-native';
import {Appbar} from 'react-native-paper';
import {TextInput, HelperText} from 'react-native-paper';
import {useEffect, useState} from 'react';
import AsyncStorage from '#react-native-community/async-storage';
import ProductPicker from './ProductPicker';
const ComplaintSubmission = ({navigation}) => {
const [productID , setproductID] = useState('');
const [description , setdescription] = useState('');
const [token, setToken] = useState('');
useEffect(() => {
saveToken();
}, []);
function handleChange(newValue){
setproductID(newValue);
}
const saveToken = async () => {
const token = await AsyncStorage.getItem('userToken');
console.log('token from storage', token);
setToken(token);
}
const send = () =>{
fetch("http://10.0.2.2:3000/customer/lodge-complaint", {
method: "post",
headers: {
'Content-Type': 'application/json',
'Authentication': `Bearer ${token}`
},
body: JSON.stringify({
description : description,
productID : value
})
})
}
const openAlert = () => {
Alert.alert(
"Complaint Successfully Submitted",
"We review it as soon as possible. Thank you for reaching for us!",
[{
text: "OK",
onPress : () => navigation.navigate("DashboardDrawer" ),
}]
);
}
return (
<ScrollView>
<Appbar.Header>
<Appbar.BackAction onPress={() => navigation.goBack()} />
<Appbar.Content title="Submit Complaint" />
<Appbar.Action icon="magnify" onPress={() => navigation.openDrawer()} />
</Appbar.Header>
<Text>Plese Fill the following</Text>
<View>
{/*{console.log('renderer token', token)}*/}
<ProductPicker value={productID} onValueChange = {handleChange()} />
<HelperText type="info">
Make sure select the correct Product
</HelperText>
</View>
<TextInput
style={styles.PIDstyle}
label="Description"
onChangeText = {(description) => setdescription(description)}
/>
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>This is submittion</Text>
<Button onPress={() => {send(); openAlert();}} title="Submit Complaint" />
</View>
</ScrollView>
);
};
export default ComplaintSubmission;
const styles = StyleSheet.create({
PIDstyle: {
marginTop: 30,
marginLeft: 10,
marginRight: 10,
},
});
And this is the picker component:
import React, {useEffect, useState} from 'react';
import {View, StyleSheet} from 'react-native';
import {Picker} from '#react-native-picker/picker';
import AsyncStorage from '#react-native-community/async-storage';
const ProductPicker = () => {
const [selectedValue, setSelectedValue] = useState('');
const [productDetails, setproductDetails] = useState([]);
console.log('product id---', selectedValue);
useEffect(() => {
getProductList();
}, []);
function handleChange(event){
props.onValueChange(event.target.value);
}
const getProductList = async () => {
const token = await AsyncStorage.getItem('userToken');
console.log(' function eka athule------', token);
fetch('http://10.0.2.2:3000/customer/get-all-products', {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Authentication': `Bearer ${token}`,
},
})
.then((response) => response.json())
.then((json) => setproductDetails(json.data))
.catch((error) => console.error(error));
};
return (
<View style={styles.container}>
<Picker
selectedValue={selectedValue}
style={{height: 40, width: 150}}
onValueChange={(itemValue, itemIndex) => {
setSelectedValue(itemValue);
handleChange();
}}
>
{productDetails.map((item, index) => {
return (
<Picker.Item label={item.productName} value={props.item.productID} key={index}/>);
})}
</Picker>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 40,
alignItems: 'center',
},
});
export default ProductPicker;
Why don't you pass the itemValue to handleChange?
onValueChange={(itemValue, itemIndex) => {
setSelectedValue(itemValue);
handleChange(itemValue);
}}
Then change the function:
function handleChange(value){
props.onValueChange(value);
}

React - api call returns undefineds

const [loading, setLoading] = useState(false);
const [meal, setMeal] = useState([]);
useEffect(() => {
setLoading(true);
async function fetchData() {
const response = await axios.get('/random.php');
setMeal(response.data.meals);
setLoading(false);
return response;
}
fetchData();
}, []);
return (
<Card className={classes.Root}>
<CardActionArea>
<CardMedia className={classes.Media} title="food">
<img src={meal[0].strMealThumb} alt="cat" />
</CardMedia>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{meal[0].strMeal}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{meal[0].strInstructions}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
);
I'm using the coed above but I get an error TypeError: Cannot read property 'strMealThumb' of undefined. I tried multiple ways with useEffect and still have the same issue
When your component renders for the first time, the network request to /random.php hasn't happened yet, so meal is set to its default value ([]). You're attempting to set the image source to meal[0].strMealThumb, but meal is an empty array, so meal[0] returns undefined, and you see the error you posted.
The solution is to prevent the component body from rendering while meal is loading, and when meal is an empty array (in case /random.php returns an empty array).
meal is an array, so you should name it meals instead.
const MyComponent = () => {
const [loading, setLoading] = useState(false);
const [meals, setMeals] = useState([]);
if (loading) {
return (
<div>Loading...</div>
);
}
if (meals.length === 0) {
return (
<div>No meals found!</div>
);
}
return ( /* ... */ );
};