Loading data on screen load - reactjs

I have following code, Right now renderProductItem is rendered with fixed products. I want list to be rendered on screen load. So when screen is loaded it should call an API and render the list based on API response.
I saw solutions using state sopmething like https://github.com/vikrantnegi/react-native-searchable-flatlist/blob/master/src/SearchableList.js but the problem is when i create constructer its not getting called on screen load. So i am not sure how to use state in my case.
I am unable to figure out how to call an API on screen load and render list once response is available.
export const ProductListScreen = ({ navigation, route }): React.ReactElement => {
const displayProducts: Product[] = products.filter(product => product.category === route.name);
const renderProductItem = (info: ListRenderItemInfo<Product>): React.ReactElement => (
<Card
style={styles.productItem}
header={() => renderItemHeader(info)}
footer={() => renderItemFooter(info)}
onPress={() => onItemPress(info.index)}>
<Text category='s1'>
{info.item.title}
</Text>
<Text
appearance='hint'
category='c1'>
{info.item.category}
</Text>
<RateBar
style={styles.rateBar}
value={4}
// onValueChange={setRating}
/>
<CategoryList
style={styles.categoryList}
data={["Adventure", "Sport", "Science", "XXX"]}
/>
<Text>
The element above represents a flex container (the blue area) with three flex items.
</Text>
</Card>
);
return (
<List
contentContainerStyle={styles.productList}
data={displayProducts.length && displayProducts || products}
renderItem={renderProductItem}
/>
);
};

You can use hooks in your ProductListScreen component. You can create a state using useState hook and with the help of useEffect you achieve the behaviour of componentDidMount.
Please refer to this link:
https://reactjs.org/docs/hooks-effect.html

