How to force a tag to full-width in React-Native-Render-Html? - javascript

We have a custom renderer for span tags but I can't find any way to force the span to full-width. I tried wrapping the custom renderer in a View as well as applying all the usual React-Native styles without luck.
Given input HTML like below
<span>Hello</span><span>Goodbye</span>
How can we render it as below?
Hello
goodbye
Currently it renders inline like:
hello goodbye
The custom renderer looks like this:
const renderers = {
span: RenderedSpan,
};
const RenderedSpan = props => {
return (
<Text>
Some hardcoded text
</Text>
);
};
I figured I'd finally found a solution for the issue when I came across an example
That inspired me to try this code to set the contentModel to block:
(still no dice)
const customHTMLElementModels = {
span: defaultHTMLElementModels.span.extend({
contentModel: HTMLContentModel.block,
}),
};

React Native Capabilities
React Native has roughly two display algorithms: flex, and text-inline. Any element which has a Text parent will be displayed with the text-inline algorithm, and all other elements will be displayed with the flex algorithm. The former severely limits the set of styles which will be supported, this is what you are stumbling on.
Elements inside text-inline will have the following styles ignored:
Margins
Percent-widths
All flex rules
I suggest you play with a snippet like the one below to have a gist:
import React from 'react';
import {SafeAreaView, useWindowDimensions, View, Text} from 'react-native';
export default function App() {
const {width} = useWindowDimensions();
return (
<SafeAreaView style={{flex: 1}}>
<Text>
Hello world!
<View style={{ /* play with padding, margins, flex... etc! */ }}>
<Text>I'm inside a view!</Text>
</View>
How are you doing?
</Text>
</SafeAreaView>
);
}
Solution 1: force width
Hence, the only way we could achieve what you are looking for via styles would be to set its width to the passed contentWidth:
import React from 'react';
import {SafeAreaView, useWindowDimensions, View} from 'react-native';
import RenderHTML, {
CustomTextualRenderer,
useContentWidth,
} from 'react-native-render-html';
const htmlContent = `
<span>Hello</span><span>Goodbye</span>
`;
const RenderedSpan: CustomTextualRenderer = ({TDefaultRenderer, ...props}) => {
const width = useContentWidth();
return (
<View style={{width}}>
<TDefaultRenderer {...props} />
</View>
);
};
const renderers = {
span: RenderedSpan,
};
export default function App() {
const {width} = useWindowDimensions();
return (
<SafeAreaView style={{flex: 1}}>
<RenderHTML
renderers={renderers}
source={{html: htmlContent}}
contentWidth={width}
/>
</SafeAreaView>
);
}
LINK TO SNACK
Solution 2: use Block content model
You suggested setting the span content model to block and with the snippet you provided, it works great:
import React from 'react';
import {SafeAreaView, useWindowDimensions, View} from 'react-native';
import RenderHTML, {
defaultHTMLElementModels,
HTMLContentModel,
} from 'react-native-render-html';
const htmlContent = `
<span>Hello</span><span>Goodbye</span>
`;
const customHTMLElementModels = {
span: defaultHTMLElementModels.span.extend({
contentModel: HTMLContentModel.block,
}),
};
export default function App() {
const {width} = useWindowDimensions();
return (
<SafeAreaView style={{flex: 1}}>
<RenderHTML source={{html: htmlContent}} contentWidth={width} customHTMLElementModels={customHTMLElementModels} />
</SafeAreaView>
);
}
The block content model will force the render engine to guarantee rendered span elements won't have any Text ascendants, thus securing a flex column display. LINK TO SNACK

Did you try something like this :
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default function App() {
const renderers = {
span: RenderedSpan,
};
const RenderedSpan = props => {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor:"#ff0000"
},
});
return (
<View style={styles.container}>
<Text>Some hardcoded text</Text>
</View>
);
};
return <RenderedSpan/>;
}

Related

Share QR React Native

