How to change a string in React-Native into a Component? - react-native

I have a var str = '<Text> something </Text>', can I render it into a Component?
I tried the following, but it doesn't work :(
var str = '<Text>test</Text>'
render(){
return(
<Text>{str}</Text>
)
}
Is there any way to do this, similar to dangerouslySetInnerHTML in React?
In my project, I get json by fetch just like
{
content:'<p>example</p>'
}
I want to replace the html element p or others into Textand so on by regular expressions, but the result is a string.
I also tried the react-native-html-render but it's document is incomprehensible and doesn't performs well.

You could create a function that converts the inner html of the response you get into a RN Text element.
var str = '<p>something here</p>'
var convertToText = (response) => {
var text = response.split(/[^A-Za-z]/).filter(x => x !== '').slice(1, -1).join(' ') //gives "something here";
return <Text>text</Text>;
}
convertToText(str) === <Text>something here</Text>

Set it as part of your state (e.g., this.state.str). In your constructor, give it a default value (e.g., this.state = {str: "test"}). Then in your function that does the fetch, do setState to change the value (e.g., this.setState({str: response})). Finally, in your render, do this:
render() {
return (
<View style={styles.container}>
<Text>{this.state.str}</Text>
</View>
);
}

var str = <Text>test</Text>;
render() {
return (
<View style={styles.container}>
{str}
</View>
);
}

Unfortunately the tag needs to be incorporated at compile-time, as JSX becomes transpiled into React.createElement calls. Or you can write the React.createElement calls yourself.
For example, you can create a parser than can walk the JSX tree in your server response. Something like
function parseResponseIntoJSXTree (string) {
// very psuedo example:
var type = parseJSXTag(string) // produces `Text`
var props = parseJSXTagProps(string) // produce any attribute=values
var innerContent = parseJSXContent(string) // produces 'something'
return React.createElement(type, props, children)
}
This only scratches the surface, as you'd need to walk the tree if there are child elements deeper than the root node.
Want my horrible, horrible answer?
Include babel in your bundle and do:
var renderableContent = require('babel-core', {presets: ['react-native'])
.transform('<Text>something</Text>')
Note - I highly discourage this, but it technically would work, unless babel requires on node dependencies that won't exist in the runtime (likely).

My requirement is similar, to make dynamic screens with arbitrary components, depending on JSON server response. Here is what I did:
const blockContent = [
{
type: 'text',
content: 'Some title',
size: 20,
color: 'black',
wrapperPadding: 10
},
{
type: 'text',
wrapperPadding: 10,
size: 16,
color: 'red',
content: 'Some text. Some text. Some text. Some text. Some text. '
},
{
type: 'text',
wrapperPadding: 10,
size: 16,
color: 'red',
content: 'Some text. Some text. Some text. Some text. Some text. '
},
{
type: 'link',
content: 'Some link',
size: 16,
color: 'blue',
wrapperPadding: 10,
url: 'http://www.google.com'
}
];
class CustomBlock extends Component {
openURL (url) {
Linking.openURL(url).catch(err => console.error('An error occurred', err));
}
render () {
return (
<View style={styles.container}>
{blockContent.map((item) => {
switch (item.type) {
case 'text':
return (
<View style={{padding: item.wrapperPadding}}>
<Text style={{fontSize: item.size, color: item.color}}>{item.content}</Text>
</View>
);
case 'link':
return (
<TouchableHighlight style={{padding: item.wrapperPadding}} underlayColor="lightgrey" onPress={this.openURL.bind(this, item.url)}>
<Text style={{fontSize: item.size, color: item.color}}>{item.content}</Text>
</TouchableHighlight>
);
}
})}
</View>
);
It's quite easy to declare all components you expect to use like I did with text and link, and to style them as well.

Related

How to use props in React Native?

I am new to React Native. I am wondering how to use props to work my code? Here is my code.
const weatherConditions = {
Rain: {
color: '#005BEA',
},
Clear: {
color: '#f7b733',
}
};
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.weather.color }
]}/>
);
};
But it does not work. Only the code below works. How to fix this? Help me.
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.Rain.color } // or { backgroundColor: weatherConditions.Clear.color }
]}
>
);
};
Your problem is that you are telling your component to look for weatherConditions.weather.color but the key weather is being interpreted literally. The component is looking for the weather key inside weatherConditions and it won't find it.
What you need to do is to do is:
backgroundColor: weatherConditions[weather].color
The brackets ensure weather is interpreted as a variable and not the word weather.
You pass the weather conditions via the prop weather and not weatherConditions. Can you try it with weather.Rain.color? It should work with that.

