I'm trying to have a grid as a background using ImageKonva and filling it with a pattern.
The problem is that when I load the canvas, the ImageKonva looks completely black, and it shows up as soon as I add children elements.
import React from 'react'
import { Stage, Layer, Image as ImageKonva } from 'react-konva'
const Canvas = ({ children, width, height, onClick }) => {
const image = new Image()
// this hash is the image of a grid
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIElEQVQoU2NkIAK8e/fuPyMR6hhGFeINJXDwgAhiwhIAOZAl5YYZd5AAAAAASUVORK5CYII='
return (
<Stage width={width} height={height}>
<Layer>
<ImageKonva
preventDefault={false}
width={width / 2}
height={width / 2}
fillPatternImage={image}
onClick={onClick}
/>
{children}
</Layer>
</Stage>
)
}
I've created this codesandbox where you can see the issue: https://codesandbox.io/s/angry-river-kszh0?file=/src/App.js
Note: It's not always reproducible, (can't figure out what's the pattern).
Sometimes it wouldn't happen, and I need to open chrome's console and refresh the page to make it happen.
You'll see the black box, and it will only show the grid after you click the button. The button only adds a child element.. from there, the image would always show. This problem only happens during the first load.
I believe I've figured it out, it seems I didn't consider that the image could be added asynchronously... so when I load the canvas, the image is not there yet.
So the solution is to only load ImageKanvas once the image is loaded using by setting an onload method to the image object
const Canvas = ({ children, width, height, onClick }) => {
const [loadGrid, setLoadGrid] = useState(false);
const image = new Image();
// this hash is the image of a grid
image.src =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIElEQVQoU2NkIAK8e/fuPyMR6hhGFeINJXDwgAhiwhIAOZAl5YYZd5AAAAAASUVORK5CYII=";
image.onload = () => {
setLoadGrid(true);
};
return (
<Stage width={width} height={height}>
<Layer>
{loadGrid && (
<ImageKonva
preventDefault={false}
width={width / 2}
height={width / 2}
fillPatternImage={image}
onClick={onClick}
/>
)}
{children}
</Layer>
</Stage>
);
};
https://codesandbox.io/s/wild-wind-br2qo?file=/src/App.js
Related
I have a search bar that has an customized input component. It works fine on another fixed nav but in the body of the page, it doesn't autofocus on single click.
I understand there are a ton of stack overflows and believe me, i've tried many of them (non jquery).
The problem: Single click on a div doesn't autofocus the input.
The device: All ios mobile (Safari and Chrome).
It does not happen on android mobile or on any of the desktops - onclick immediately focuses and brings up the keyboard on android mobile.
Here's some code to show how its setup
Input component
<ImportedSearchLIbrary
inputProp={
<InputComponent
autofocus
ref={inputRef}
/>
}
/>
^There is no problem with this component because it works just fine in that nav bar i mentioned.
This is how it is implemented in the body of a page>
const SearchComponent = () => {
const [show, setShow] = useState(false);
const wrapperRef = useRef(null);
const handleClose = () => {
setShow(false);
};
const handleClick = (e) => {
if (e && e.nativeEvent) e.nativeEvent.stopImmediatePropagation();
setShow(true);
const element = document.getElementById('searchID');
const yOffset = -72;
const yPosition = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
const screenWidth = window.innerWidth;
window.scrollTo({
top: yPosition,
behavior: 'smooth',
});
};
useClickOutside(wrapperRef, handleClose, [show]);
return (
<section id='searchId' ref={wrapperRef}>
<Icon iconName="search" className='' />
<div onClick={(e) => handleClick(e)}>
{show ? (
<SearchInput
placeholder='search me'
showsearch={show}
/>
) : (
<p>search me</p>
)}
</div>
{show && (
<Icon iconName="close" onClick={() => handleClose()} />
)}
</section>
);
};
So you can see that when <section> is clicked, show is set to true, and the searchbar AND the close icon show up. On desktop and android mobile (chrome, brave browser, mozilla and safari on desktop), when you click the section, it will show the search bar and auto focus to type.
This image shows how it looks - the red portion is the searchbar, the white background is the section, clickable area.
What I've tried so far>>
I've tried accessing the input ref like>
const inputRef = useRef(null);
<SearchInput
placeholder='search me'
showsearch={show}
inputRef={inputRef}
/>
Then in useEffect, (with and without settimeout)
useEffect(()=> {
setTimeout(() => {
if (show && inputRef) {
inputRef.current.focus();
}
}, 100);
}, [show]);
This didn't throw any errors but it didn't work.
Then, I tried the same thing in the searchinput component so when the component loads, in useEffect I set the focus to the passed ref.
Then I tried this >
document.addEventListener('mousedown', inputRef.current.focus());
These are the main things I've tried.
Now what works, is that if i ALWAYS show the input ie remove the condition show &&, but I don't want that.
Resources I found from stackOverflow but they didn't quite work>>
Can't Set Focus in Safari
ReactJS: How-to set focus to input-element when it enters the DOM?
https://www.quora.com/Regarding-the-mobile-browser-Safari-for-both-the-iPhone-and-iPad-with-JavaScript-how-can-I-launch-the-on-screen-keyboard
If more info is needed, i'd be happy to edit my Question with it.
Any help will be appreciated.
Thanks
I'm building a React Native App using Hooks and ES6.
The Home screen fetches some data from API and display it as image galleries.
The Home screen is the parent component which includes a slideshow gallery and a Flatlist as children.
1) Slideshow - image gallery autoplay that run some operations (fetching URL in case user clicks on them) on each image iteration (every 3 secs) - This causes the re-render on the entire parent components and its children but it renders every 3 secs as supposed
2) Flatlist - just a simple Flatlist that takes an array of images - this should only rendered once when the parent component is initially loaded first
The problem
Every 3 seconds a new image is displayed in the slideshow, a function runs to fetch some props for the image displayed, the parent component is re-rendered but there is no need for Flatlist in the second gallery to run again since the array of images is the same as initially loaded (not changed)
This is my Parent code
const getTopTenMovies = (state) => state.moviescreen.popularmovies //fetching data from Redux
const upcomingMovies = (state) => state.moviescreen.upcomingmovies
const MoviesScreen = (props) => {
const topTenMovies = useSelector(getTopTenMovies);
const [imageIndex, setImageIndex] = useState("");
/* useEffect below runs every time imageIndex changes (a new image is displayed every 3 secs) */
useEffect(() => {
setCarouselMovieId(selectedImageItem.id);
setCarouselMovieTitle(selectedImageItem.title);
}, [imageIndex]);
/* user clicks on an image and is taken to another screen
const fetchMovieHandler = async (movieId, movieTitle) => {
props.navigation.navigate({
routeName: "MovieDetail",
params: {
assetId: movieId,
assetName: movieTitle,
},
});
};
return (
<ImageGallery
data={topTenMovies}
currentImage=setImageIndex(index)
...
/>
<View style={styles.containerRow}>
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={(item) => item.id.toString()}
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
/>
</View>
</View>
)
}
The component renders the individual images within the Flatlist and passes the parameters (movieId, movieTitle) to the fetchMovieHandler function.
HomeItem code below...
class HomeItem extends React.PureComponent {
render() {
const assetHandler = async () => {
this.props.onAssetSelection(this.props.id, this.props.title);
};
console.log("HomeItem is called");
return (
<TouchableOpacity onPress={assetHandler}>
<View>
<Image
key={this.props.id}
source={{
uri: `https://*******${this.props.poster}`,
}}
/>
</View>
</TouchableOpacity>
);
}
}
export default React.memo(HomeItem);
Every 3 seconds the HomeItem is called and I see 10 log entries (one for each image and render - upcomingMovies has 10 images). From the UI everything looks fines since the images dont seems to change probably because I've defined the HomeItem as PureComponent and using React.memo(HomeItem) but it is causing performance issues since I'm getting the following error:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc
I tried to include the Flatlist inside the HomeItem component but the problem persists.
Following further research and thanks to #landorid suggestion I figured out that I need control the renderItem of the FlatList by declaring it in a separate function with a useCallback hook and dependency on my data source.
Tried to include Flatlist in a separate PureComponent my the re-rendering kept happening.
The ideal solution is to change this code
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
to something like this
.....
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
keyExtractor = (item) => item.id.toString();
return (
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={keyExtractor}
renderItem={renderRow}
/>
and then wrapping the renderRow() function in a useCallback hook.
The problem is that my data source data={upcomingMovies} is an array of objects with 2 levels and I need to use itemData.item.title to retrieve the title value for example.
While it works in my current renderItem settings it won't work when I use it in an external function as in
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
I simply get, "item" variable not available
Any suggestion on how to modify the function?
If I were you I would change the following:
Don't place inline functions in FlatList (keyExtractor, renderItem)! Place it to separated function.
Use simple Component extension instead of PureComponent and compare your props with `shouldComponentUpdate. (I think the passed function called onAssetSelection causes the re-render. Check it.)
You don't need to use React.memo because it is for functional components.
I'm building a simple ReactNative app for iOS and I'm trying to add a background image. It seems like there is no backgroundImage tag, and while I did manage to show the image on the page once, I can't repeat it throughout the page like you can with CSS. Any suggestions?
iOS
Images on iOS now include a repeat resizeMode property.
Use it like so:
<Image
src={/* whatever your source is */}
resizeMode="repeat"
/>
Android
June 2018 Update:
Starting from react-native 0.56 Android Images also support the repeat prop. (https://github.com/facebook/react-native/commit/0459e4ffaadb161598ce1a5b14c08d49a9257c9c)
Before June 2018:
On Android the repeat property does NOT work so:
you'll have to use something like Shiraman's answer
External lib:
There's now this great project called react-native-bgimage (created by Alex Melanchenko) which works well:
Here's how I use it:
import RepeatingPattern from 'react-native-bgimage';
<RepeatingPattern
drawable="block_pattern"
style={{
height: 45,
}}
/>
and then I add a pattern png file in android/app/src/main/res/drawable/block_pattern.png
Despite that this question is pretty old I would like to put my two cents in. It is also can be done via <ImageBackground> component (reference).
Example:
<ImageBackground source={require('./path/to/image.png')} style={{width: '100%', height: '100%'}} imageStyle={{resizeMode: 'repeat'}}>
// Your inside content goes here
</ImageBackground>
Don't forget to import component in the beginning of file, e.g. import { ImageBackground } from 'react-native';
I would like to extend Sriraman answer. To make the repeated images as the background, you need to take an addtional step of adding a new view and making its position absolute and background transparent, then adding all the other components inside of it.
var React = require('react-native');
var Dimensions = require('Dimensions');
var {
Image
} = React;
var RepeatImage = React.createClass({
render: function(){
var images = [],
imgWidth = 7,
winWidth =Dimensions.get('window').width;
for(var i=0;i<Math.ceil(winWidth / imgWidth);i++){
images.push((
<Image source={{uri: 'http://xxx.png'}} style={} />
))
}
return (
<View style={{flex:1,flexDirection:'row'}}>
{
images.map(function(img,i){
return img;
})
}
<View style={{position: 'absolute', top: 0, bottom: 0, left: 0, right: 0}}>
{/*Place all you compoents inside this view*/}
</View>
</View>
)
}
});
You can't repeat background image like CSS in React Native. But, You can achieve it by iterating the image like
var React = require('react-native');
var Dimensions = require('Dimensions');
var {
Image
} = React;
var RepeatImage = React.createClass({
render: function(){
var images = [],
imgWidth = 7,
winWidth =Dimensions.get('window').width;
for(var i=0;i<Math.ceil(winWidth / imgWidth);i++){
images.push((
<Image source={{uri: 'http://xxx.png'}} style={} />
))
}
return (
<View style={{flex:1,flexDirection:'row'}}>
{
images.map(function(img,i){
return img;
})
}
</View>
)
}
});
I'm currently writing an application on react-native version 0.7.1. I'm attempting to write a chat component.
With chat, you usually have the text input at the bottom and messages get pushed from the bottom to the top of the screen. Also, when the chat screen initializes, it initializes at the bottom of the screen and allows the user to scroll up. I would like to implement the same behavior in react-native. The current behavior of the ScrollView pushes items from the top to the bottom. I've attempted a few different solutions to this:
The first solution involves attempting to measure the ScrollView component. The concept is, if I know the height of the view, I can this.refs.messages.scrollTo(contentHeight). Some people mentioned the following function: this.refs.messages.measure(callbackWithTheseParams((a, b, width, height, px,py )). But the measure function turns out to be undefined.
The second method involved the package https://www.npmjs.com/package/react-native-invertible-scroll-view. This package actually sorted my data correctly too (reverse). You can think about it like flipping the pixels on the screen so that (0,0) is the bottom. This allows me to call scrollTo(0,0). However, seems like this functionality is only available in the 0.8.0-rc. Which I'm not sure is stable (or as stable rather).
EDIT - Adding Example
My constructor:
componentDidMount() {
this.measureComponent()
}
// measureComponent
measureComponent() {
console.log('this gets called');
this.refs.messages.measure((ox, oy, width, height) => {
console.log(height); // does not get called.
});
}
My render function is:
return (
<ScrollView
scrollEventThrottle={200}
ref="messages"
style={styles.container}>
<View style={styles.messages}>
{messages.map(this.renderMessage)}
</View>
<TextInput
style={styles.newMessage}
value={this.state.message}
onSubmitEditing={this.sendMessage}
onChangeText={(text) => this.setState({message: text})} />
</ScrollView>
);
I'm not sure if there is an easier way to get the height of a ScrollView, but I did notice this in the React Native source code for ListView (/Libraries/CustomComponents/ListView/ListView.js):
_measureAndUpdateScrollProps: function() {
var scrollComponent = this.getScrollResponder();
...
RCTUIManager.measureLayout(
scrollComponent.getInnerViewNode(),
React.findNodeHandle(scrollComponent),
logError,
this._setScrollContentLength
);
...
},
_setScrollContentLength: function(left, top, width, height) {
this.scrollProperties.contentLength = !this.props.horizontal ?
height : width;
},
Which gets either the height or the width depending on if the horizontal property is set. RCTUIManager is part of the native modules module (require('NativeModules').UIManager).
this will probably turn out undefined if you did not bind the function in the constructor.
this.refs.messages.measure(callbackWithTheseParams((a, b, width, height, px,py )).
try something like:
_measureFunc() {
this.refs.MESSAGES.measure((ox, oy, width, height) => {
setState ({ h: height });
});
}
For example this would be your messages component:
<View ref='MESSAGES' ></View>
then add bind (placed in constructor):
this._measureFunc = this._measureFunc.bind(this);
I'm having trouble getting my highchart to reduce in size when the window is resized down.
I have created an example of my code in JSFiddle http://jsfiddle.net/britboy/UvFaQ/
<table border='1' width='100%'><tr><td width='100%'>
<div id="container" width='100%'></div>
</td></tr></table>
Its a simple Highchart chart displayed in a table - if I make the window bigger the chart expands, however if I make the window smaller the chart doesn't want to reduce instead the table gets scroll bars.
I've tried setting up a resize event and then adjusting the chart size with chart.setSize( ) but the problem is the div containing the chart never reduces any further in size, so the setSize() does not get triggered. Since the chart resizes automatically when the chart container gets bigger I would have though the same should work when the chart gets smaller. I think the problem is that the chart's size is preventing its container from shrinking.
How do I code a chart in a table that will reduce in size when the table is reduced?
Thanks
There are n number of ways to accomplish it.
One would be as pointed out by #Azeem. in the comment.
Other way, I actually, bind it with window. Whenever the window re-sizes, bind event would trigger a function to re-size the div element used to render the chart.
$(window).bind("resize", resizeChart);
then,
function resizeChart() {
var width = $(document).width() - 55;
var height = $(document).height() - 60;
$("#container").css("width", width);
$("#container").css("height", height);
}
Check out the sample fiddle here.
Try this method :
var chart = $('#graph1').highcharts();
var DetailsWidth = $('#graph1');
http://jsfiddle.net/cjohn/5ghzk4t8/4/
[Adding a solution for react-highcharts here, because this pops up as first SO solution when searching for "react-highcharts shrink" on Google]
For react-highcharts you can simply call the React Component forceUpdate() method in the window resize event listener. It will trigger Highcharts to redraw to the new, smaller area. See also Rerender view on browser resize with React
NOTE: my code was tested using flex-auto (see CSS Flexible Box Layout) in the parent and the Highcharts DOM elements. It would be interesting to know if this works for other layouts too.
import React from 'react';
import Highcharts from 'react-highcharts';
...
export default class Chart extends React.Component {
constructor(props) {
...
// install callbacks
this.resize = () => {
// throttle resize events as redraw is expensive
if (this.timeout)
return;
this.timeout = setTimeout(
() => {
// force Highcharts to always adhere
// to changes in the view area
this.forceUpdate();
this.timeout = undefined;
},
50); // milliseconds
};
...
}
...
componentDidMount() {
...
// listen to window resize events
window.addEventListener('resize', this.resize);
...
}
...
componentWillUnmount() {
...
// remove listener when component goes away
window.removeEventListener('resize', this.resize);
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
...
}
...
render() {
...
return (
<Highcharts
config={...}
domProps={{
className: 'd-flex flex-auto',
...
}}
...
/>
);
}
...
}