Rendering part of a React function component after async call gets completed - javascript

I'm using material-ui with a React function component and using its Autocomplete component. I customized it and whenever I change the text in input field, I expect the component to render new search result.
callAPI("xyz")
I'm calling the API in the action and using the xyz parameter, I'm calling the dispatch method from this function component.
Problem here is, when the component makes the call, it is supposed to wait for the API response and then render the result, but it gets an unresolved promise, so it fails rendering.
<Paper square>
{callAPI("xyz").results.map(
result => console.log(result);
)}
</Paper>
as results are a unresolved promise, it will fail to map.
I need some way to call the map only once data is available, or show some text before data is there and then change once data is fetched.
Any suggestions to correct this code will be very helpful.
EDIT:
function IntegrationDownshift() {
return (
<div>
<Downshift id="downshift-simple">
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
selectedItem
}) => (
<div>
{renderInput({
fullWidth: true,
InputProps: getInputProps({
placeholder: "Search users with id"
})
})}
<div {...getMenuProps()}>
{isOpen ?
<Paper square>
{callAPI(inputValue).users.map(
(suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({
item:
suggestion.userName
}),
highlightedIndex,
selectedItem
})
)}
</Paper>
: null}
</div>
</div>
)}
</Downshift>
</div>
);
}

React 16.8 introduces Hooks:
Hooks are functions that let you “hook into” React state and lifecycle
features from function components.
so you have useState() which you can declare a state variable with an empty array and call your API in the useEffect() to populate the state when you get the response from the API:
function App() {
const [data, setData] = useState([]);
useEffect(() => {
callAPI("xyz").then(result => {
setData(result);
})
}, []);
if(!data.length) return (<span>loading...</span>);
return (
<Paper square>
{data.map(
result => console.log(result);
)}
</Paper>
);
}
More about hooks: https://reactjs.org/docs/hooks-intro.html.

Easiest way to handle this is with a ternanry expression
And also its best practice to call your API request in a lifecycle method and then save the result in local state.
componentDidMount() {
callAPI("xyz").results.map(
result => this.setState(result);
}
<Paper square>
{this.state.results ?
this.state.results.map(
result => console.log(result);
: <p> Loading... </p>
)}
</Paper>

Related

React-Native: cannot update a component while rendering a different component

I've got this simple component Login:
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}
function getFormValues(values: any) {
setCredentialState(values);
}
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
Which calls UserCredentialForm:
export default function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
}
While navigating in my app I've got this error:
react native cannot update a component Login while rendering a
different component Formik.
Then it points me to the error in the setCredentialState inside getFormValues handler in Login component.
I've resolved this using a ref instead of a state, but the problem itself is unsolved to me.
What if I need to update my parent component view after a child event?
The reason for that error is because you call setState inside render(). The call getFormValues(values), which set the state of credentialState is called inside the render.
When the state is set, the Login component get rerendered, thus recreating a new function of getFormValues. As this is used as the prop of UserCredentialForm, it also causes that component to rerender, which causes the render prop inside Formik to calls again, which calls getFormValues causing the state change, causing an infinite loop.
One solution you can try is to add useCallback to the two functions, which prevent them to have new identities after the state changes and consequently change the props, thus creating infinite rerender.
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
const getFormErrors = useCallback(function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}, []);
const getFormValues = useCallback(function getFormValues(values: any) {
setCredentialState(values);
}, []);
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
However, there is still an issue and that is the identity of values may not be stable and by setting it to state, it will keep causing rerender. What you want to do is to tell UserCredentialForm not to rerender even when that state changes, and since the state is not used as a prop in UserCredentialForm, you can do that with React.memo.
export default React.memo(function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
})
I think you got an unlimited loop of rendering,
you setState by getFormValues and the Login component re-render make UserCredentialForm re-render too, so it call getFormValues again and again
You can call getFormValues(values) in a useEffect hook after values of formik update
You are calling getFormValues and getFormErrors inside a callback provided by Formik, that means you cannot wrap them inside an effect hook to suppress this warning or it will violate rules of hooks.
I faced the same issue in React JS and got rid of it by using the following: approach.
I used useFormik hook as an alternate.
and afterwards I refactored Formik form into a new component from where I made state changes to parent component.
This way I neither violated rules of hooks nor got this warning.
Also in the that newly refactored component you might need useFormikContext and useField
Simple example can be like
UserCredentialForm:
// Formik x React Native example
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { Formik } from 'formik';
export const MyReactNativeForm = ({onSubmit}) => (
<Formik
initialValues={{ email: '' }}
onSubmit={values => onSubmit(values)}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
);
Usage like
function Login() {
function doAction(values) {
console.log(values);
//credentialState rest call...
}
return (
<View>
....
<UserCredentialForm onSubmit={doAction} />
....
</View>
);
}

