React - api call returns undefineds - axios

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 ( /* ... */ );
};

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);

Why won't my Modal open on a successful api call

I designed a modal using Material UI that I am importing and passing props to. I'm trying to have the modal open in either the .then block or the .catch of the axios call. It opens in the .catch but not in the .then though I have other operations occurring without a problem in the .then block. Also all of the state is properly being set confirmed via console.log for the modal to open. If I remove the code from the .then block the modal opens fine.
This is just a snippet of the code. I removed all the other .then operations to make the code more simple. Any ideas would be appreciative.
import { ResponseModal } from "components/Modals/ApiResponse-Modal";
export const ImportDataProperties = ({ element }) => {
const [responseMessage, setResponseMessage] = useState("");
const [type, setType] = useState("");
const [modalOpen, setmodalOpen] = useState(false);
const handleClose = () => setmodalOpen(false);
const handleSave = () => {
axios
.post("dshservices/api/Dataset/UploadFile", 'example data')
.then((res) => {
const { dateCreated, id, databaseName, schemaName, tableName } = res.data.value;
setResponseMessage(`time of success: ${dateCreated}`);
setType("success");
setmodalOpen(true);
})
.catch((error) => {
setResponseMessage(
`time of error: ${new Date().toLocaleDateString() + " (CST)*"}, ${error}`
);
setType("error");
setmodalOpen(true);
});
};
return (
<Content>
<Button onClick={handleSave} disabled={disabled} variant="contained">
UPLOAD FILE
</Button>
<ResponseModal
message={responseMessage}
type={type}
handleClose={handleClose}
open={modalOpen}
/>
</Content>
);
};

React useEffect to obtain Autocomplete options (material UI) from 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.

How to access to the prop assigned component in enzyme

This is my component:
const Cmp = (props) => (
<List>
<ListItem primaryText='test' leftCheckbox={<Checkbox onCheck={props.onCheck} />} />
</List>
);
I want to test checking of the checkbox. Here is my test:
it('test', (done) => {
const handleCheck = () => {
done();
}
const wrapper = shallow(<Cmp onCheck={handleCheck} />);
wrapper.find('the checkbox').simulate('check');
});
How can I find the checkbox?
You need to find the ListItem element then get the prop and call simulate on it:
it('test', (done) => {
const handleCheck = () => {
done();
}
const wrapper = shallow(<Cmp onCheck={handleCheck} />);
const checkBox = shallow(wrapper.find('ListItem').first().prop('leftCheckbox')())
checkbox.simulate('check');
});