Recursion: How to fix error "Invariant Violation: Text strings must be rendered within a <Text> component."

I am trying to write a recursive function to display a discussion (conversation and replies) tree.
export default class SocialPost extends Component {
constructor(props) {
super(props);
}
replies = data => {
return data.map(item => {
return <View key={item._id}>{this.individualPost(item)}</View>;
});
};
individualPost = data => {
return (
<View>
<View style={styles.container}>
<Text>{data.comment}</Text>
</View>
{data.replies.length && this.replies(data.replies)}
</View>
);
};
render() {
return <View>{this.individualPost(this.props.data)}</View>;
}
}
data = [
{
replies: [
{
replies: [],
_id: "5cb07bb28346d729a25dfc38",
comment: "xyz"
}
],
_id: "5cb07b8a8346d729a25dfc37",
comment: "abc"
}
];
but I get this error instead: Invariant Violation: Text strings must be rendered within a component.
What can I do to fix this problem?
The problem is probably on this line: {data.replies.length && this.replies(data.replies)}.
If data.replies.length is 0, the part after the && will not be called. That means that React will attempt to render the string "0" there, which is not allowed outside of a <Text> component.
A solution would be to just do {this.replies(data.replies)}, and then return null from the replies() function when there are no replies.
data.comment doesn't exist on your first pass. Data is just an array, so react-native things you are trying to render something here w/o it existing. The error is telling you literally that only text can be rendered there. You are trying to render a data type.
individualPost = data => {
return (
<View>
<View style={styles.container}>
<Text>{data.comment}</Text> //your problem
</View>
{data.replies.length && this.replies(data.replies)}
</View>
);
};

How to Render Realm ListView with Sections Header

