ReactJS adding onClick handler into element given an HTML string - html

I'm given an HTML string from an API:
<div><h1>something</h1><img src="something" /></div>
I would like to add an onClick handler onto the img tag. I thought about using regex replace, but it's highly advised against.
I'm new to React... how would you go about solving this problem?
Any links or pointing into the right direction would be highly appreciated!
EDIT
Is there a way to add a listener to all anchor tags in a react friendly way? I'm thinking then I can just check the anchor tag's children, and if there's an image element, then I can run my code block.

I think a more idiomatic React way of doing this would be to refactor this into a component, replete with it's own onClick handler, and then insert your image URL via props. Something like
class MyImg extends React.Component {
onClick() {
// foo
}
render() {
return (
<div onClick={this.onClick}>
<h1>{this.props.foo}</h1>
<img src={this.props.imgSrc} />
</div>
);
}
}

Can you update your API to return JSON instead of HTML? JSON is easier to manipulate and you can create react elements on the fly, for example, let's assume your API returns this:
{
component: 'div',
children: [
{ component: 'h1', text: 'Something here' },
{ component: 'img', src: 'something.jpg' },
],
}
If you have that kind of response is very easy to create React elements at run time, you can also add events when creating these components, for example:
class DynamicContent extends PureComponent {
onImageClick = () => {
// Do something here!
console.log('Testing click!');
}
render() {
const children = response.children;
return React.createElement(response.component, null,
React.createElement(children[0].component, null, children[0].text),
React.createElement(children[1].component, {
...children[1],
onClick: this.onImageClick, // <-- Adds the click event
}),
);
}
}
You might want to create an utility that dynamically walks through the response object and creates all the components that you need.

Related

React Render HTML to Infowindow using #React-Google-Maps/api

How do I to render html in google maps Infowindow?
I have tried several different ways to do it, but none of them seems to work.
My code :
import React, { Component, useState } from 'react';
import ReactDOMServer from 'react-dom/server';
import { GoogleMap, LoadScript, useLoadScript, InfoWindow, Marker, } from '#react-google-maps/api'
export class MarkerWithInfoWindow extends React.Component {
constructor() {
super();
this.state = {
isOpen: false
}
this.onToggleOpen = this.onToggleOpen.bind(this);
this.onEventWindowClick = this.onEventWindowClick.bind(this)
}
onToggleOpen() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
const imgUrl = this.props.icon;
return (<Marker
title={this.props.title}
position={this.props.position}
icon={{
url: imgUrl,
}}
>
{this.state.isOpen && <InfoWindow
onCloseClick={this.onToggleOpen}
position={this.props.position}
onMouseOut={this.onToggleOpen}
pixelOffset={{ width: 25, height: 25 }}
zIndex={-1}
onClick={this.onEventWindowClick(this)}
>
<> {ReactDOMServer.renderToString(this.props.content)}</>
</InfoWindow>}
</Marker>)
}
}
my result looks like this:
I guess content prop is passed as HTML string, then dangerouslySetInnerHTML needs to be set to render it as html instead of returning it as a plain string, for instance:
const InfoWindowContent = () => <div dangerouslySetInnerHTML={{ __html: this.props.content }}></div>;
Usage
<InfoWindow
onCloseClick={this.onToggleOpen}
position={this.props.position}>
<InfoWindowContent/>
</InfoWindow>
Demo
I've done this only as a functional component. According to the Google Map API docs, the desired HTML content can be passed as the value of the content property of the InfoWindow instance at its instantiation. According to the documentation of the react-google-maps/api, the (initial) options can be passed as a prop in the InfoWindow JSX.
In my case, I recompute the InfoWindow content on the fly, so I keep that string as a piece of state (dataWindowContent in my case). React guarantees that the InfoWindow component will be rerendered each time the state changes, so that's how I keep mine current.
Finally, the InfoWindow component is created with the InfoWindow component open by default. I keep mine closed until it has something to show, so I put that behavior in the onInfoWindowLoad handler.
You'll need a ref to the underlying Google Maps API instance -- the InfoWindow component conveniently passes that object as an argument when it invokes the onLoad handler. So here's a sketch of how you might do what you're attempting (note that I haven't exercised this, I'm just trying to say what you might try):
const MarkerWithInfoWindow = ( {content, title, position, icon, ...props} ) => {
const [infoWindowContent, setInfoWindowContent] = useState(content);
const [localPosition, setLocalPosition] = useState(position);
const rawInfoWindowRef = useRef(null);
const onInfoWindowLoad = (aRawInfoWindow) => {
rawInfoWindowRef.current = aRawInfoWindow;
if (''===infoWindowContent) {
aRawInfoWindow.close();
}
};
return (
<GoogleMap>
<Marker
title={title}
position={localPosition}
icon={{url: icon}}
>
<InfoWindow
onLoad={onInfoWindowLoad}
onCloseClick={onInfoWindowClick}
options={{content: infoWindowContent}}
position={localPosition}
/>
</Marker>
<GoogleMap>
);
};
Note that I've passed your desired HTML into the InfoWindow instance as the value of its content parameter inside its option prop.
Note also that position and content props are used to initialize the corresponding local state. Use their setters to move the marker/infoWindow or move the map. Also note I've surrounded your Marker and InfoWindow with a containing GoogleMap. I'm pretty sure the components require this container. You might need set the center property of the containing map as another piece of state so that it changes if the position changes.
This approach lets you avoid the use of dangerouslySetInnerHTML. The only way to change infoWindowContent is to use its setter (setInfoWindowContent) and this will force a rerender of the component tree. In that re-render, the low-level InfoWindow instance will be updated with its new content automatically, and it will render the HTML.

