How to modify child Component content? - javascript

I have a Component like this:
<MyTimer props={+new Date()}>
<span className="year" formatter="yyyy" />
<span className="mon" formatter="mm" />
<span className="day" formatter="dd" />
</MyTimer>
MyTimer is Component like this(I just ignore some other code):
render() {
/**
* this code may not work, I just do what I think.
*/
return (<div>
{ this.props.children.map((child, i) =>
// child.children=[util.date(child.formatter)])
// set child.props.children as "util.date(child.formatter)"
}
</div>)
}
I want to change the content of span in component, which should result like this:
<span class="year">2016</span>
<span class="mon">07</span>
<span class="day">06</span>
So why do I need this, because I can change the child element type as I like:
<MyTimer props={+new Date()}>
<div className="year" formatter="yyyy" />
<span className="mon" formatter="mm" />
<p className="day" formatter="dd" />
</MyTimer>
I just do not care about what the child element is, and also className.

React has a feature called "contexts", which allow a parent component to implicitly pass some data to children.
The actual formatting then can happen in a special "formatter" component. Resulting in something like this:
import React, { Component, PropTypes } from 'react';
import moment from 'moment';
class MyTimer extends Component {
static childContextTypes = {
date: PropTypes.object
};
// expose some props to children
getChildContext() {
return {
date: this.props.date
};
}
render() {
return <div>{this.props.children}</div>;
}
}
class TimerPart extends Component {
// this is REQUIRED to receive the date from the parent
static contextTypes = {
date: PropTypes.object
};
render() {
// use it like `this.context.date`
return <span>{moment(this.context.date).format(this.props.format)}</span>;
}
}
export default class App extends Component {
render() {
return (
<MyTimer date={new Date()}>
<TimerPart format="YYYY"/>
<TimerPart format="MM"/>
<TimerPart format="DD"/>
</MyTimer>
)
}
}

Related

How to properly render Component after this.setState in React

I have this React component
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
this.setState({
resources: this.props.location.resources,
});
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
It gets the resources from the Link component, and that works fine. If I check out the state of the Component from the dev tools, the state looks right. And I thought with my logic this should work. So firstly, the state is empty, the component gets rendered, since the state is empty it doesn't render any components. Then, setState gets called, it gets all the resources and saves them into the state, and then the component would re-render, and it should work, but it doesn't. I'm getting a TypeError: Cannot read property 'map' of undefined error. What is the correct way to do this and how do I fix this?
Try this code:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: this.props && this.props.location && this.props.location.resources?this.props.location.resources:[],
};
}
componentDidMount() {
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or use directly props
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{
this.props && this.props.location &&
this.props.location.resources
?this.props.location.resources.map(res => (
<div>test</div>
))
:null
}
</div>
);
}
}
Or use componentWillReceiveProps or getDerivedStateFromProps life cycle methods.
Check this.props.location.resources is array.
See more: https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607
For first check is this.props.location.resources array, or if data type changes you can add checking, you can use lodash isArray or with js like this:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
Array.isArray(this.props.location.resources) {
this.setState({
resources: this.props.location.resources,
});
}
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or you can just use hooks like this:
import React, { useState, useEffect } from "react";
export default function ResourceForField({location}) {
const [ resources, setResources ] = useState([]);
useEffect(() => {
if (location && Array.isArray(location.resources)) {
setResources(location.resources)
}
}, [location]);
return (
<div>
{resources.map(res => (
<div>test</div>
))}
</div>
);
}
If the internal state of ResourceForField doesn't change and always equals to its prop, you shouldn't save the prop in the state. You can instead create a pure functional component.
Also note that there's nothing preventing you from initializing the state from the props in constructor method. i.e. you're not required to wait for the component to mount in order to access the props.
So, I'd write the following component for ResourceForField:
function ResourceForField({resources = []}) {
return (
<div>
{
resources.map(res => (<div>test</div>))
}
</div>
);
}

React-Chat-Widget props not forwarded

I am using the react-chat-widget and trying to call a function in the base class of my application from a custom component rendered by the renderCustomComponent function of the widget.
Here is the code for the base class:
import React, { Component } from 'react';
import { Widget, handleNewUserMessage, addResponseMessage, addUserMessage, renderCustomComponent } from 'react-chat-widget';
import 'react-chat-widget/lib/styles.css';
import Reply from './Reply.js';
class App extends Component {
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, this.correct);
}
correct = () => {
console.log("success");
}
render() {
return (
<div className="App">
<Background />
<Widget
handleNewUserMessage={this.handleNewUserMessage}
/>
</div>
);
}
}
export default App;
And here is the code for the custom component Reply:
import React, { Component } from 'react';
import { Widget, addResponseMessage, renderCustomComponent, addUserMessage } from 'react-chat-widget';
class Reply extends Component {
constructor(props) {
super(props);
}
sendQuickReply = (reply) => {
console.log(this.props); //returns empty object
//this.props.correct(); <-- should be called
};
render() {
return (
<div className="message">
<div key="x" className={"response"}onClick={this.sendQuickReply.bind(this, "xx")}>xx</div>
</div>)
}
}
export default Reply;
According to ReactJS call parent method this should work. However, when I print the this.props object it is empty, although the documentation of the renderCustomComponent method states that the second argument of the component to render are the props that the component needs (in this case the parent class function).
Where have I gone wrong?
The second parameter is considered as props, but it is expected to be an object. you would pass it like
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, {correct: this.correct});
}

