I'm trying to prefetch an image using Image.prefetch() in an entry level component so that when the user navigates to a child component which displays the aforementioned image, it'll be ready to be displayed with less delay.
Image.prefetch() is mentioned under the Image topic in React Native documentation but I found the examples there fairly convoluted.
Can someone who used this feature explain how it would work for the simple use case I mentioned above?
Here's how I tried to implement it:
// main.js
class Main extends Component {
render() {
let prefetch = Image.prefetch(this.props.url);
return (
<Content imgUrl={this.props.url}/>
);
}
}
// content.js
class Content extends Component {
render() {
const { imgUrl } = this.props;
return (
<Image source={imgUrl} />
);
}
}
The result of this implementation is an empty Image because it just returns a promise without an Image.
What am I missing here?
You need to render Content after the prefetch is resolved:
render() {
Image.prefetch(this.props.url)
.then(() => {
return (
<Content imgUrl={this.props.url} />
);
})
.catch(error => console.log(error))
}
You may also show the ActivityIndicator while prefetching inside the Main render, conditional on a state variable that changes its flag (true/false) when the prefetch is resolved so that the indicator disappears on that event.
Related
So I've got two react components, and for some reason one of them is running again (and causing a nasty bug) when I click another to go to another component.
My guess is that this is because I am running some asynchronous code for geolocation in my component constructor, but I don't know enough about React to be 100% certain of this.
The showPosition method makes an API call based on a user's location and other variables.
class Cards extends Component {
constructor() {
super();
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
this.showPosition = this.showPosition.bind(this);
}
...
render() {
if (this.state.isLoading) {
return <Loading />;
}
// The lines below this ALWAYS get run, even several seconds after I
// have been on my new component!
console.log("state value new");
console.log(this.state.data);
return (
<Page className="main-page">
<div className="cards">
{this.state.data.merchants.map( (merchant, index) =>
<CardRow
merchant={{merchant}}
count={index}
className={'card-color-' + index}
/>
)}
</div>
</Page>
);
}
}
This cards component creates a child component called CardRow, and then that component creates several Card and PromoCard component children.
I won't link the full card, but the way I am accessing the component that breaks is this way - a user clicks the link, and is directed to the chat component:
<Link to={{
pathname: "/chat/" + this.state.merchant.id,
state: {merchant: this.state.merchant}
}}>
I made a toy component for chat, and everything loads fine, but then the render function in <Cards /> runs again, which messes up my entire chat interface.
Why is this happening? Is it related to my geolocation code in my constructor? Something else potentially?
You have a memory leak, it is not a good practice to set listeners in your constructor.
You must use lifecycle methods (componentDidMount, componentWillUnmount, etc...)
the componentDidMount lifecycle method is the right place to set a listener
constructor() {
this.showPosition = this.showPosition.bind(this);
this.unmounted = false;
}
componentDidMount() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
}
}
componentWillUnmount() {
// indicate that the component has been unmounted
this.unmounted = true;
}
showPosition() {
// exiting early if the component was unmounted
// prevent any update using setState
if (this.unmounted) { return; }
// more code...
}
I'm using server-side rendering with Webpack's code-splitting. The server returns the HTML for the component. However, when React initializes, since I'm using code-splitting, the React component I want to render isn't downloaded yet. Typically, I'd want to display a loading screen. However, the HTML for the component is already rendered, so I don't want to replace it with a loading screen.
Is there a way to get React to temporarily ignore the component and not update the DOM?
The component looks something like this:
export default class SomeRoute extends Preact.Component {
constructor() {
super();
this.state = {
Component: null,
};
}
componentDidMount() {
if (!this.state.component) {
this.props.componentLoader().then(Component => this.setState({ Component }));
}
}
render({}, { Component }) {
if (!Component) {
return (
<p>Loading...</p>
);
}
return (
<Component />
);
}
}
The output of <Component /> is already returned by the server.
You can use shouldComponentUpdate(). When you return false it will not update the component.
shouldCompnentUpdate(nextprops,nextstate){
return Boolean(this.state.Component)
}
I have a simple component that fetches data and only then displays it:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetch( { path: '/load/stuff' } ).then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
Each instance of MyComponent loads the same data from the same URL and I need to somehow store it to avoid duplicate requests to the server.
For example, if I have 10 MyComponent on page - there should be just one request (1 fetch).
My question is what's the correct way to store such data? Should I use static variable? Or I need to use two different components?
Thanks for advice!
For people trying to figure it out using functional component.
If you only want to fetch the data on mount then you can add an empty array as attribute to useEffect
So it would be :
useEffect( () => { yourFetch and set }, []) //Empty array for deps.
You should rather consider using state management library like redux, where you can store all the application state and the components who need data can subscribe to. You can call fetch just one time maybe in the root component of the app and all 10 instances of your component can subscribe to state.
If you want to avoid using redux or some kind of state management library, you can import a file which does the fetching for you. Something along these lines. Essentially the cache is stored within the fetcher.js file. When you import the file, it's not actually imported as separate code every time, so the cache variable is consistent between imports. On the first request, the cache is set to the Promise; on followup requests the Promise is just returned.
// fetcher.js
let cache = null;
export default function makeRequest() {
if (!cache) {
cache = fetch({
path: '/load/stuff'
});
}
return cache;
}
// index.js
import fetcher from './fetcher.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
stuff: null
};
}
componentDidMount() {
// load stuff
fetcher().then( stuff => {
this.setState({
loaded: true,
stuff: stuff
});
} );
}
render() {
if ( !this.state.loaded ) {
// not loaded yet
return false;
}
// display component based on loaded stuff
return (
<SomeControl>
{ this.state.stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
);
}
}
You can use something like the following code to join active requests into one promise:
const f = (cache) => (o) => {
const cached = cache.get(o.path);
if (cached) {
return cached;
}
const p = fetch(o.path).then((result) => {
cache.delete(o.path);
return result;
});
cache.set(o.path, p);
return p;
};
export default f(new Map());//use Map as caching
If you want to simulate the single fetch call with using react only. Then You can use Provider Consumer API from react context API. There you can make only one api call in provider and can use the data in your components.
const YourContext = React.createContext({});//instead of blacnk object you can have array also depending on your data type of response
const { Provider, Consumer } = YourContext
class ProviderComponent extends React.Component {
componentDidMount() {
//make your api call here and and set the value in state
fetch("your/url").then((res) => {
this.setState({
value: res,
})
})
}
render() {
<Provider value={this.state.value}>
{this.props.children}
</Provider>
}
}
export {
Provider,
Consumer,
}
At some top level you can wrap your Page component inside Provider. Like this
<Provider>
<YourParentComponent />
</Provider>
In your components where you want to use your data. You can something like this kind of setup
import { Consumer } from "path to the file having definition of provider and consumer"
<Consumer>
{stuff => <SomeControl>
{ stuff.map( ( item, index ) =>
<h1>items with stuff</h1>
) }
</SomeControl>
}
</Consumer>
The more convenient way is to use some kind of state manager like redux or mobx. You can explore those options also. You can read about Contexts here
link to context react website
Note: This is psuedo code. for exact implementation , refer the link
mentioned above
If your use case suggests that you may have 10 of these components on the page, then I think your second option is the answer - two components. One component for fetching data and rendering children based on the data, and the second component to receive data and render it.
This is the basis for “smart” and “dumb” components. Smart components know how to fetch data and perform operations with those data, while dumb components simply render data given to them. It seems to me that the component you’ve specified above is too smart for its own good.
I can't get my head wrapped around this.
The problem: let's say there's an app and there can be some sort of notifications/dialogs/etc that i want to create from my code.
I can have "global" component and manage it, but it would limit me to only one notification at a time, this will not fit.
render() {
<App>
// Some components...
<Notification />
</App>
}
Or i can manage multiple notifications by the component Notification itself. But state management will not be clear.
The other problem if i have some sort of user confirmation from that component (if it's a confirmation dialog instead of simple notification) this will not be very convinient to handle with this solution.
The other solution is to render a component manually. Something like:
notify(props) {
const wrapper = document.body.appendChild(document.createElement('div'))
const component = ReactDOM.render(React.createElement(Notification, props), wrapper)
//...
// return Promise or component itself
}
So i would call as:
notify({message: '...'})
.then(...)
or:
notify({message: '...', onConfirm: ...})
This solution seems hacky, i would like to let React handle rendering, and i have an additional needless div. Also, if React API changes, my code breaks.
What is the best practice for this scenario? Maybe i'm missing something completely different?
You could use React Context for this.
You create a React context at a high level in your application and then associate a values to it. This should allow components to create / interact with notifications.
export const NotificationContext = React.createContext({
notifications: [],
createNotification: () => {}
});
class App extends Component {
constructor() {
super();
this.state = {
notifications: []
};
this.createNotification = this.createNotification.bind(this);
}
createNotification(body) {
this.setState(prevState => ({
notifications: [body, ...prevState.notifications]
}));
}
render() {
const { notifications } = this.state;
const contextValue = {
notifications,
createNotification: this.createNotification
};
return (
<NotificationContext.Provider value={contextValue}>
<NotificationButton />
{notifications.map(notification => (
<Notification body={notification} />
))}
</NotificationContext.Provider>
);
}
}
The notifications are stored in an array to allow multiple at a time. Currently, this implementation will never delete them but this functionality can be added.
To create a notification, you will use the corresponding context consumer from within the App. I have added a simple implementation here for demonstration purposes.
import { NotificationContext } from "./App.jsx";
const NotificationButton = () => (
<NotificationContext.Consumer>
{({ notifications, createNotification }) => (
<button onClick={() => createNotification(notifications.length)}>
Add Notification
</button>
)}
</NotificationContext.Consumer>
);
You can view the working example here.
I'm trying to design a component where the user can click a button which will trigger a giphy API call that will eventually render a series of gif images.
So far I'm able to successfully get everything done except the actual image rendering. Here's my code so far:
retrieveURLs() {
*bunch of stuff to make API call and return an array of URLs related
to the category of the button the user pushes*
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
return results.map(function(url, index) {
console.log(url); //logs each url
return (<img key={index} src={url} alt="" />)
}, this);
});
}
render() {
return(
<div id="gif-div">
{this.renderGifs()}
</div>
)
}
Nothing gets rendered despite each console.log() event indicating that the URL(s) are at least being passed properly.
I do something similar for the parent component to render the buttons from an array of categories that looks like this:
btnArray = [*bunch of categories here*];
renderButtons() {
return btnArray.map(function(item, i) {
let btnID = btnArray[i].replace(/\s+/g, "+");
return <button type='button' className="btn btn-info" onClick={this.handleCategorySelect} key={i} id={btnID} value={btnID}>{btnArray[i]}</button>
}, this)
}
The buttons are rendered properly, but my images are not. Neither the renderbuttons nor the rendergifs metohds alter the state. Honestly I can't see a meaningful difference between the two, so I'd like to have some help figuring out why one works but the other doesn't.
This is the nature of asynchronous functions; you can't return a value from within a callback to the original call site. If you were to write:
const res = this.retrieveURLs().then(function(results) {
return results;
});
you'd only be changing the resolution value of the promise. res won't be assigned the value of results, but rather it will be assigned the promise created by this.retrieveURLs(), and the only way to retrieve the value of a resolved promise is by attaching a .then callback.
What you could do is this:
this.retrieveURLs().then(results => {
this.setState({ images: results });
});
Now your internal state will be updated asynchronously, and your component will be told to re-render with the new data, which you can use in your render function by accessing the state.
Note: I'm using an arrow function to create the callback in the above example, because otherwise this won't be bound to the right context. Alternatively, you could do the old that = this trick, or use Function#bind.
The problem lies with the rendering function for the images and the way React does diffing between the previous and current state. Since the fetch is asynchronous and the render is not, when the fetch is completed React doesn't know that it needs to re-render you component. There are ways to force a re-render in this case, but a better solution is to use the functionality that's already part of the shouldUpdate check.
Your implementation might change to look something like the following:
class Images extends Component {
state = {
images: [],
error: null
}
componentDidMount() {
return this.retrieveImages()
.then(images => this.setState({images}))
.catch(error => this.setState({error}))
}
... rest of your class definition
render () {
return (
<div>
{this.state.images.map(image => <img src={image.url} />)}
</div>
)
}
}
I would also have some handling for bad results, missing key / values, etc. I hope that works for you, if not let me know! :)
first of all you forgot the return statement:
renderGifs() {
return this.retrieveURLs().then(function(results) {
...
}
but this won't solve anything as it is returning a Promise.
You need to save request results in the state and then map it:
constructor(props){
super(props);
this.state = { images: [] };
}
componentDidMount() {
this.renderGifs();
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
this.stateState({ images: results });
});
}
render() {
return(
<div id="gif-div">
{
this.state.images.map((url, index) => (<img key={index} src={url} alt="" />);
}
</div>
)
}