Adding a button to Navigator to exercise a choice

Navigator contains a feature where users can define their own table views, see DAML docs for Navigator.
Is it possible to create a view where one column renders a button that, when clicked, immediately exercises a choice?
Yes, this is possible. The customized views allow you to render arbitrary React components, so let's create one to exercise a choice.
First, start with a working frontend-config.js file. The DAML quickstart project contains one.
Then, make sure you import at least the following symbols at the top of the file:
import React from 'react';
import { Button, DamlLfValue, withExercise } from '#da/ui-core';
Then, define the following top level values (for example, just below export const version={...}):
// Create a React component to render a button that exercises a choice on click.
const ExerciseChoiceButtonBase = (props) => (
<Button
onClick={(e) => {
props.exercise(props.contractId, props.choiceName, props.choiceArgument);
e.stopPropagation();
}}
>
{props.title}
</Button>
)
ExerciseChoiceButtonBase.displayName = 'ExerciseChoiceButtonBase';
// Inject the `exercise` property to the props of the wrapped component.
// The value of that property is a convenience function to send a
// network request to exercise a choice.
const ExerciseChoiceButton = withExercise()(ExerciseChoiceButtonBase)
ExerciseChoiceButton.displayName = 'ExerciseChoiceButton';
Finally, use the following code in your table cell definition:
{
key: "id",
title: "Action",
createCell: ({rowData}) => {
// Render our new component.
// The contract ID and choice argument are computed from the current contract row.
return ({
type: "react",
value: <ExerciseChoiceButton
title='Transfer to issuer'
contractId={rowData.id}
choiceArgument={
DamlLfValue.record(undefined, [
{label: 'newOwner', value: DamlLfValue.party(DamlLfValue.toJSON(rowData.argument).issuer)}
])
}
choiceName='Iou_Transfer'
/>
});
},
sortable: true,
width: 80,
weight: 3,
alignment: "left"
}
Another option would be create a React component where the onClick handler sends a REST API request using fetch(). Inspect the network traffic when exercising a choice through the Navigator UI in order to find out the format of the request.

Refresh previous screen on goBack()