Use componentDidMount() to display the content. In React, this is a "lifecycle method". In the examples cited in the documentation you see that functions can be triggered when a component is added or removed. Based on your description you would want to test out componentDidMount for your code.
In the code sample you cited, you can see that this developer uses it in his class to call the makeRemoteRequest function
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const url = `https://randomuser.me/api/?&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: res.results,
error: res.error || null,
loading: false,
});
this.arrayholder = res.results;
})
.catch(error => {
this.setState({ error, loading: false });
});
};

Related

React JS - How to update image only once loaded?

I have a React app that receives a stream of image URIs from an EventSource. I want to display the most recent image, and only replace the image once the next image is fully loaded and can be displayed instantaneously. How do I accomplish this?
Edit 2: My current implementation does exactly this, except for one critical component: the most recent image is displayed as it loads, changing the vertical arrangement of the page.
Edit: Adding relevant snippets of code, this is my EventSource:
useEffect(() => {
let serverImageURIsEventSource = new EventSource(
url,
{withCredentials: false})
serverImageURIsEventSource.addEventListener('open', () => {
console.log('SSE opened!');
});
serverImageURIsEventSource.addEventListener('message', (e) => {
console.log(e.data);
setImageURIs([e.data]); // e.data is a string to the image URI
});
serverImageURIsEventSource.addEventListener('error', (e) => {
console.error('Error: ', e);
});
serverImageURIsEventSource.addEventListener('close', () => {
console.log('SSE closed!');
serverImageURIsEventSource.close();
});
return () => {
serverImageURIsEventSource.close();
};
}, [prompt]);
And this is my JSX with the images.
<ImageList cols={1}>
{imageURIs.map((imageURI) => (
<ImageListItem key={imageURI}>
<img
src={`${imageURI}`}
srcSet={`${imageURI}`}
loading="lazy"
// loading="eager" default
/>
</ImageListItem>
))}
</ImageList>
Make imageURIs as an array of object instead of array and a assign a number prop as your reference to order it by when you render it.
setImageURIs(prevImageUris => [...prevImageUris, {uri: e.data, number: prevImageUris.length + 1}])
and use sort when rendering
{imageURIs.sort((a,b) => b.number - a.number).map((imageURI) => (
<ImageListItem key={imageURI.uri}>
<img
src={`${imageURI.uri}`}
srcSet={`${imageURI.uri}`}
loading="lazy"
// loading="eager" default
/>
</ImageListItem>
))}

ReactJS - Alternative to passing useState between files

Background
I have a file that presents my main page Dash.js
This presents some data from an API on a "card", from two other files List.js and ListLoading.js
I have an additional "card" which I can trigger open with default useState value of 1, and the onClick works to close, as you will see in the dash.js file.
Current Code
//Dash.js
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true }).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} />
<Card>
);
}
//List.js
const List = (props) => {
const { repos } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;
//ListLoading.js
function WithListLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Fetching data may take some time, please wait
</p>
);
};
}
export default WithListLoading;
Desired Outcome
I want to set the the value for openCard.useState() to the repos.id.
e.g. onClick={() => openCard({repos.id})}
The complication of this is that I need to retrieve that code from List.js and pass it to the useState for the openCard, which is in Dash.js.
I am still fairly new to react so this is proving a little tricky to work out how to do.
What I've tried
I have looked into useContext, but either it has confused me or I am right to think this would not work for what I am trying to do.
I have looked into redux, however this seems like that may be overkill for this solution.
I have tried a series of passing the different constants via import/export however I now understand that useState is not designed to work this way and should really be used within the function/class where it is contained.
So any thoughts to remedy would be greatly appreciated!
So, just to restate what I understood your issue to be:
You have a parent component that renders a list of objects and can render a detail card of one of the object.
You want to have a single item in your list of objects be able to tell the parent "please open card 123".
Now to look at the options you considered:
Redux I agree Redux is overkill for this. Redux is usually only necessary if you need complex, possibly async reading and writing to a single shared datasource across the whole scope of your application. For a little UI interaction like this, it is definitely not worth setting up Redux.
React Context Context relies on a Provider component, which you wrap some chunk of your app in. Any component below that Provider can then use useContext to reach into the memory of that Provider. You can store anything in there that you could store in a component, from a single state variable up to a more complex useReducer setup. So, in a way, this basically does what you were hoping to do with static variables passing the state around. This is the right solution if you were going to be using this state value across a wide variety of components.
Props are probably the right way to go here - since you have a parent who wants to get messages from a child directly you can give the child a callback function. This is the same as the onClick function you can give a button, except here you can pass your list a onShowCard function.
In your Dash:
<ListLoading
isLoading={appState.loading} repost={appState.repos}
onShowCard={(cardId) => setopenCard(cardId)} />
At the end of the List:
{repos.map((repo) => {
return (
<button key={repo.id} className='repo-text max-width' onClick={() => { props.onShowCard(repo.id) }>
ID:{repo.id}{" "}Value:{repo.value}
</button>
);}
)}
You can pass on the function to update state to ListLoading component which will be forwarded to List component assuming it is wrapped by thee HOC WithListLoading.
Inside List you can then attach and onClick on the element to pass on the id of the clicked element
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true
}).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
const handleOpen = id => {
setopenCard(id);
}
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} handleOpen={handleOpen} />
<Card>
);
}
const List = (props) => {
const { repos, handleOpen } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' onClick={() => props.handleOpen(repo.id)} >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;

React not rendering component after componentDidMount

I have an Component, which is no rerendering after componentDidMount.
The render Method is looking like this :
render() {
{
console.log("----------RENDERING-----------------------------")
}
return (
<ImageBackground
source={require('../assets/images/bg.png')}
style={styles.bg}
>
<View style={styles.containerHome}>
<View style={styles.top}>
<City/>
<Text>myapp</Text>
{/*<Filters />*/}
</View>
<CardStack
loop={true}
verticalSwipe={false}
renderNoMoreCards={() => null}
ref={swiper => (this.swiper = swiper)}
>
{this.state.data.map((item, index) => (
<Card key={index}>
<CardItem
text={item.name}
detail={item.detail}
imageurl={item.imageurl}
matches="0"
actions
onPressLeft={() => this.swiper.swipeLeft()}
onPressRight={() => this.swiper.swipeRight()}
/>
</Card>
))}
</CardStack>
</View>
</ImageBackground>
);
}
...simply rendering a card stack.
Relevant here is this line :
this.state.data.map((item, index) => (
If i set the Data static from a file (Demo), it is working!
means if the line is looking like this
Demo.map((item, index) => (
everything alright!
but when i set the data in componentDidMount, it is not working!
I really dont know what react-native is doing here :
componentDidMount() {
this.setState({
isLoaded: true,
data: Demo
});
I set the state.data to exactly the same Demo Values, but react is not rerendering.
It seems to be that this.state.data is always empty.
Maybe anyone can help me?
Thx so much
ComponentDidMount() executes after the render() function, so you had better do this before rendering and outside of ComponentDidMount():
this.setState({
isLoaded: true,
data: Demo
});
So initially, before render(), you have to set some value of data.
Try with three possible answers:
Default value {data:Demo}, or
Implement this.state in a function which can be executed before render(), or
Put it in the render() function before the return statement.
Thx so much for the hint.
But i have already problems. Seems that i dont get the lifecylce, even when i am programming now react for a liitle bit.
Your hint was excellent, when i do it in the constructor, it is working.
But in the end, i wann fetch the data there, and if i do this, it doesnt seems to work in the constructor.
fetch('http://hostname/api/v1/xxxx', {
method: 'get',
headers: new Headers({
'Authorization': 'Bearer pumuckl',
'Content-Type': 'application/json'
}
)
}).then(res => res.json())
.then(
(result) => {
this.state = {
data: Demo,
}
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
the code is from the facebook documentation! Fetch is working!
What i am doing here is setting the state to the Demo Data, just to see if the lifecylce is waiting for the constructor and it seems that it doesn't.
Seems that rendering is be triggered before the constructor has finished initializing (..i can not imagine, that would be really bad), but i get a null Exception in the List!
Do i have to work with async await? I dont think so.
Just wanna initialze my List before rendering from a fetch.
Absolutely strange.
so if you look in my logs how the lifecycle is processing :
10-28 17:44:06.847 4796 5362 I ReactNativeJS: *************************** Constructor *****************************************************
10-28 17:44:06.851 4796 5362 I ReactNativeJS: ----------RENDERING-----------------------------
10-28 17:44:06.918 4796 5362 I ReactNativeJS: *************************** component Did Mount *****************************************************
10-28 17:44:06.927 4796 5362 I ReactNativeJS: ----------RENDERING-----------------------------
10-28 17:44:06.935 4796 5362 I ReactNativeJS: *************************** component Did Update *****************************************************
I am really a little bit desperate at the moment....
when i log my data in the rendering method :
render() {
const data = this.state.data
{
console.log("----------RENDERING-----------------------------")
console.log("----------DATA IN RENDERING-----------------------------")
console.log(data)
}
return ( ...
actually, the data seem be there. But using
{data.map((item, index) => (
does not work, while
{Demo.map((item, index) => (
is working.
I really dont know what going on here?

Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:
const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [props.parameterCurrent])
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
console.log(parameterCurrent[key]);
return (
prevState => ({
...prevState,
parameterSelections: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
Swal.fire({
position: "center",
icon: "success",
title:
JSON.stringify(key) + " set to " + JSON.stringify(valKey),
font: "1em",
showConfirmButton: false,
timer: 2500,
heightAuto: false,
height: "20px"
});
})
.otherwise(function (err) {
alert(
Swal.fire({
position: "top-end",
icon: "error",
title: err,
showConfirmButton: false,
timer: 1500,
width: "16rem",
height: "5rem"
})
);
});
}
);
};
const classes = useStyles();
return (
<div>
{Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
console.log(parameterSelections[key])
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterSelections[key]}
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
}
</div >
)};
export default Parameter;
The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).
Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!
This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect
In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [parameterCurrent])
However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.
I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...
I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

Accessing Apollo's loading boolean outside of Mutation component

The Mutation component in react-apollo exposes a handy loading boolean in the render prop function which is ideal for adding loaders to the UI whilst a request is being made. In the example below my Button component calls the createPlan function when clicked which initiates a GraphQL mutation. Whilst this is happening a spinner appears on the button courtesy of the loading prop.
<Mutation mutation={CREATE_PLAN}>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
The issue I have is that other aspects of my UI also need to change based on this loading boolean. I have tried lifting the Mutation component up the React tree so that I can manually pass the loading prop down to any components which rely on it, which works, but the page I am building has multiple mutations that can take place at any given time (such as deleting a plan, adding a single item in a plan, deleting a single item in a plan etc.) and having all of these Mutation components sitting at the page-level component feels very messy.
Is there a way that I can access the loading property outside of this Mutation component? If not, what is the best way to handle this problem? I have read that you can manually update the Apollo local state using the update function on the Mutation component (see example below) but I haven't been able to work out how to access the loading value here (plus it feels like accessing the loading property of a specific mutation without having to manually write it to the cache yourself would be a common request).
<Mutation
mutation={CREATE_PLAN}
update={cache => {
cache.writeData({
data: {
createPlanLoading: `I DON"T HAVE ACCESS TO THE LOADING BOOLEAN HERE`,
},
});
}}
>
{(createPlan, { loading }) => (
<Button
onClick={() => createPlan({ variables: { input: {} } })}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I face the same problem in my projects and yes, putting all mutations components at the page-level component is very messy. The best way I found to handle this is by creating React states. For instance:
const [createPlanLoading, setCreatePLanLoading] = React.useState(false);
...
<Mutation mutation={CREATE_PLAN} onCompleted={() => setCreatePLanLoading(false)}>
{(createPlan, { loading }) => (
<Button
onClick={() => {
createPlan({ variables: { input: {} } });
setCreatePLanLoading(true);
}
loading={loading}
>
Save
</Button>
)}
</Mutation>
I like the answer with React States. However, when there are many different children it looks messy with so many variables.
I've made a bit update for it for these cases:
const Parent = () => {
const [loadingChilds, setLoading] = useState({});
// check if at least one child item is loading, then show spinner
const loading = Object.values(loadingChilds).reduce((t, value) => t || value, false);
return (
<div>
{loading ? (
<CircularProgress />
) : null}
<Child1 setLoading={setLoading}/>
<Child2 setLoading={setLoading}/>
</div>
);
};
const Child1 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child1 !== loading ? { ...prev, Child1: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 1 Content
</div>
);
};
const Child2 = ({ setLoading }) => {
const [send, { loading }] = useMutation(MUTATION_NAME2);
useEffect(() => {
// add info about state to the state object if it's changed
setLoading((prev) => (prev.Child2 !== loading ? { ...prev, Child2: loading } : prev));
});
const someActionHandler = (variables) => {
send({ variables});
};
return (
<div>
Child 2 Content
</div>
);
};

Resources