How to map an array inside an array in UseEffect

Below image will be the response I get from useEffect
My useEffect will look like this:
useEffect(() => {
const getStatesData = async () => {
await fetch ('http://localhost:3001/covid19')
.then((response) => response.json())
.then((data) => {
const dataStates = data.states
const states = dataStates.map((state2) => (
{
name: state2.name,
total: state2.total
}
));
setStates(states)
})
}
getStatesData();
}, []);
I'm trying to get name and total inside districts, how can I do that?
I've tried using adding districts: state2.districts in useEffect, and adding {state.districts.map(({name, total}) => ({name} ))} directly in the <div> but it's returning an error: Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
I'm currently using material-ui to help me get thigs easier, here is my full code excluding the useEffect:
<div className="App">
{states.map(state => (
<Accordion expanded={expanded === state.name}
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={classes.heading}>{state.name} ({state.total}) </Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
!! DISTRICTS NAME HERE (DISTRICTS TOTAL HERE) !!
</Typography>
</AccordionDetails>
</Accordion>
))}
</div>
Instead of mapping districts like this:
{state.districts.map(({name, total}) => ({name} ))}
do it like this:
{state.districts.map(({name, total}) => `${name} (${total})`)}
or even better to a component like this:
{state.districts.map(({name, total}) => <div>{name} ({total})<div>)}
within the Typography component.
The reason being, the mapping state.districts.map(({name, total}) => ({name} )) returns an object for each district, and React doesn't recognizes objects as renderable components.

Delete a component always returning the last item of the array

I'm trying to delete a component receiving your id as param for my function deleteCard(). This component is wraped in an array, but at trigger the function this always deletes the last item of the array. I'm using map to render my array of cards.
Please check the code in my GitHub. I'm using react Modal for control the cards rendering and the deleteCard function is invoked from an external file:
https://github.com/pablolucio97/places-to-know
Card compoenent:
const Card = ({
id,
local,
countryName,
countryFlag,
goalDate,
openModalDelete,
openModalEdit
} : countryCardTypes) => {
return (
<CardContainer key={id}>
<TopContainer>
<CountryInfoContainer>
<ImageFlag src={countryFlag} />
<CountryTitle>{countryName}</CountryTitle>
</CountryInfoContainer>
<ButtonsContainer>
<EditButton onClick={openModalEdit}>
<MdEdit size={18} color='#333'/>
</EditButton>
<CloseButton onClick={openModalDelete}>
<MdClose size={18} color='#333'/>
</CloseButton>
</ButtonsContainer>
</TopContainer>
<Divider />
<BottomContainer>
<Text>Local: {local}</Text>
<Text>Meta: {goalDate}</Text>
</BottomContainer>
</CardContainer>
)
}
export default Card
Function to delete card:
async function deleteCard(id: number){
await axios.delete(`http://localhost:3333/cards/${id}`)
}
Rendering the array of cards:
{
countriesCards?.map(card => (
<Card
id={card.id}
local={card.local}
goalDate={card.goalDate}
countryName={card.countryName}
countryFlag={card.countryFlag}
openModalEdit={() => { setShowModalEdit(true) }}
openModalDelete={() => { setShowModalDelete(true) }}
/>
))
}
What to do for my deleteCard() deletes the current card that trigger this function?
Please check the code in my GitHub. I'm using react Modal for control the cards rendering and the deleteCard function is invoked from an external file:
https://github.com/pablolucio97/places-to-know

Dispatch works every other time in children in a loop in useeffect