I'm new in react/react native. I'm trying to share a QR Code as image.
Generate QR works, but I want to share it as an image (whatsapp, bluetooth, etc).
import QRCode from 'react-native-qrcode-svg';
let svg = useRef();
//let svg = '';
<QRCode
size={300}
value={`${name}`}
getRef={(c) => (svg = c)}
/>
I tried "get base64 string encode of the qrcode" from official documentation, but I just don't get it
//From Off Doc
getDataURL() {
this.svg.toDataURL(this.callback);
}
callback(dataURL) {
console.log(dataURL);
}
What I tried to do (all my code):
import React, { useRef } from 'react';
import QRCode from 'react-native-qrcode-svg';
const QR = ({ name }: any) => {
let svg = useRef();
const getDataURL = () => {
svg.toDataURL(callback(dataURL));
//console.log(svg);
}
callback(dataURL) {
console.log(dataURL);
}
return (
<>
<QRCode
size={300}
value={`${name}`}
getRef={(c) => (svg = c)}
/>
<Button onPress={getDataURL}
title="Call Funct"
color="#1FAAE2" />
</>
);
get error svg.toDataURL is not a function.
I have been in this for days, I also read another stackover queries with the same problem but solutions in those questions didn't work for me. Thank you in advance guys
Error toDataURL
console.log(svg)
I have changed a couple of things in your code and used it on a expo app where I installed react-native-qrcode-svg and react-native-svg
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, TextInput, Button } from "react-native";
import { useRef } from "react";
import QRCode from "react-native-qrcode-svg";
const QR = ({ name }: any) => {
let svg = useRef<SVG>(null);
const getDataURL = () => {
svg?.toDataURL(callback);
//console.log(svg);
};
function callback(dataURL: string) {
console.log(dataURL);
}
return (
<>
<QRCode size={300} value={`${name}`} getRef={(c) => (svg = c)} />
<Button onPress={getDataURL} title="Call Funct" color="#1FAAE2" />
</>
);
};
export default function App() {
const input = useRef<TextInput>(null);
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
<QR />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
Main changes from your code is defining the callback as a function
// you had
callback(dataURL) {
console.log(dataURL);
}
// but it should be
function callback(dataURL) {
console.log(dataURL);
}
// or
const callback = (dataURL) => {
console.log(dataURL)
}
and doing the call properly on getDataURL
// you had
svg.toDataURL(callback(dataURL));
// but it should be
svg.toDataURL(callback);
After those changes clicking in the button returns the dataURL in the console as expected.
Old answer before question edit:
Your issue seems to be that svg is not defined when you call svg.toDataURL how are you calling the function? If you are doing that on the first render it is possible that the ref is not ready yet.
If you are doing that using a callback in a button in the screen then the issue should be around the code setting the ref.
Can you post your whole component?
My solution with typescript: I needed to add
'// #ts-ignore' because I used Typescript
import React, { useRef } from 'react';
import QRCode from 'react-native-qrcode-svg';
import { TouchableOpacity, Text } from 'react-native';
export const Screen1 = ( ) => {
const svg = useRef();
const getDataURL = () => {
// #ts-ignore
svg.current?.toDataURL(callback);
};
const callback = (dataURL: string) => {
console.log( dataURL );
}
return (
<>
<QRCode
value={ "Some String" }
size={ 250 }
color="black"
backgroundColor="white"
getRef={(c: any) => ( svg.current = c )}
/>
<TouchableOpacity
activeOpacity={ 0.8 }
style={ styles.Button }
onPress={() => getDataURL() }
>
<Text style={ styles.Text }> Press </Text>
</TouchableOpacity>
</>
)
You get the function not found error when you testing it with web, test it with iOS simulator then it will work.

ImageBackground resizeMode not working in style props

I am developing a simple app in react native. i just started using react native. i am using latest version 0.64. problem is that resizeMode of ImageBackground is not working when used in stylesheet. however when i use it as direct props of ImageBackground components it starts working.
Following is my code:
/*global require */
...
import {
StyleSheet,
ImageBackground,
View,
TouchableOpacity,
Text,
useWindowDimensions,
} from "react-native";
...
export default function LaunchScreen({ navigation }) {
...
const image = require("../../assets/test.png");
...
return (
<View onLayout={handleLayout} style={styles.container}>
<ImageBackground source={image} style={styles.image}>
...
</ImageBackground>
</View>
);
}
const styles = StyleSheet.create({
...
image: {
flex: 1,
resizeMode: "contain",//--> This is not working.
justifyContent: "center",
},
...
});
LaunchScreen.propTypes = {
navigation: PropTypes.object.isRequired,
};
if i add this prop of resizeMode to ImageBackground it starts working.as per docs https://reactnative.dev/docs/imagebackground it should work in stylesheet also. any idea why this is happening ?? and whats the solution
Follow the below example for resizeMode,
<ImageBackground
...otherProps,
resizeMode= 'contain'
>
{child}
</ImageBackground>

React Native: Passing useState() data to unrelated screens

Explanation: I am creating a fitness app, my fitness app has a component called WorkoutTimer that connects to the workout screen, and that screen is accessed via the HomeScreen. Inside the WorkoutTimer, I have an exerciseCount useState() that counts every time the timer does a complete loop (onto the next exercise). I have a different screen called StatsScreen which is accessed via the HomeScreen tab that I plan to display (and save) the number of exercises completed.
What I've done: I have quite literally spent all day researching around this, but it seems a bit harder with unrelated screens. I saw I might have to use useContext() but it seemed super difficult. I am fairly new to react native so I am trying my best haha! I have attached the code for each screen I think is needed, and attached a screenshot of my homeScreen tab so you can get a feel of how my application works.
WorkoutTimer.js
import React, { useState, useEffect, useRef } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Button,
Animated,
Image,
SafeAreaView,
} from "react-native";
import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";
export default function WorkoutTimer() {
const [count, setCount] = useState(1);
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
const exercise = new Array(21);
exercise[1] = require("../assets/FR1.png");
exercise[2] = require("../assets/FR2.png");
exercise[3] = require("../assets/FR3.png");
exercise[4] = require("../assets/FR4.png");
exercise[5] = require("../assets/FR5.png");
exercise[6] = require("../assets/FR6.png");
exercise[7] = require("../assets/FR7.png");
exercise[8] = require("../assets/FR8.png");
exercise[9] = require("../assets/S1.png");
exercise[10] = require("../assets/S2.png");
exercise[11] = require("../assets/S3.png");
exercise[12] = require("../assets/S4.png");
exercise[13] = require("../assets/S5.png");
exercise[14] = require("../assets/S6.png");
exercise[15] = require("../assets/S7.png");
exercise[16] = require("../assets/S8.png");
exercise[17] = require("../assets/S9.png");
exercise[18] = require("../assets/S10.png");
exercise[19] = require("../assets/S11.png");
exercise[20] = require("../assets/S12.png");
exercise[21] = require("../assets/S13.png");
return (
<View style={styles.container}>
<View style={styles.timerCont}>
<CountdownCircleTimer
isPlaying
duration={45}
size={240}
colors={"#7B4FFF"}
onComplete={() => {
setCount((prevState) => prevState + 1);
setExerciseCount((prevState) => prevState + 1);
if (count == 21) {
return [false, 0];
}
return [(true, 1000)]; // repeat animation for one second
}}
>
{({ remainingTime, animatedColor }) => (
<View>
<Image
source={exercise[count]}
style={{
width: 150,
height: 150,
}}
/>
<View style={styles.timeOutside}>
<Animated.Text
style={{
color: animatedColor,
fontSize: 18,
position: "absolute",
marginTop: 67,
marginLeft: 35,
}}
>
{remainingTime}
</Animated.Text>
<Text style={styles.value}>seconds</Text>
</View>
</View>
)}
</CountdownCircleTimer>
</View>
</View>
);
}
const styles = StyleSheet.create({})
WorkoutScreen.js
import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import WorkoutTimer from "../components/WorkoutTimer";
export default function WorkoutScreen() {
return (
<View style={styles.container}>
<WorkoutTimer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
HomeScreen.js
import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "#expo/vector-icons";
import { Colors } from "../colors/Colors";
export default function HomeScreen({ navigation }) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.pageRef}>SUMMARY</Text>
<Text style={styles.heading}>STRETCH & ROLL</Text>
<View style={styles.content}>
<TouchableOpacity
style={styles.timerDefault}
onPress={() => navigation.navigate("WorkoutScreen")}
>
<Button title="START WORKOUT" color={Colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={styles.statContainer}
onPress={() => navigation.navigate("StatsScreen")}
>
<AntDesign name="barschart" size={18} color={Colors.primary} />
<Text style={{ color: Colors.primary }}>Statistics</Text>
<AntDesign name="book" size={18} color={Colors.primary} />
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({})
StatsScreen.js
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
export default function StatsScreen() {
return (
<View style={styles.container}>
<Text display={exerciseCount} style={styles.exerciseText}>
{exerciseCount}
</Text>
<Text display={workoutCount} style={styles.workoutText}>
{workoutCount}
</Text>
</View>
);
}
const styles = StyleSheet.create({});
Home Screen Image
As far as I can tell, you're almost there! You're trying to get your 2 state
variables from the WorkoutTimer like this:
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
Unfortunatly this won't work :( . These two variables change throughout your
App's life-time and that kinda makes them "special".
In React, these kinds of variables need to be declared in a parent component
and passed along to all children, which are interested in them.
So in your current Setup you have a parent child relationship like:
HomeScreen -> WorkoutScreen -> WorkoutTimer.
If you move the variables to HomeScreen (HomeScreen.js)
export default function HomeScreen({ navigation }) {
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
you can then pass them along to WorkoutScreen or StatsScreen with something
like:
navigation.navigate("WorkoutScreen", { exerciseCount })
navigation.navigate("StatsScreen", { exerciseCount })
You'll probably have to read up on react-navigation's documentation for .navigate I'm not sure I remember this correctly.
In order to read the variable you can then:
export default function WorkoutScreen({ navigation }) {
const exerciseCount = navigation.getParam(exerciseCount);
return (
<View style={styles.container}>
<WorkoutTimer exerciseCount={exerciseCount} />
</View>
);
}
and finally show it in the WorkoutTimer:
export default function WorkoutTimer({ exerciseCount }) {
Of course that's just part of the solution, since you'll also have to pass
along a way to update your variables (setExerciseCount and setWorkoutCount).
I encourage you to read through the links I posted and try to get this to work.
After you've accumulated a few of these stateful variables, you might also want to look at Redux, but this is a bit much for now.
Your app looks cool, keep at it!
I ended up solving this problem with useContext if anyone is curious, it was hard to solve initially. But once I got my head around it, it wasn't too difficult to understand.
I created another file called exerciseContext with this code:
import React, { useState, createContext } from "react";
const ExerciseContext = createContext([{}, () => {}]);
const ExerciseProvider = (props) => {
const [state, setState] = useState(0);
//{ exerciseCount: 0, workoutCount: 0 }
return (
<ExerciseContext.Provider value={[state, setState]}>
{props.children}
</ExerciseContext.Provider>
);
};
export { ExerciseContext, ExerciseProvider };
and in App.js I used ExerciseProvider which allowed me to pass the data over the screens.
if (fontsLoaded) {
return (
<ExerciseProvider>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</ExerciseProvider>
);
} else {
return (
<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} />
);
}
}
I could call it with:
import { ExerciseContext } from "../components/ExerciseContext";
and
const [exerciseCount, setExerciseCount] = useContext(ExerciseContext);
This meant I could change the state too! Boom, solved! If anyone needs an explanation, let me know!
I think you have to use Mobx or Redux for state management. That will be more productive for you instead built-in state.

React Native got this error when i ran my code in phone using expo

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const ComponentsScreen = () => {
return (
<View>
<Text style={styles.textStyle}> Hello World! </Text>
</View>
);
}
const styles = StyleSheet.Create({
textStyle: {
fontsize: 30
}
});
export default ComponentsScreen;
Here is the picture link
Don't know what is the error here, help me please.
Change StyleSheet.Create C to lower case like :
const styles = StyleSheet.create({
will be fine.

FlatList doesn't show title tags at all

I'm quite new at react-redux, and currently am trying to build some stack application that will simply be some type of menu that will have it's sections with titles and when pressing the title it will show some simple text about it. So I'm using Flatlist for rendering all these sections, and the rendering works perfectly fine. It renders exactly 9 sections which is the number of my current objects, meaning that it has access to the data, plus when I try to change the font size of the title that these sections have to display, the size of the sections change as well, meaning that it definitely has access to the title strings as well, but for some reason the text is not showing up at all.
Here's how it looks:
I tried to change the text color, change size, change background color, add some padding, but section still doesn't show the text.
So here's where I implement the FlatList:
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import ListItem from './ListItem';
class LibraryList extends Component {
renderItem(library) {
return <ListItem library={library} />;
}
render() {
console.log(this.props.libraries);
return (
<FlatList
data={this.props.libraries}
renderItem={this.renderItem}
keyExtractor={(library) => library.id.toString()}
/>
);
}
}
const mapStateToProps = state => {
return { libraries: state.libraries };
};
export default connect(mapStateToProps)(LibraryList);
Here I implement the section itself:
import React, { Component } from 'react';
import { Text } from 'react-native';
import { CardSection } from './common';
class ListItem extends Component {
render() {
const { titleStyle } = styles;
return (
<CardSection>
<Text style={titleStyle}>
{this.props.library.title}
</Text>
</CardSection>
);
}
}
const styles = {
titleStyle: {
color: 'black',
fontSize: 20,
fontWeight: '600',
}
};
export default ListItem;
And here's the code for the section itself:
import React from 'react';
import { View } from 'react-native';
const CardSection = (props) => {
return (
<View style={styles.containerStyle}>
{props.children}
</View>
);
};
const styles = {
containerStyle: {
borderBottomWidth: 1,
padding: 5,
backgroundColor: '#fff',
justifyContent: 'flex-start',
flexDirection: 'row',
borderColor: '#ddd',
}
};
export { CardSection };
Expected result would be for titles to show up in those 9 sections.
In ListItem replace {this.props.library.title} with {this.props.library.item.title} within the Text component.

Categories