I'm working on passing data between a react webpage and a react-native webview. I want to send a signal to the react-native webview on the mobile once the webpage has been loaded.
I wonder why on the react webpage, the window.postMessage() doesn't work unless it's used with setTimeout. There's no error at all in the console, but it has to be delayed for about 500 in order to work. Can anyone explain that? I prefer avoiding setTimeout because it feels unreliable.
#observer
class TalkToMobile extends React.Component {
#observable message;
render() {
return (
<div>
{
message ?
<Editor data={this.message}/>: null
}
</div>
)
}
componentDidMount() {
document.addEventListener('message', (e: any) => {
if (e.data) {
this.message = JSON.parse(e.data);
}
});
let payload = {};
payload.command = "giveMeData";
window.postMessage(JSON.stringify(payload), "*")
/*
setTimeout(()=>{
let payload = {};
payload.command = "giveMeData";
window.postMessage(JSON.stringify(payload), "*")
}, 500);*/
}
}
You probably need to wait for the webview to load before posting messages to it, so it makes sense to do it from within the webview onLoad callback. Try the following:
...
onWebViewLoaded() {
if (this.webview) {
this.webview.postMessage('');
}
}
render() {
return (
<WebView
ref={webview => { this.webview = webview }}
source={{uri: 'https://facebook.github.io/react/'}}
onLoad={this.onWebViewLoaded}
/>
)
}
...
If you want to post message from webview to the app, try handling it in componentDidUpdate. By the time it gets fired the DOM is loaded and there will be no need for setTimeout
Related
When you open reactjs.org, under "Declarative" header, there is a sentence: React will efficiently update and render just the right components when your data changes.
For a couple of my apps, I'm using the following structure:
App
| AppContainer(all the app logic, protected before login)
| Login(Login form)
This structure works well if you return 2 different components inside App's render, according to the user's credentials.
render(){
if(isUserLoggedIn()){
return <AppContainer />;
}
return <Login />;
}
Inside the Login component, I'm refreshing the page with window.location.reload so the App's render will be triggered, and I'll get the AppContainer component.
But it feels a little like jQuery + Angular. Is there a better (more React) way to trigger render function, or is this how things should be?
Is there a better(more React) way to trigger render function...
The usual way is to have state, in this case at minimum a boolean for whether the user is logged in, and update that state when the user logs in successfully or logs out. Updating state triggers rendering.
In your case, since you're using Redux, you'd probably have your state there.
I don't use Redux (yet?), this is vaguely what it would look like without, roughly (if you're using a class component as you seem to be):
class App extends Component {
constructor(props) {
super(props);
this.state = {
loggedIn: /*...initial value, perhaps from web storage or cookie...*/;
};
this.onLogin = this.onLogin.bind(this);
this.onLogout = this.onLogout.bind(this);
}
onLogin() {
// ...probably stuff here, then:
this.setState({loggedIn: true});
}
onLogout() {
// ...probably stuff here, then:
this.setState({loggedIn: false});
}
render() {
if (this.state.logedIn) {
return <AppComponent onLogout={this.onLogout}/>;
}
return <Login onLogin={this.onLogin}/>;
}
}
or with hooks:
const App = () => {
const [loggedIn, setLoggedIn] = useState(/*...initial value, perhaps from web storage or cookie...*/);
const onLogin = useCallback(() => {
// ...probably stuff here, then:
setLoggedIn(true);
}, [loggedIn]);
const onLogout = useCallback(() => {
// ...probably stuff here, then:
setLoggedIn(false);
}, [loggedIn]);
if (this.state.logedIn) {
return <AppComponent onLogout={onLogout}/>;
}
return <Login onLogin={onLogin}/>;
}
(again, roughly)
If you need to update the component state, then you can pass an observable and listen for changes or use some state management library.
Here is one possible solution:
Create observable class
declare type IObserverHandler = (event: any) => void;
export class Observable {
private observers: IObserverHandler[] = [];
public subscribe(observer: IObserverHandler) {
if (!this.observers.includes(observer)) {
this.observers.push(observer);
}
}
public unsubscribe(observer: IObserverHandler) {
this.observers = this.observers.filter(o => o !== observer);
}
public publish(event: any) {
for (const observer of this.observers) {
observer(event);
}
}
}
Create Login class that will publish events on actions such as login or logout
class Login extends Observable {
public login() {
this.publish({ value: true });
}
public logout() {
this.publish({ value: false });
}
}
In component subscribe to observer and update component state using event value
export abstract class Component extends React.Component<any, any> {
private observer: IObserverHandler;
private observable: Login;
constructor(props: any) {
super(props);
this.observable = this.props.observable;
this.state = { isAuthenticated: false }
this.observer = (event) => {
this.setState({ isAuthenticated: event.value })
}
this.observable.subscribe(this.observer);
}
componentWillUnmount() {
this.observable.unsubscribe(this.observer);
}
}
You can use useNavigate and navigate to the same url you are on. For example, instead of window.location.reload(), you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
Creating some extra state and tracking them for re-rendering your page might unnecessarily complicate your code.
I'm fairly new to React and I'm trying to lazy load a markdown file stored on the server.
I've tried setting up an async arrow function that fetches the file and runs it through marked.
I found this demo here https://codesandbox.io/s/7zx3jlrry1 which I've tried following but haven't figured out how to follow it.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
// some state
}
}
render() {
let markdown = React.lazy(async () => {
// fetch the file
const response = await fetch(path)
if (!response.ok) {
return (<p>Response was not ok</p>)
} else {
// if successful, return markdown
let blob = await response.blob()
return marked(blob)
}
})
return (
<React.Suspense fallback={<div class="markdown">Loading</div>}>
<div class="markdown" id={this.props.id} dangerouslySetInnerHTML={{__html: markdown}} />
</React.Suspense>
)
}
}
When I try debugging it the arrow function isn't actually executed, and the inner HTML of the div is "[object Object]".
Any help as to how I can achieve this would be greatly appreciated.
You get [object Object] in your html because dangerouslySetInnerHTML expects a function returning the object {__html: '<div>some html string</div>'}. Other than that you are not using recommended way of fetching data through network requests. Please read on to get more details on how to perform your task.
React Suspense is used to lazy load Components not for fetching data as the react docs state:
In the future we plan to let Suspense handle more scenarios such as data fetching.
React.Suspense lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. Today, lazy loading components is the only use case supported by :
You don't require lazyload in this scenario. Use react-lifecycle methods in order to do things like fetching data at the correct time. What you require here is react-lifecylce method componentDidMount. Also you can use component state to manipulate what is rendered and what is not. e.g you can show error occured or loading by setting variables.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
error: false,
html: ""
}
}
componentDidMount = () => {
this.fetchMarkdown()
}
fetchMarkdown = async () => {
const response = await fetch(path)
if (!response.ok) {
// if failed set loading to false and error to true
this.setState({ loading: false, error: true })
} else {
// If successful set the state html = markdown from response
let blob = await response.text()
this.setState({ loading: false, html: blob })
}
}
getMarkup = () => {
return { __html: this.state.html }
}
render() {
if (this.state.error) {
return <div>Error loading markup...</div>
}
else if (this.state.loading){
return <div>Loading...</div>
}
else {
return <div class="markdown" id={this.props.id} dangerouslySetInnerHTML={this.getMarkup()} />
}
}
}
I'm creating a hackernews-clone using this API
This is my component structure
-main
|--menubar
|--articles
|--searchbar
Below is the code block which I use to fetch the data from external API.
componentWillReceiveProps({search}){
console.log(search);
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = ''){
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.props.getData(data.hits);
});
}
I'm making the API call in componentDidMount() lifecycle method(as it should be) and getting the data correctly on startup.
But here I need to pass a search value through searchbar component to menubar component to do a custom search. As I'm using only react (not using redux atm) I'm passing it as a prop to the menubar component.
As the mentioned codeblock if I search react and passed it through props, it logs react once (as I'm calling it on componentWillReceiveProps()). But if I run fetchData method inside componentWillReceiveProps with search parameter I receive it goes an infinite loop. And it goes an infinite loop even before I pass the search value as a prop.
So here, how can I call fetchdata() method with updating props ?
I've already read this stackoverflow answers but making an API call in componentWillReceiveProps doesn't work.
So where should I call the fetchdata() in my case ? Is this because of asynchronous ?
Update : codepen for the project
You can do it by
componentWillReceiveProps({search}){
if (search !== this.props.search) {
this.fetchdata(search);
}
}
but I think the right way would be to do it in componentDidUpdate as react docs say
This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
componentDidMount() {
this.fetchdata('story');
}
componentDidUpdate(prevProps) {
if (this.props.search !== prevProps.search) {
this.fetchdata(this.props.search);
}
}
Why not just do this by composition and handle the data fetching in the main HoC (higher order component).
For example:
class SearchBar extends React.Component {
handleInput(event) {
const searchValue = event.target.value;
this.props.onChange(searchValue);
}
render() {
return <input type="text" onChange={this.handleInput} />;
}
}
class Main extends React.Component {
constructor() {
this.state = {
hits: []
};
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = '') {
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.setState({ hits: data.hits });
});
}
render() {
return (
<div>
<MenuBar />
<SearchBar onChange={this.fetchdata} />
<Articles data={this.state.hits} />
</div>
);
}
}
Have the fetchdata function in the main component and pass it to the SearchBar component as a onChange function which will be called when the search bar input will change (or a search button get pressed).
What do you think?
Could it be that inside this.props.getData() you change a state value, which is ultimately passed on as a prop? This would then cause the componentWillReceiveProps function to be re-called.
You can probably overcome this issue by checking if the search prop has changed in componentWillReceiveProps:
componentWillReceiveProps ({search}) {
if (search !== this.props.search) {
this.fetchdata(search);
}
}
I am using X3DOM inside my React application to display an external X3D file
export class X3DComponent extends React.Component {
constructor(props) {
super(props);
this.x3dLoaded = this.x3dLoaded.bind(this);
}
x3dLoaded(e){
console.log('inside');
console.log(e);
}
render() {
return (
<div>
<x3d id='x3dElement' is='x3d'>
<scene is="x3d">
<navigationInfo type='EXAMINE' is='x3d'/>
<viewpoint id="viewPoint" is="x3d"
centerOfRotation='0 0 0 '
position="-1.94639 1.79771 -2.89271"
orientation="0.03886 0.99185 0.12133 3.7568"
isActive="true"
/>
<inline id="x3dInline" DEF='x3dInline' nameSpaceName="tanatloc" is="x3d" mapDEFToID="true"
url={this.props.fileUrl}/>
<loadSensor is='x3d' DEF='InlineLoadSensor' isLoaded={this.x3dLoaded} timeOut='5'>
<inline is='x3d' USE='x3dInline' containerField='watchList'/>
</loadSensor>
</scene>
</x3d>
</div>
);
}
}
I would like to listen to the event emitted by isLoaded in loadSensor.
According to X3D specs
An isLoaded TRUE event is generated when all of the elements have been loaded. (http://www.web3d.org/documents/specifications/19775-1/V3.3/Part01/components/networking.html#LoadSensor)
How can I do it using React ? This event is emitted after componentDidMount.
Thank you !
I don't think loadSensor has been implemented in X3DOM, c.f. https://doc.x3dom.org/author/nodes.html. But you can use a 'simple' onload event on your inline file:
<inline id="x3dInline" DEF='x3dInline' nameSpaceName="tanatloc" is="x3d"
mapDEFToID="true" url={this.props.fileUrl} onload={this.x3dLoaded} />
You can also check out https://github.com/x3dom/x3dom/blob/7d8ad13c2d2c17d9fd317e27b9e121379a53407f/test/regression-suite/test/cases/inlineEvents/inlineEvents.html for more examples.
I met the same problem as you do. I just write a simple polling in componentDidMount to solve this problem.
componentDidMount() {
const poller = () => {
this.timer = setTimeout(() => {
const loaded = document.querySelector('inline').getAttribute('load')
if (loaded) {
// do sth
} else {
poller()
}
}, 500)
}
poller()
}
Here's my approach... Similar to #neo_seele's answer, but using rxjs
EDIT added some timeout logic to avoid waiting indefinitely
import { interval, timer } from 'rxjs';
import { takeUntil, takeWhile } from 'rxjs/operators';
//reference to the <inline> element
const refInline = useRef(null);
const [error, setError] = useState("");
useEffect(() => {
window.x3dom && window.x3dom.reload();
//check every 250ms if model is already loaded, when it does, it calls the complete callback
//however if after 5s it hasn't loaded, it shows an error
interval(250)
.pipe(takeUntil(timer(5000)))
.pipe(takeWhile(()=>!refInline.current.querySelectorAll("Shape").length))
.subscribe({complete: ()=>{
if (!refInline.current.querySelectorAll("Shape").length){
setError(`Model could not be loaded, please check ${conf.url} format and try again.`);
} else {
// ... my logic
}
}});
}, [...dependencies]);
I am facing some trouble using the List View onEndReached component in react native.
Render code:
#autobind
_fetchMoreHistory(){
console.log("Fetch more history called");
}
<View style={[styles.Ctr]}>
<ListView dataSource={this.state.txHistorySource}
renderRow={ this._renderRow }
onEndReached ={ this._fetchMoreHistory }
onEndReachedThreshold = {10}/>
</View>
The moment I open the screen _fetchMoreHistory is called twice and works normally after that onEndReached reached. Can someone help debug this ?
I faced the same issue and searched a lot but didn't find any answers, so I used a condition to check if the first request got the data I fire onendreashed again else I don't
Example
// onEndReached
If(condition) {
Make the call
}
So my solution is simple. Don't use onEndReached at all. Use onScroll and detect the end of the scroll.
isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 20; // how far from the bottom
return layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
};
and the FlatList component
<FlatList
data={data}
onScroll={({ nativeEvent }) => {
if (this.isCloseToBottom(nativeEvent)) {
// Dont forget to debounce or throttle this function.
this.handleOnEndReached();
}
}}
/>
I had the same issue. But I figured out that I had the ScrollView that wraps my FlatList.
When I removed it all started working properly.
It's a pity that NOTHING WAS SAID ABOUT THAT IN THE OFFICIAL DOCS
You can try my solution
You should configure limit > 10. Example limit = 15
Add onMomentumScrollBegin prop to your ListView declaration.
<ListView
data={this.props.data}
onEndReached={...}
onEndReachedThreshold={0.5}
...
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
/>
Modify your onEndReached callback to trigger data fetching only once per momentum.
onEndReached =()=> {
if(!this.onEndReachedCalledDuringMomentum) {
this.props.fetchData();
this.onEndReachedCalledDuringMomentum = true;
}
};
I've solved this problem by creating a state variable that tells me is the service is loading or not.
class Component1 extends Component {
public constructor(props) {
super(props);
this.state = {
isLoading: false,
listOfItems: []
};
}
public componentDidMount() {
this.loadData();
}
public render(){
return (
<FlatList
data={this.state.listOfItems}
renderItem={this.renderItem}
onEndReachedThreshold={0.1}
onEndReached={this.loadData}
/>
);
}
private loadData = () => {
if (this.state.isLoading === true) {
/// Avoid make another request is there's happening a request right now
return;
}
this.setState({isLoading: true});
this.fetchData()
.then(() => {
/// Handle success response
this.setState({isLoading: false});
})
.catch(() => {
this.setState({isLoading: false});
});
}
}
You can write the code for fetching data in onMomentumScrollEnd rather than onEndReached, like this:
onMomentumScrollEnd={() => this.props.onEndReached()}
It might not be written as the available prop in the react native documentation, but if you will see the source code for FlatList, it uses Virtualized List which in return has the mentioned prop available.