I recently start learning React. I have problem when use dispatch in UseEffect, which is inside the child in the loop. I can't publish all project, but below piece of code:
On the home page online store products are displayed. In a separate component there is a small card that is displayed using the map loop:
<Row className='mt-20'>
{products.map(product => (
<ProductItem key={product._id} product={product} history={history} />
))}
</Row>
Child component code:
const ProductItem = ({ product, history }) => {
const dispatch = useDispatch()
const reviewList = useSelector(state => state.reviewList)
const { reviews } = reviewList
useEffect(() => {
let clean = false
if (!clean) dispatch(listReviewsDetailsByProductId(product._id))
return () => (clean = true)
}, [dispatch, product])
const productRate =
reviews.reduce((acc, item) => item.rating + acc, 0) / reviews.length || 0
return (
<Col xl='3' sm='6'>
<Card>
<Card.Body>
<div
className={'product-img position-relative ' + styles.wrapperImage}
>
<Link to={'/product/' + product.slug}>
{product.images[0] && (
<img
src={product.images[0].path}
alt=''
className='mx-auto d-block img-fluid'
/>
)}
</Link>
</div>
<div className='mt-4 text-center'>
<h5 className='mb-3 text-truncate'>
<Link to={'/product/' + product.slug} className='text-dark'>
{product.name}{' '}
</Link>
</h5>
{reviews && (
<div className='mb-3'>
<StarRatingsCustom rating={productRate} size='14' />
</div>
)}
<ProductPrice price={product.price} newPrice={product.newPrice} />
</div>
</Card.Body>
</Card>
</Col>
)
}
I use dispatch action to get reviews for a specific product from the server and then calculate the rating and display it. Unfortunately, it works every other time, the rating appears, then disappears. I would be grateful for your help!
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SingleProduct (created by Context.Consumer)
in Route (at Root.jsx:96)
The problem is the clean variable which is not a part of your component's state. It exists only inside the scope of the useEffect callback and gets recreated every time that the effect runs.
What is the purpose of clean and when should we set it to true? The cleanup function of a useEffect hook only runs when the component unmounts so that would not be the right place to set a boolean flag like this.
In order to dispatch once per product, we can eliminate it and just rely on the dependencies array. I'm using product_.id instead of product so that it re-runs only if the id changes, but other property changes won't trigger it.
useEffect(() => {
dispatch(listReviewsDetailsByProductId(product._id))
}, [dispatch, product._id])
If this clean state serves some purpose, then it needs to be created with useState so that the same value persists across re-renders. You need to figure out where you would be calling setClean. This code would call the dispatch only once per component even if the product prop changed to a new product, which is probably not what you want.
const [clean, setClean] = useState(false);
useEffect(() => {
if (!clean) {
dispatch(listReviewsDetailsByProductId(product._id))
setClean(true);
}
}, [dispatch, product._id, clean, setClean])

How to fix TypeScript function type error?

I have TypeScript code:
const ItemsList: React.FC<any> = async () => {
const data = await Http.get(url);
return (
<ul>
{data.map(item => {
return (
<ItemPreview key={item.id} item={item} />
);
})}
</ul>
)
}
export default ItemsList
And got error:
Type '()=>Promise<JSX.Element>' is not assingable to type 'FC'. Type 'Promise' is missing the folowing properties from type 'ReactElement<any, any>': type, props, key
Have no idea how to fix it. What type should it be instead of 'React.FC'?
You're effectively thinking about it the wrong way. Instead of trying to make React component rendering wait for your data, instead you should be thinking of it in terms of updating state in React. Your component should just render whatever the current state is:
return (
<ul>
{data.map(item => {
return (
<ItemPreview key={item.id} item={item} />
);
})}
</ul>
)
Now, what is data? Clearly it's an array, and you also have an asynchronous operation which gets the values for that array. So currently there are a couple potential states:
An initial empty array
The updated array with data
You'd use React state to track that. For example:
const [data, setData] = useState([]);
Http.get(url).then(result => {
setData(result);
});
This first sets data to an empty array, and then in the response from the asynchronous operation updates it to the new data. Since it's using setState, this will instruct React to update the rendering of the component because state has changed.
So in this case the UI will first show no results, then momentarily will show the data. You can add complexity to this by tracking an "is loading" state of some kind and showing a "loading spinner" or some indication to the user that data is being fetched.

Categories