I am new to React Native. How can we refresh/reload previous screen when returning to it by calling goBack()?
Lets say we have 3 screens A, B, C:
A -> B -> C
When we run goBack() from screen C it goes back to screen B but with old state/data. How can we refresh it? The constructor doesn't get called 2nd time.
Adding an Api Call in a focus callBack in the screen you're returning to solves the issue.
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
UPDATE 2023: willFocus event was renamed to focus
componentDidMount() {
this.props.fetchData();
this.focusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.focusSubscription();
}
How about using useIsFocused hook?
https://reactnavigation.org/docs/function-after-focusing-screen/#re-rendering-screen-with-the-useisfocused-hook
const componentB = (props) => {
// check if screen is focused
const isFocused = useIsFocused();
// listen for isFocused, if useFocused changes
// call the function that you use to mount the component.
useEffect(() => {
isFocused && updateSomeFunction()
},[isFocused]);
}
For react-navigation 5.x use
5.x
use
componentDidMount() {
this.loadData();
this.focusListener = this.props.navigation.addListener('focus', () => {
this.loadData();
//Put your Data loading function here instead of my this.loadData()
});
}
For functional component
function Home({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadData();
//Put your Data loading function here instead of my loadData()
});
return unsubscribe;
}, [navigation]);
return <HomeContent />;
}
On your screen B constructor will work like magic :)
this.props.navigation.addListener(
'didFocus',
payload => {
this.setState({is_updated:true});
}
);
Yes, constructor is called only for the first time and you can't call it twice.
First: But you can separate the data getter/setter from the constructor and put it in a function, this way you can pass the function down to the next Scene and whenever you're going back you may simply recall the function.
Better: You can make a go back function in your first scene which also updates the scene while going back and pass the go back function down. This way the second scene would not be aware of your update function which is reasonable.
Best: You can use redux and dispatch a go-back action in your second scene. Then in your reducer you take care of going back & refreshing your scene.
The built in listener function which comes with React-Navigation would be the easiest solution. Whenever a component is 'focused' on a again by navigating back, the listener will fire off. By writing a loadData function that can be called both when loading the Component AND when the listener is notified, you can easily reload data when navigating back.
componentWillMount(){
this._subscribe = this.props.navigation.addListener('didFocus', () => {
this.LoadData();
//Put your Data loading function here instead of my this.LoadData()
});}
Easy! insert the function inside useFocusEffect(func)
import { useFocusEffect } from '#react-navigation/native'
I have a similar situation and the way i refreshed was to reset the route when the back button is pressed. So, what happens is when the back button is pressed the screen is re-pushed into the stack and the useEffect on my screen loads the data
navigation.reset({
index: 0,
routes: [{ name: "SCREEN WHERE THE GOBACK BUTTON SHOULD GO" }],
});
Update for react-navigation v5 and use the React Hooks. Actually, the use is the same with react base class. For more detail, please checkout the documentation here
Here is the sample code:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
As above code, We add the event listener while the variable navigation change then We do something like call function refresh() and finally, we return the function for removing the event listener. Simple!
I think we have a very easy way (which works in 2021) to do so. Instead of using goBack or navigate, you should use push
this.props.navigation.push('your_route_B').
You can also pass params in the same way as we pass in navigate.
The only difference b/w navigate and push is that navigate checks if the route which we are passing exists in the stack. Thus taking us to the older one but, push just sends us there without checking whether that is in the stack or not (i.e, whether the route was visited earlier or not.)
This can be achived by useFocusEffect from '#react-navigation/native'
useFocusEffect will effect every time when screen is focus
Ref: https://reactnavigation.org/docs/use-focus-effect/
import { useFocusEffect } from '#react-navigation/native';
function Profile({ }) {
useFocusEffect(
React.useCallback(() => {
//Below alert will fire every time when profile screen is focused
alert('Hi from profile')
}, [])
);
return // ...code ;
}
You can use this event: navigation.addListener('focus'
And you can implement like this:
const Cards = ({ navigation }) => {
...
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
navigation.addListener('focus',() =>{
load();
});
}, [])
or you can use useIsFocused, and you can use that as a dependecy for useEffect
import { useIsFocused } from '#react-navigation/native'
const Cards = ({ navigation }) => {
const isFocused = useIsFocused()
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
load()
}, [isFocused])
For react navigation (5.x), you just need to add a focus subscription and put your component initializing logic in a separate function like so:
componentDidMount() {
this.init();
this.didFocusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.init();
}
);
}
init = async () => {
//fetch some data and set state here
}
If you're trying to get new data into a previous view, and it isn't working, you may want to revisit the way you're piping data into that view to begin with. Calling goBack shouldn't effect the mounting of a previous component, and likely won't call its constructor again as you've noted.
As a first step, I would ask if you're using a Component, PureComponent, or Functional Component. Based on your constructor comment it sounds like you're extending a Component class.
If you're using a component, the render method is subject to shouldComponentUpdate and the value of your state is in your control.
I would recommend using componentWillReceiveProps to validate the component is receiving the new data, and ensuring its state has been updated to reflect the new data.
If you're using the constructor to call an API or async function of some kind, consider moving that function into a parent component of both the route you're calling goBack from and the component you're wanting to update with the most recent data. Then you can ask your parent component to re-query the API, or update its state from a child component.
If Route C updates the "state/data" of the application, that update should be propagated to a shared parent of routes A, B and C, and then passsed down as a prop.
Alternatively, you can use a state management solution like Redux to maintain that state independent of parent/child components - you would wrap your components in a connect higher-order component to get the latest updates any time the application state changes.
TL;DR Ultimately it sounds like the answer to your question is rooted in where your application state is being stored. It should be stored high enough in your component hierarchy that each route always receives the latest data as a prop, passed from its parent.
Thanks to #Bat.
I have spent a lot of hours on finding the answer and finally, I got a basic solution which is working according to my needs. I was quite worried though.
Simply make a function like this in your previous activity make sure to bind it.
changeData(){
var mydata= salesmanActions.retrieveAllSalesman();
this.setState({dataListFill: mydata});
alert('' + mydata.length);
}
Simple, then in constructor bind this,
this.changeData= this.changeData.bind(this);
After that, as I am using react native navigation, so I will simply pass this function to the second screen just like the code below:
onPress={() => this.props.navigation.navigate('Add Salesman', {doChange:
this.changeData} )}
So when the new screen registered as "Add Salesman" will be called, a parameter named "doChange" which is assigned a function will also be transfered to other screen.
Now, in other screen call this method anywhere, by :
this.props.route.params.doChange();
It works for me. I hope works for you too, THANKS for the idea #Bat.
let we have 2 screen A and B , screen A showing all data . and screen B is responsible for adding that data. we add some data on using screen B and want to show instant changes on Screen A . we use below code in A
componentDidMount(){
this.focusListener = this.props.navigation.addListener('focus', () => {
thi`enter code here`s.startData();
//Put your Data loading function here
});
}
This is what you can do with react navigation v6.
Create a separate stack in stack navigator like this:
const PropertyListStack = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name={ROUTE_PROPERTY_LIST} component={PropertyList}/>
</Stack.Navigator>
)};
Now, whenever you you want to reload your initial screen navigate using this stack. like this:
navigation.navigate(
ROUTE_DASHBOARD_TABS,
{screen: ROUTE_PROPERTY_LIST_STACK}
);
This will reload your base screen. In my case base screen is PropertyList.
If you know the name of the Screen you want to go , then you can use this code.
navigation.navigate("Screen"); navigation.replace("Screen");
This code works fine if you don't have nested routes.
This answer assumes that the react-native-navigation library is being used, which is unlikely because it doesn't actually have a goBack() method...
The constructor doesn't call a second time because screen A and B are still rendered (but hidden behind screen C). If you need to know when screen B is going to be visible again you can listen to navigation events.
class ScreenB extends Component {
constructor(props) {
super(props);
// Listen to all events for screen B
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
onNavigatorEvent = event => {
switch (event.id) {
case 'willAppear':
// refresh your state...
break;
};
}
Other events: willDisappear, didAppear, didDisappear
An alternate solution to your problem is to use a state management solution like Redux to provide the state to all screens whenever it is updated (rather than just on screen transitions. See old react-native-nav/redux example.

Understand es6 and jsx in react

I am going through the official redux tutorial.
http://redux.js.org/docs/basics/UsageWithReact.html
In the file
components/Link.js have the following code
import React, { PropTypes } from 'react'
const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>
}
return (
<a href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
export default Link
What I am wondering why function link accepting the variable surrounded by curly braces. Secondly, the return statement inside the if block has the span jsx tag without the braces but the secondly the return statement outside the if block with the <a> tag has a braces. Can anyone explains why?
EDIT: After finding out about destructuring assignment from the answers I read the following article about how to use it in a function and it became very clear to me.
https://simonsmith.io/destructuring-objects-as-function-parameters-in-es6/
That's a stateless function, you can define react classes as plain JS functions when they don't have state and life-cycle methods
The curly braces are placed in there to use an amazing es6 feature called Destructuring.
Basically using es6 that's the same as doing:
const Link = (props) => {
const { active, children, onClick } = props;
...
And without using ES6 it would be the same as doing:
const Link = (props) => {
const active = props.active;
const children = props.children;
const onClick = props.onClick;
....
About the return you use brackets when your jsx elements have more then 1 line.
The function argument uses destructuring assignment to extract values from an object.
The braces around JSX are here to keep the indentation clean.
You can do this:
return <div>
lol
</div>
But you can't do this:
return
<div>
lol
</div>
So, to keep the indentation of JSX clean, you have to wrap the markup with braces.

In angular2, how to call a function inside an element without mouse event?

Below is part of code in parent component, I already get the enable value from eventEmitter in its child component, which is enable=true.
<img src="{{currentImg}}" alt="Play Image not found" (click)="onMouseClick()">
<pause-button (isPauseBtnClicked)="enable = $event"></pause-button>
status is: {{enable}}
Then how can I assign a value for currentImg="someImg.png" after it listened the eventEmitter(enable=true)? Should I write a function? if so, how can I call that function in img tag without any mouse event?
I konw with mouse click event, things becomes easier, currentImg can be assign a value inside function.
onMouseClick() {
this.currentImg = this.clickedImg;
}
Look I don't know what you want to achieve. But writing this answer by thinking that you want to go with EventEmitter way without calling any mouseevent.
Note: Your expectation might be different. But It might help you out. If doesn't, kindly use it as a reference. I might have understood something completely different but purpose is not to guide you in wrong way
<img src="{{currentImg}}" alt="Play Image not found" (click)="onMouseClick()">
<pause-button (isPauseBtnClicked)="fire($event)"></pause-button><br>
status is: {{enable}}<br> // haven't played with status
{{currentImg}}
boot.ts
fire(arg) {
console.log('test start');
//this.animate.subscribe((value) => { this.name = value; });
this.currentImg=arg;
console.log(arg);
}
Working Plunker
PasueButton.ts
#Component({
selector: 'pause-button ',
template: `
________________________________________________________
<br>
I'm child
<br>
<img src="img path" (click)="clickMe()"/>
<= click the img
<br>
_____________________________________________________
`
,
})
export class PasueButton implements OnInit {
#Output() isPauseBtnClicked: EventEmitter = new EventEmitter();
constructor() {
console.log('Constructor called');
}
ngOnInit() {
console.log('onit strat');
}
clickMe()
{
this.isPauseBtnClicked.next('child Img path is copied');
}
}