How do i pass a custom props from RenderHTMLSource to a custom renderer in react-native-render-html? - react-native-render-html

I have a component called RenderHtml which renders my html:
<RenderHTMLSource
contentWidth={width * 0.8}
source={{html: props.html}}
/>
And a custom img renderer that looks like this:
<Touchable
onPress={() => props.???}
>
<Image src={props.tnode.attributes.src} />
</Touchable>
And i want to call a function inside RenderHtml from Touchable's onPress method.
Is there a way to pass a callback to custom renderers?

There are two common techniques:
1. Prop drilling via renderersProps
RenderHTML
const renderersProps = useMemo(() => ({img: {onPress: (uri) => /*do whatever*/ }}), [/*Don't forget deps here*/]);
<RenderHTML
contentWidth={width * 0.8}
renderersProps={renderersProps}
source={{html: props.html}}
/>
Custom renderer
const {onPress} = useRendererProps('img');
return (
<Touchable onPress={() => onPress(props.tnode.attributes.src)}>
<Image src={props.tnode.attributes.src} />
</Touchable>
);
When using typescript
You'd need to augment the module to include the new renderer prop:
declare module 'react-native-render-html' {
interface RenderersProps {
img: {
onPress: (source: string) => void;
};
}
}
2. React Context
Refer to React official documentation.

Related

React-native-tags-input implemented with hooks

I'm using react-native-tags-input library from npmjs and I found an example for using it in a React Class component, but I want to use it in a stateless(function) component. That's the big problem. I don't understand how to make this library to work in a stateless component. Here's a snippet from my code:
import TagInput from "react-native-tags-input";
const chooseTags = () => {
const [chips, setChips] = useState({
tag: "",
tagsArray: ["first", "second"],
});
const updateTags = (state) => setChips({ ...chips, state });
return (
<View>
<TagInput updateState={() => updateTags} tags={chips} />
</View>
);
};

Is it possible to partially apply a React component?

Say I have a <Button> component which takes two properties: text and id e.g.,
<Button text="delete" id="123"/>
Now say I have a list of user ids: [101, 102, 103, …]
Would it be possible to partially apply <Button>? e.g.,
ids.map(<Button text="delete" id={__}>)
Where __ is just a placeholder waiting to be replaced with the current id.
If it was possible, would partially applying a React component have any adverse effect on the React Reconciliation Algorithm?
You could use two ways
one, which is not really a partial
ids.map((id)=><Button text="delete" id={id} />)
and the partial one which is really extracting the function above and using it
const PartialDeleteButton = (id) => <Button text="delete" id={id} />
ids.map(PartialDeleteButton)
which you could also use as
<PartialDeleteButton id={5} />
i cannot see how these would affect the reconciliation algorithm
There is no partial render of a component in React.
A component watches on state and props. Whenever you change either one, it will refresh the component. So if you change id dynamically, it will re-render the component.
However that would be 1 extra re-render.
You can however choose to write functions to prevent that like
React.memo: For latest react
shouldComponentUpdate: For older version.
Following is a demo for React.memo:
What to look in fiddle, notice I have added a setTimeout that updates data and it will call the render of ToDoApp but since components are memoised, it will not be called
function Button({id, value}) {
const onClick = () => {
console.log(`${id} - ${value}`)
}
console.log(`Rendering Btn ${value}`)
return (<button id={id || '__'} onClick={onClick}>{ value }</button>);
}
const MyButton = React.memo(
Button,
(prevProps, nextProps) => {
return prevProps.value === nextProps.value
}
)
Note: Since you will stop rendering of a component, you will not get updated props in it.
You could use useCallback to get a similar effect to partial application:
const HelloGreeter = useCallback(({name}: {name: string}) =>
(<Greeter name={name} greet="hello" />), []);
So, in context:
interface GreeterProps {
greet: string
name: string
}
const Greeter = ({greet, name}: GreeterProps) => (
<div>{greet}, {name}</div>
);
const MyComponent = () => {
const [name1, setName1] = useState<string>("world")
const HelloGreeter = useCallback(({name}: {name: string}) =>
(<Greeter name={name} greet="hello" />), []);
const setNameCallback = useCallback((e: ChangeEvent<HTMLInputElement>) =>
setName1(e.target.value), []);
return(
<>
<HelloGreeter name={name1} >
<input value={name1} onChange={setNameCallback} />
</>
);
}
This would not confuse the React renderer, because useCallback defines the function once only.