ReactJs: Dynamic component loading passing properties

I have a library of ReactJS components, as the following code:
components.js
class Comp1 extends Component {
render () {
return (
<div>Component 1 Text: {this.props.text}</div>
);
}
}
class Comp2 extends Component {
render () {
return (
<div>Component 2 Text: {this.props.text}</div>
);
}
}
export components = {
Comp1,
Comp2
}
The main component needs to choose wich one to render based on a passed property:
main.js
import { components } from './components';
class Main extends Component {
getComponent = (name) => {
return components[name];
};
render () {
let comp = this.getComponent(this.props.componentName);
return (
<div>
<comp <=== HOW TO CALL THE GIVEN COMPONENT PASSING ITS PROPERTY
text={'This is component' + this.props.componentName }
/>
</div>
);
}
}
class App extends Component {
render () {
return (
<div>
<Main componentName='Comp1' /> // Or 'Comp2'
</div>
);
}
}
}
I need in the main code to render the component and pass its properties, but I can´t make it work (see the comments on code). A simple {comp} renders the component, but I need to be able to pass its properties accordingly.
What I´ve tried:
{comp text={'This is component' + this.props.componentName}}
<comp text={'This is component' + this.props.componentName}/>
None of them worked.
You component name need to begin with a UpperCase character. so it should look like
import { components } from './components';
class Main extends Component {
getComponent = (name) => {
return components[name];
};
render () {
let Comp = this.getComponent(this.props.componentName);
return (
<div>
<Comp text={'This is component' + this.props.componentName }
/>
</div>
);
}
}
class App extends Component {
render () {
return (
<div>
<Main componentName='Comp1' /> // Or 'Comp2'
</div>
);
}
}
}

How to override className on extended component?

I am trying to position this component differently on a certain page. But when I provide it with another className property it is only using the original class's styling that was provided when declaring the component.
Component:
import React, { Component } from 'react';
import styles from './label.css';
class Label extends Component {
render() {
return (
<div className={styles.labelClass} />
);
}
}
export default Label;
Page where I want to position it differently:
import React, { Component } from 'react';
import styles from './page.css';
import Label from '../common/label.jsx';
class Page extends Component {
render() {
return (
<div>
<Label className={styles.positionLeft} />
</div>
);
}
}
export default Page;
Normally I would do this with custom styling but I have to use media
queries so this isn't possible in this situation.
Since <Label> is a custom component, you can to manually pass the className prop down.
This is a good use case for default props!
class Label extends Component {
render() {
return (
<div className={this.props.className} />
);
}
}
Label.defaultProps = {
className: styles.labelClass
}
That way, if no className is provided to Label, it will use the labelClass style, otherwise, it will use the prop.
I fixed it by adding another optional property customClass to the component.
Label
import React, { Component } from 'react';
import styles from './label.css';
class Label extends Component {
render() {
return (
<div className={styles.labelClass + ' ' + this.props.customClass} />
);
}
}
export default Label;
Page
import React, { Component } from 'react';
import styles from './page.css';
import Label from '../common/label.jsx';
class Page extends Component {
render() {
return (
<div>
<Label customClass={styles.positionLeft} />
</div>
);
}
}
export default Page;
You need to explicitly reference the className property from Label's props - try:
import React, { Component } from 'react';
import styles from './label.css';
class Label extends Component {
render() {
let { className } = this.props
if (!className) {
className = styles.labelClass
}
return (
<div className={className} />
);
}
}
export default Label;

(React + Meteor) Edit content in another div?

I currently have this as my "index" for my Meteor app:
import React from 'react';
export const App = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired,
},
render() {
return <div className="app-div">
**<Foo/>**
**<Bar/>**
{ this.props.children }
</div>;
},
});
I am wondering if I can somehow change content of "Foo", through code in "Bar".
Essentially, "Foo" will have code like this:
export class Foo extends React.Component {
render() {
return(
<div className="test">TEXT TO BE REPLACED</div>
);
}
}
Bar, will also have similar code:
export class Bar extends React.Component {
render() {
// Code goes here to change that div "test"'s content in Foo^^^^^^
return(
<div>...</div>
);
}
}
But I need to have some sort of code that changes the "TEXT TO BE REPLACED". Is there a way to do this somehow? Maybe with the react DOM or something? I am kind of brute forcing my way through this, so I may not know basic fundamentals, sorry
Thanks in advance
In React+Meteor, communication between two siblings component should be done through their parent.
For your case, I would use a state in App component to store the content of "TEXT TO BE REPLACE", and a function inside App component to update that state content:
import React from 'react';
export const App = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired,
},
getInitialState() {
return {
fooText: '',
};
},
updateFooText(value) {
this.setState({
fooText: value,
});
},
render() {
return (
<div className="app-div">
<Foo text={this.state.fooText} />
<Bar updateFooText={this.updateFooText} />
{ this.props.children }
</div>
);
},
});
export class Foo extends React.Component {
render() {
return (
<div className="test">{this.props.text}</div>
);
}
}
export class Bar extends React.Component {
onChangeHandler(e) {
this.props.updateFooText(e.target.value);
},
render() {
// Code goes here to change that div "test"'s content in Foo^^^^^^
return (
<div>
<input type="text" onChange={this.onChangeHandler} />
</div>
);
}
}

Categories

Resources