Dynamic templates in React - ecmascript-6

I want to create a simple Wizard component that should be as generic as possible.
I want to inject 2 params for body content: template (with some logic included) and its context.
export class ParentClass extends React.Component {
render() {
let template = `Some text: {this.context.testFunc()}`;
let context = new TestContext();
return (
<Wizard template={template} context={context} />
);
}
}
export class TestContext {
testFunc() {
return "another text";
}
}
export class Wizard extends React.Component {
context: null;
constructor(props) {
super(props);
this.context = this.props.context;
}
render() {
return (
<div>
{this.props.template}
</div>
);
}
}
The problem is the logic included in template does not execute (it writes everything as a string in Wizard).
I use ES2015 and Babel for compiling.

When you are using template literals you have to use $.
For example
`Some text: {this.context.testFunc()}`;
should be
`Some text: ${this.context.testFunc()}`;
Also, I think that you got a problem in your render function
render() {
let template = `Some text: {this.context.testFunc()}`;
let context = new TestContext();
return (
<Wizard template={template} context={context} />
);
}
should be
render() {
let context = new TestContext();
let template = `Some text: ${context.testFunc()}`;
return (
<Wizard template={template} context={context} />
);
}
Hope it helps!

Related

Passing props through a "template" component

I'm building a template component using React with TypeScript but i'm facing an issue i'm unable to solve. I'm posting this in case anyone knows how to approach it.
My project has a MyComp component that invokes TemplateComp using a subcomponent GraphComp and the data that Graph requires.
TemplateComp invokes and stylises the Graph subcomponent plus adds some props that are needed (such as customPropertyA) next to graphData.
GraphComp is requiring certain parameters that graphData needs to render properly.
The issue i'm facing is related to the types definition from GraphComp to MyComp while passing through TemplateComp. It may seem that (because TemplateComp defines graphData as any, as it is unknown to it) MyComp understands that graphData can also be any, but in reality it should be equal to the properties that Graph is requiring as Props (but not all of them).
Is there any way to let MyComp and TemplateComp infer the types that GraphComp is asking for?
Here is my code:
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={GraphComp}
graphData={{
value: 0
}}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: ElementType
graphData: any
},
{}
> {
customPropertyA = 'hello'
render() {
const { graphData, Graph } = this.props
return (
<div>
<Graph {...graphData} customPropertyA={this.customPropertyA} />
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
I'm perfectly fine with modifying how these components work. However, i still need the 3 layer approach and to be able to define GraphComp's props from within MyComp and TemplateComp separately.
For those wondering how to approach this, i managed to fix it by calling the mounted JSX instead of mounting it in TemplateComp and removed properties from it as they can also be described in MyComp.
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={<GraphComp value={0} customPropertyA={"hello"} />}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: JSX.Element
},
{}
> {
render() {
const { Graph } = this.props
return (
<div>
{Graph}
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}

react-native: Using async/await with setState

I have this simple code.
export default class ProductDetail extends Component {
constructor(props) {
super(props);
this.state = { test: null,id:this.props.navigation.state.params.productId };
console.log(1);
}
componentWillMount() {
console.log(2);
this.getProductRequest(this.state.id);
console.log(3);
}
async getProductRequest(id) {
try {
let api_token = await AsyncStorage.getItem('apiToken')
let response = await fetch('...')
let json = await response.json();
this.setState({test: json});
} catch(error) {
//
}
}
render() {
console.log(4);
console.log(this.state.test);
return (
<View><Text>test</Text></View>
);
}
}
Now, I checked it in a debuger:
I expect this result:
1
2
3
4
{data: {…}, status: "success", ...}
But I get this:
1
2
3
4
null
4
{data: {…}, status: "success", ...}
I think it means render() run twice!
how can I handle this error?
I think it means render() run twice!
It does: Once before your async result is available, and then again when it is and you use setState. This is normal and expected.
You can't hold up the first render waiting for an async operation to complete. Your choices are:
Have the component render appropriately when it doesn't have the data yet. Or,
If you don't want to render the component at all until the async operation has completed, move that operation in to the parent component and only render this component when the data is available, passing the data to this component as props.
Just to add to T.J Crowder's answer, one thing I like to do is return an ActivityIndicator if data is not received yet.
import {
View,
Text,
ActivityIndicator
} from 'react-native';
export default class ProductDetail extends Component {
... your code ...
render() {
if (!this.state.test) {
return <ActivityIndicator size='large' color='black' />
}
console.log(4);
console.log(this.state.test);
return (
<View><Text>test</Text></View>
);
}
}

How to render image from JSON string into my react application?

This is my JSON string:
{"blocks":
[{
"key":"mm3r",
"text":"",
"type":"unstyled",
"depth":0,
"inlineStyleRanges":[],
"entityRanges":[],
"data":{}}],
"entityMap":
{
"0":
{
"type":"IMAGE",
"mutability":"MUTABLE",
"data":{"src":"https://t00.deviantart.net/1vvQLZ9mzHkH16x62-aLZmIlY1I=/fit-in/300x900/filters:no_upscale():origin()/pre00/e334/th/pre/f/2014/270/7/e/protect__luffy_x_suicidal_reader__by_wulferious-d80s516.png",
"height":"auto",
"width":"auto"
}
}
}
}
Following is my react component:
let theObject;
class Blog extends Component{
constructor(props){
super(props);
this.blogContent = props.blogContent;
this.blogId = props.blogId;
}
This is where I'm doing JSON.parse
componentWillMount(){
theObject = JSON.parse( this.blogContent );
console.log(this.blogContent);
}
Here is my render part. Presently I'm just calling theObject.blocks[i].text which works fine but I don't know how to render the image. In short how should I call it??
render(props) {
return(
<div className = "blog header">
{
Array.from(Array(theObject.blocks.length), (e, i) => {
return <p key={i}>{theObject.blocks[i].text }</p>
})}
</div>
);
}
}
Blog.proptypes = {
blogContent: Proptypes.string
}
export default Blog;
Seems like you would need to iterate over the entityMap values and use the data.src for each of them for the image source.
Something along these lines:
Object.values(theObject.blocks[i].entityMap).map(val => <img src={val.data.src} />)

In React.js array value is not passing properly via props?

I have a react app that has two components one Customer and another called Tags. The Customer sends its state's tags value to Tags. As following:
Customer.jsx
import React from "react";
import Tags from "./Tags.jsx";
export default class Customer extends React.Component {
constructor(props) {
super(props);
this.state = {customer:''};
}
componentDidMount(){
const url = `http://localhost:3000/api/customers/${this.props.params.id}`
fetch(url)
.then(res=>res.json())
.then(data=>{
console.log(data);
this.setState({
customer: data
});
})
.catch(error=>{
console.log(error)
});
}
render() {
return (
<div>
Company Name :{this.state.customer.companyName}
Customer Name :{this.state.customer.name}
Tags: <Tags tags={this.state.customer.tags} />
</div>
);
}
}
Tags.jsx
import React from "react";
export default class Tags extends React.Component {
constructor(props) {
super(props);
}
render() {
let tags = this.props.tags.map(tag=>{
return (<span>tag</span>);
});
return (
<div>
{tags}
</div>
);
}
}
When I run the code I get, "TypeError: Cannot read property 'map' of undefined(…)". If I replace below from Tags.jsx
let tags = this.props.tags.map(tag=>{
return (<span>tag</span>);
});
with
console.log(this.props.tags);
The output is an array.
What is happening? I really do not understand. What can I do?
In the constructor of Customer you are defining the state of a customer as a string, not an object. You should change it to reflect the actual customer properties, ie:
this.state = {customer: {name: '', tags: []}};
It's undefined, because of you're making an API call and the data isn't returned yet.
In your Customer component you can check the existance of this.state.customer.tags and if exist - then render the tags.
Somethling like that:
{ this.state.customer.tags ? <Tags tags={this.state.customer.tags} /> : null }

Invoke function from React component declared as variable

How to invoke React component's function when this component is given in variable? I have a Parent that passes Test class into Child component, and this child wants to change something in Test.
export class Parent extends React.Component {
render() {
let test = (<Test />);
return (<Child tester={test} />);
}
}
export class Child extends React.Component {
render() {
this.props.tester.setText("qwerty"); // how to invoke setText, setState or something like that?
return ({this.props.tester});
}
}
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
text: this.props.text || ""
};
}
setText(text) {
this.setState({ text: text });
}
render() {
return (<div>{this.state.text}</div>);
}
}
I think you should think about life cycle of react components.
Please try the code below(I just added logging), and observe logs carefully.
export class Parent extends React.Component {
render() {
let test = (<Test />);
return (<Child tester={test} />);
}
}
export class Child extends React.Component {
render() {
console.log("Child render"); // <= logging added!
// this.props.tester.setText("qwerty");
// What kind of object is 'this.props.tester(= <Test />)' here???
return ({this.props.tester});
}
}
export class Test extends React.Component {
constructor(props) {
super(props);
console.log("Test constructor"); // <= logging added!
this.state = {
text: this.props.text || ""
};
}
setText(text) {
// this.setState({ text: text });
// this is another problem. We cannot call setState before mounted.
this.state.text= text;
}
render() {
return (<div>{this.state.text}</div>);
}
}
If so, you will see 2 important facts.
'Test' component is not instantiated yet, when you call 'setText'.
How can we call a method of object which is not instantiated? Cannot!
this means 'this.props.tester' is not an instance of 'Test' component.
But if you really want to exec your code, modify Child.render like this.
render() {
var test = new Test({props:{}});
// or even this can work, but I don't know this is right thing
// var test = new this.props.tester.type({props:{}});
test.setText("qwerty");
return test.render();
}
But I don't think this is a good way.
From another point of view, one may come up with an idea like,
render() {
// Here, this.props.tester == <Test />
this.props.tester.props.text = "qwerty";
return (this.props.tester);
}
but of course it's not possible, because 'this.props.tester' is read-only property for Child.