How to test functionality of function props in storybook?

I have a parent component, <AssetSelectorMenu>, which is composed of two child components:
export const AssetSelectorMenu = (({ assets, sortByName }) => {
return (
<>
<AssetSelectorHeader sortByName={sortByName} />
{assets && assets.map((asset) => (
<AssetSelectorRow key={asset} />
))}
</>
);
});
storybook for AssetSelectorMenu:
export const Default = () => (
<AssetSelectorMenu sortByName={action("sortByName")} assets={assets} />
);
Inside the storybook for AssetSelectorMenu, I'd like to test that the function prop sortByName actually visually sorts the assets by name. At the moment, it only makes sure it the function gets called, but visually it's not sorting the assets. How can I do that?
If you want to use state in your Storybook examples so that your components are fully working based on interaction you need to use the createElement function from React.
Here is a simple example using a Checkbox component that has it's value managed by state which simulates using a state manager like Redux or Context etc.
import { Fragment, useState, createElement } from 'react'
<Preview>
<Story name="Default">
{createElement(() => {
const [value, setValue] = useState(['Yes'])
const onChange = (event, value) => {
setValue(value)
}
return (
<Checkbox
name="checkbox"
values={[
{ label: 'Yes', value: 'Yes' },
{ label: 'No', value: 'No' }
]}
value={value}
onChange={onChange}
/>
)
})}
</Story>
</Preview>

ReactJS sending ref to global useContext state (Konva)