I'm using react native with realm db. The realm schema is as follows:
static schema = {
name: 'TodoItem',
primaryKey: 'id',
properties: {
id: {type: 'string'},
value: {type: 'string'},
Category: {type: 'string'},
completed: {type: 'bool', default: false},
createdTimestamp: {type: 'date'}
}
}
export const todoItemDS = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2, sectionHeaderHasChanged: (s1, s2) => s1 !== s2})
const mapStateToProps = (state, props) => ({
dataSource: todoItemDS.cloneWithRowsAndSections(todoItemsResults),
}
The ListView tag is as follows:
<ListView
dataSource={dataSource}
renderRow={this.renderRow.bind(this)}
renderSectionHeader={this.renderSectionHeader.bind(this)}
/>
and renderSectionHeader:
renderSectionHeader(sectionData, category) {
return (
<Text>{category}</Text>
)
}
renderRow(item){
const {dataSource, deleteTodoItem} = this.props
return (
<View style={{ justifyContent: 'space-between',flexDirection: 'row'}}>
<CheckBox onPress={(e) => this.completed(item.id,item.value,e.target.checked)} style={{marginTop: 15 }}checked={item.completed} />
<Text onPress={(e) => this.goToPageTwo(item.id)} style={{ alignSelf: 'center',flex:10}} >{item.value}
</Text>
<Button iconLeft large transparent primary style={{ height: 30 , flex:1 }} onPress={() => deleteTodoItem(item)}>
<Icon name="trash-o" style={{ color: 'red' }} />
</Button>
</View>)
}
I fill todoItems datasource from this function:
export const getTodoItems = () => {
const todoItems = TodoItem.get().sorted('createdTimestamp', true);
return todoItems
}
However, the rows and sections are rendered with empty sections text and empty rows text as shown in the image.
What is missing in this code and how can I render sections and rows correctly?
I added a listener to realm code that fills the data source as follows:
export const getTodoItems = () => {
console.log('create db:', Realm.path)
const itemData = {}
const todoItems = TodoItem.get().sorted('createdTimestamp', true).filtered('completed=false');
todoItems.addListener((items, changes) => {
// Update UI in response to inserted objects
changes.insertions.forEach((index) => {
if(itemData[items[index].Category]) {
itemData[items[index].Category].push(items[index])
} else
itemData[items[index].Category] = []//;
});
// Update UI in response to modified objects
changes.modifications.forEach((index) => {
});
// Update UI in response to deleted objects
changes.deletions.forEach((index) => {
// Deleted objects cannot be accessed directly
// Support for accessing deleted objects coming soon...
});
});;
todoItems.forEach((item) => {
if(itemData[item.Category]) {
itemData[item.Category].push(item)
} else
itemData[item.Category] = []//;
})
return itemData //todoItems
}
However, I can't see added items. The added item only shows up after adding another item. Any ideas?
The rendered SectionHeader is showing integer is because you didn't construct the dataSource in the right format, see the documentation here: link.
You need to construct something like:
const todoItemsData = {
categoryOne: [itemOne, itemTwo],
categoryTwo: [itemThree, itemFour]
}
But right now what you have is just an array of objects [realmItemOne, realmItemTwo], and if you just pass in an array of objects to construct the dataSource that gonna consumed by the cloneWithRowsAndSections, the category param in renderSectionHeader will becomes integer index accroding to here, Object.keys(arrayOfObjects) will return [0, 1, 2, ...]
So you want to map your todoItemsResults and construct the something like this
const itemData = {}
todoItemsResults.forEach((item) => {
if(itemData[item.Category] {
itemData[item.Category].push(item)
} else
itemData[item.Category] = [];
}
});
const mapStateToProps = (state, props) => ({
dataSource: todoItemDS.cloneWithRowsAndSections(itemData),
}
For the rendered row not showing any data issue, I think is due to the same problem, the item param you are calling within renderRow should be a data attribute for one of you todoItem object. You can try replace {item.value} within {item} to see what item is actually is in your setting. I believe this will be solved if you can construct the right data to feed your dataSource.
For your follow up comments regarding listerning to Realm update:
You can do something like this
class DummyPage extends Component {
constructor(props) {
super(props)
this.realmUpdated = this.realmUpdated.bind(this);
realm.objects('todoItem').addListener('change', this.realmUpdated);
}
componentWillUnmount() {
realm.objects('todoItem').removeListener('change', this.realmUpdated);
}
realmUpdated() {
this.forceUpdate();
}
render() {
//build your data source in here and render the sectionList
}
}

ReduxForm vs tcomb-form for Native Development

We used ReduxForm in our Web App and we are now trying to build a native app and it seems most of the guys are using tcomb-form.
ReduxForm can be used for native development. Not sure what's the biggest advantage of using tcomb-form.
I tried using it (Didn't explore all the advanced features yet) how ever once we define the schema the basic rendering of the controls is done for you. How ever if you want to customize how a control is displayed i am not sure whether tcomb-form supports it.
Ex:
Here in my Component's render() method:
let Form= tFormNative.form.Form;
let options= {
fields: {
}
};
let username= {
label: I18n.t('LoginForm.username'),
maxLength: 12,
editable: true,
};
let email = {
label: I18n.t('LoginForm.email'),
keyboardType: 'email-address',
editable: true,
};
let password = {
label: I18n.t('LoginForm.password'),
maxLength: 12,
secureTextEntry: true,
editable: true,
};
let registerForm = tFormNative.struct({
username: tFormNative.String,
email: tFormNative.String,
password: tFormNative.String,
passwordReEnter: tFormNative.String,
});
return(
<View style={styles.container}>
<Form
style={styles.textInput}
ref='form'
type={registerForm}
options={options}
/>
</View>
);
Now the Label and Control ( based on the type you provided in struct() ) are created.
How ever Lets say i want to use an Icon aling with the Label for each control i am not sure whether that is permitted.
Appreciate any inputs.
Thanks
Sateesh
If you would like to customize the entire control you can go for a factory, I'll post an example of a Slider i use for number input. I have not tried ReduxForm but i do like tcomb-form a lot, and I don't see anything that should not be doable in terms of customization. Good luck to you!
import React from 'react';
import { View, Text, Slider } from 'react-native';
import t from 'tcomb-form-native';
import Strings from '../config/strings.js';
var Component = t.form.Component;
class TcombSlider extends Component {
constructor(props) {
super(props);
var locals = super.getLocals();
}
getLocals() {
var locals = super.getLocals();
return locals;
}
getTemplate() {
let self = this;
return function (locals) {
var sliderConfig = locals.config.slider;
var stylesheet = locals.stylesheet;
var formGroupStyle = stylesheet.formGroup.normal;
var controlLabelStyle = stylesheet.controlLabel.normal;
var checkboxStyle = stylesheet.checkbox.normal;
var helpBlockStyle = stylesheet.helpBlock.normal;
var errorBlockStyle = stylesheet.errorBlock;
if (locals.hasError) {
formGroupStyle = stylesheet.formGroup.error;
controlLabelStyle = stylesheet.controlLabel.error;
checkboxStyle = stylesheet.checkbox.error;
helpBlockStyle = stylesheet.helpBlock.error;
}
var label = locals.label ? <Text style={controlLabelStyle}>{locals.label}</Text> : null;
var help = locals.config.help ? <Text style={helpBlockStyle}>{locals.config.help}</Text> : null;
var error = locals.hasError && locals.error ? <Text accessibilityLiveRegion="polite" style={[errorBlockStyle, {marginTop: 2}]}>{locals.error}</Text> : null;
return (
<View style={formGroupStyle}>
{label}
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'flex-end', paddingRight: 15}}>
<Text>{locals.value}m</Text>
</View>
<View style={{marginBottom: 5}}>
<Slider
minimumValue={sliderConfig.minimumValue}
maximumValue={sliderConfig.maximumValue}
step={sliderConfig.step}
value={locals.value}
onValueChange={(value) => self._onChange(locals, value)}/>
</View>
{help}
{error}
</View>
);
}
}
_onChange(locals, val) {
locals.onChange(val);
}
}
export default TcombSlider
And use it like this:
const options = {
fields: {
search_range: {
config: {
slider: {
maximumValue: 1000,
minimumValue: 0,
step: 5,
disabled: false,
},
},
factory: TcombSlider,
},
...
},
}
I'm posting this as an example because i had a hard time putting together my first factory.
I would leave this as a comment if I could, but my reputation isn't high enough.
You need to override your form's local template with a custom template which adds an Icon and TextInput for each field.
This gist shows how you can do this.

Rich ReactNative TextInput

Any way to create a "rich" TextInput in React Native? Maybe not a full blown wysiwyg, but maybe just change the text color of various pieces of text; like the #mention feature on Twitter or Facebook.
Solution is that you can use <Text> elements as children in a <TextInput> like this:
<TextInput>
whoa no way <Text style={{color:'red'}}>rawr</Text>
</TextInput>
This question was asked a while ago but I think my answer can help other people looking for how to color the #mention part of a string. I am not sure if the way I did it is clean or the "react" way but here is how I did it:
I take the inputed string and split it with an empty space as the separator. I then loop through the array and if the current item matches the pattern of #mention/#user, it is replaced with the Text tag with the styles applied; else return the item. At the end, I render inputText array (contains strings and jsx elements)inside the TextInput element. Hope this helps!
render() {
let inputText = this.state.content;
if (inputText){
inputText = inputText.split(/(\s)/g).map((item, i) => {
if (/#[a-zA-Z0-9]+/g.test(item)){
return <Text key={i} style={{color: 'green'}}>{item}</Text>;
}
return item;
})
return <TextInput>{inputText}</TextInput>
Have a look at the TokenizedTextExample from the react-native docs. I think that'll get you close to what you're looking to do. The relevant code follows:
class TokenizedTextExample extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: 'Hello #World'};
}
render() {
//define delimiter
let delimiter = /\s+/;
//split string
let _text = this.state.text;
let token, index, parts = [];
while (_text) {
delimiter.lastIndex = 0;
token = delimiter.exec(_text);
if (token === null) {
break;
}
index = token.index;
if (token[0].length === 0) {
index = 1;
}
parts.push(_text.substr(0, index));
parts.push(token[0]);
index = index + token[0].length;
_text = _text.slice(index);
}
parts.push(_text);
//highlight hashtags
parts = parts.map((text) => {
if (/^#/.test(text)) {
return <Text key={text} style={styles.hashtag}>{text}</Text>;
} else {
return text;
}
});
return (
<View>
<TextInput
multiline={true}
style={styles.multiline}
onChangeText={(text) => {
this.setState({text});
}}>
<Text>{parts}</Text>
</TextInput>
</View>
);
}
}
You will have to use regex in order to achieve that behaviour. Someone has already created package for that have a look at react-native-parsed-text
This library allows you to parse a text and extract parts using a RegExp or predefined patterns. Currently there are 3 predefined types: url, phone and email.
Example from their github
<ParsedText
style={styles.text}
parse={
[
{type: 'url', style: styles.url, onPress: this.handleUrlPress},
{type: 'phone', style: styles.phone, onPress: this.handlePhonePress},
{type: 'email', style: styles.email, onPress: this.handleEmailPress},
{pattern: /Bob|David/, style: styles.name, onPress: this.handleNamePress},
{pattern: /\[(#[^:]+):([^\]]+)\]/i, style: styles.username, onPress: this.handleNamePress, renderText: this.renderText},
{pattern: /42/, style: styles.magicNumber},
{pattern: /#(\w+)/, style: styles.hashTag},
]
}
>
Hello this is an example of the ParsedText, links like http://www.google.com or http://www.facebook.com are clickable and phone number 444-555-6666 can call too.
But you can also do more with this package, for example Bob will change style and David too. foo#gmail.com
And the magic number is 42!
#react #react-native
</ParsedText>