I am using useContext as a global state solution. I have a Store.jsx which contains my state, and a reducer.jsx which reduces. I am using Konva to create some shapes on an HTML5 Canvas. My goal is when I click on a shape I want to update my global state with a reference to what is active, and when I click again, to clear the reference.
My Full Code can be found here:
https://codesandbox.io/s/staging-platform-2li83?file=/src/App.jsx
Problem:
The problem is when I update the global state via the onClick event of a shape, its says that the reference is 'null', but when I console.log the reference in the onClick I can see the correct reference.
I think I am missing an important point to how useRef works.
This is how the flow appears in my head when I think about this:
I create a canvas, and I map an array of rectangle properties. This creates 4 rectangles. I use a wrapper component that returns a rectangle.
{rectArray.map((rectangle, index) => {
return (
<RectWrapper key={index} rectangle={rectangle} index={index} />
);
})}
Inside the RectWrapper, I create a reference, pass it to the ref prop of the Rect. In the onclick function, when I console log 'shapeRef' I see the refence ONLY when dispatch is commented out. If I uncomment dispatch then it shows as null, and if I console log the state, the reference is always null.
const RectWrapper = ({ rectangle, index }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef); // This correctly identifies the rect only when dispatch is uncommented
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
perhaps I am going about this to wrong way with hooks. I am just trying to keep a global state of whats been clicked on because components in another file would rely on this state.
The problem is happening because you are creating RectWrapper component as a functional component within your App component causing a new reference of the component to be created again and again and thus the reference is lost
Move your RectWrapper into a separate component declared outside of App component and pass on dispatch as a prop to it
import React, { useEffect, useContext, useState, Component } from "react";
import { Stage, Layer, Rect, Transformer } from "react-konva";
import { Context } from "./Store.jsx";
import "./styles.css";
const RectWrapper = ({ rectangle, index, dispatch }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef);
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
export default function App() {
const [state, dispatch] = useContext(Context);
console.log("Global State:");
console.log(state);
const rectArray = [
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 }
];
return (
<div className="App">
<Stage height={500} width={500}>
<Layer>
{rectArray.map((rectangle, index) => {
return (
<RectWrapper
dispatch={dispatch}
key={index}
rectangle={rectangle}
index={index}
/>
);
})}
</Layer>
</Stage>
</div>
);
}
Working demo
I don't think you need to create a ref in RectWrapper, because onClick has one event parameter. And the ref of the element that was clicked can be found in the event:
onClick={(e) => {
const thisRef = e.target;
console.log(thisRef );
...
Here is a working version without useRef: https://codesandbox.io/s/peaceful-brook-je8qo

How to show/hide spinners, snackbars, or other transient components with React Function Components

Is it better to render spinners, snackbars, etc. in separate DOM elements instead of adding them to the main application component tree? In React class components, it was really easy to get a reference to the class components methods to show/hide the spinner. With the new React Hooks function components, it's not so easy anymore. If I put the spinner in the main component tree, could I use the new "useContext" hook to show/hide the spinner?
Below is a React Hooks global spinner using Material-UI that works but is very hacky. How can this be made more elegant?
namespace Spinner {
'use strict';
export let show: any; // Show method ref.
export let hide: any; // Hide method ref.
export function Render() {
const [visible, setVisible] = React.useState(false); //Set refresh method.
function showIt() {
setVisible(true); // Show spinner.
}
function hideIt() {
setVisible(false); // Hide spinner.
}
const styles: any = createStyles({
col1Container: { display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' },
});
return (
<div>
{visible && <div style={styles.col1Container}>
<CircularProgress key={Util.uid()}
color='secondary'
size={30}
thickness={3.6}
/>
</div>}
<SetSpinnerRefs showRef={showIt} hideRef={hideIt} />
</div>
); // end return.
} // end function.
const mounted: boolean = true;
interface iProps {
showRef();
hideRef();
}
function SetSpinnerRefs(props: iProps) {
// ComponentDidMount.
React.useEffect(() => {
Spinner.show = props.showRef;
Spinner.hide = props.hideRef;
}, [mounted]);
return (<span />);
}
} // end module.
The problem is similar to this one, and a solution for spinners would be the same as the one for modal windows. React hooks don't change the way it works but can make it more concise.
There is supposed to be single spinner instance in component hierarchy:
const SpinnerContext = React.createContext();
const SpinnerContainer = props => {
const [visible, setVisible] = React.useState(false);
const spinner = useMemo(() => ({
show: () => setVisible(true),
hide: () => setVisible(false),
}), []);
render() {
return <>
{visible && <Spinner />}
<SpinnerContext.Provider value={spinner}>
{props.children}
</SpinnerContext.Provider>
</>;
}
}
Which is passed with a context:
const ComponentThatUsesSpinner = props => {
const spinner = useContext(SpinnerContext);
...
spinner.hide();
...
}
<SpinnerContainer>
...
<ComponentThatUsesSpinner />
...
</SpinnerContainer>
In React class components, it was really easy to get a reference to the class components methods to show/hide the spinner
You can continue to use class components. They are not going anywhere 🌹
The not so good way
It is actually poor practice in my opinion to use class methods to show and hide a spinner. Assuming your api looks like
<Spinner {ref=>this.something=ref}/>
And you use
this.something.show(); // or .hide
The better way
<Spinner shown={state.shown}/>
Now you get to change state.shown instead of storing the ref and using show / hide.
Although I think that Basarat's answer is the modern way of solving this problem, the below code is the way I ended up doing it. This way I only need one line of code to build the spinner and only one line of code to show/hide it.
<Spinner.Render /> {/* Build spinner component */}
Spinner.show(); //Show spinner.
namespace Spinner {
'use strict';
export let show: any; //Ref to showIt method.
export let hide: any; //Ref to hideIt method.
export function Render() {
const [visible, setVisible] = React.useState(false); //Set refresh method.
function showIt() {
setVisible(true); //Show spinner.
}
function hideIt() {
setVisible(false); //Hide spinner.
}
const showRef: any = React.useRef(showIt);
const hideRef: any = React.useRef(hideIt);
//Component did mount.
React.useEffect(() => {
Spinner.show = showRef.current;
Spinner.hide = hideRef.current;
}, []);
const styles: any = createStyles({
row1Container: { display: 'flex', alignItems: 'center', justifyContent: 'center' },
});
return (
<div>
{visible && <div style={styles.row1Container}>
<CircularProgress
color='secondary'
size={30}
thickness={3.6}
/>
</div>}
</div>
); //end return.
} //end function.
} //end module.

Resources