David Linke David Linke - 16 days ago 5
React JSX Question

React not propagating state to child component

I am getting started with react and i have come up with the following issue.

The component below renders a list of fonts that are defined within an array i get from the "Fonts" import.

To display the fonts i use an array of diverse messages that are specified in the "Messages" import.

When i render the list, the renderFonts function calls a sub-component "FontSample" and passes the font object and the message to render as properties.

Then i got this function "addGreeting", that allows you to set one specific message to display it on all fonts. I do it by changing the the array messages to an array that contains only one message within the state of the component.

The issue is, that although i can verify that the state changed correctly, the render gets triggered, and the subcomponent is called, the messages within the font list rendered do not change at all.

I am pretty sure this is some basic React behavior i haven't learned yet, but my good friend Google has been evasive on this one.

Your patience is appreciated as i am a Designer who happens to code.

... And as suggested, the codepen with the example: http://codepen.io/davelinke/pen/YppzNW

import React, { Component } from 'react';

import FontSample from './FontSample';
import AddGreeter from './AddGreeter';
import Fonts from './Fonts';
import Messages from './Messages';

import './FontList.css';

class FontList extends Component {
constructor(props){
super(props);
let theFonts = Fonts;
this.state = {
fonts:theFonts,
messages:Messages
};
this.renderFonts = function(){
let demMessages = this.state.messages;
let demFonts = this.state.fonts;
return demFonts.map((font,i) => (<FontSample key={i} font={font} message={demMessages[i % demMessages.length]} />));
}.bind(this);
this.addGreeting = function(newName){
console.log('hello ' + newName);
this.setState({ messages: [newName] });
}.bind(this);
}
render() {
return (
<div>
<AddGreeter addGreeting={this.addGreeting} />
<div className="hello-world-list">
{this.renderFonts()}
</div>
</div>
);
}
}

export default FontList;


Fonts.js (the regular google api json data within an array)

let Fonts = [

{
"kind": "webfonts#webfont",
"family": "Roboto",
"category": "sans-serif",
"variants": [
"100",
"100italic",
"300",
"300italic",
"regular",
"italic",
"500",
"500italic",
"700",
"700italic",
"900",
"900italic"
],
"subsets": [
"greek-ext",
"latin-ext",
"latin",
"vietnamese",
"cyrillic-ext",
"cyrillic",
"greek"
],
"version": "v15",
"lastModified": "2016-10-05",
"files": {
"100": "http://fonts.gstatic.com/s/roboto/v15/7MygqTe2zs9YkP0adA9QQQ.ttf",
"300": "http://fonts.gstatic.com/s/roboto/v15/dtpHsbgPEm2lVWciJZ0P-A.ttf",
"500": "http://fonts.gstatic.com/s/roboto/v15/Uxzkqj-MIMWle-XP2pDNAA.ttf",
"700": "http://fonts.gstatic.com/s/roboto/v15/bdHGHleUa-ndQCOrdpfxfw.ttf",
"900": "http://fonts.gstatic.com/s/roboto/v15/H1vB34nOKWXqzKotq25pcg.ttf",
"100italic": "http://fonts.gstatic.com/s/roboto/v15/T1xnudodhcgwXCmZQ490TPesZW2xOQ-xsNqO47m55DA.ttf",
"300italic": "http://fonts.gstatic.com/s/roboto/v15/iE8HhaRzdhPxC93dOdA056CWcynf_cDxXwCLxiixG1c.ttf",
"regular": "http://fonts.gstatic.com/s/roboto/v15/W5F8_SL0XFawnjxHGsZjJA.ttf",
"italic": "http://fonts.gstatic.com/s/roboto/v15/hcKoSgxdnKlbH5dlTwKbow.ttf",
"500italic": "http://fonts.gstatic.com/s/roboto/v15/daIfzbEw-lbjMyv4rMUUTqCWcynf_cDxXwCLxiixG1c.ttf",
"700italic": "http://fonts.gstatic.com/s/roboto/v15/owYYXKukxFDFjr0ZO8NXh6CWcynf_cDxXwCLxiixG1c.ttf",
"900italic": "http://fonts.gstatic.com/s/roboto/v15/b9PWBSMHrT2zM5FgUdtu0aCWcynf_cDxXwCLxiixG1c.ttf"
}
}, {
"kind": "webfonts#webfont",
"family": "Open Sans",
"category": "sans-serif",
"variants": [
"300",
"300italic",
"regular",
"italic",
"600",
"600italic",
"700",
"700italic",
"800",
"800italic"
],
"subsets": [
"greek-ext",
"latin-ext",
"latin",
"vietnamese",
"cyrillic-ext",
"cyrillic",
"greek"
],
"version": "v13",
"lastModified": "2016-10-05",
"files": {
"300": "http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTS3USBnSvpkopQaUR-2r7iU.ttf",
"600": "http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSi3USBnSvpkopQaUR-2r7iU.ttf",
"700": "http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzC3USBnSvpkopQaUR-2r7iU.ttf",
"800": "http://fonts.gstatic.com/s/opensans/v13/EInbV5DfGHOiMmvb1Xr-hi3USBnSvpkopQaUR-2r7iU.ttf",
"300italic": "http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxi9-WlPSxbfiI49GsXo3q0g.ttf",
"regular": "http://fonts.gstatic.com/s/opensans/v13/IgZJs4-7SA1XX_edsoXWog.ttf",
"italic": "http://fonts.gstatic.com/s/opensans/v13/O4NhV7_qs9r9seTo7fnsVKCWcynf_cDxXwCLxiixG1c.ttf",
"600italic": "http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxpZ7xm-Bj30Bj2KNdXDzSZg.ttf",
"700italic": "http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxne1Pd76Vl7zRpE7NLJQ7XU.ttf",
"800italic": "http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxg89PwPrYLaRFJ-HNCU9NbA.ttf"
}
}
];
export default Fonts;


Messages.js (an array of messages)

let Messages = [
'All their equipment and instruments are alive.',
'A red flair silhouetted the jagged edge of a wing.',
'I watched the storm, so beautiful yet terrific.',
'Almost before we knew it, we had left the ground.',
'A shining crescent far beneath the flying vessel.',
'It was going to be a lonely trip back.',
'Mist enveloped the ship three hours out from port.',
'My two natures had memory in common.',
'Silver mist suffused the deck of the ship.',
'The face of the moon was in shadow.',
'She stared through the window at the stars.',
'The recorded voice scratched in the speaker.',
'The sky was cloudless and of a deep dark blue.',
'The spectacle before us was indeed sublime.',
'Then came the night of the first falling star.',
'Waves flung themselves at the blue evening.'
];

export default Messages;


FontSample.js (the font sample component)

import React, { Component } from 'react';
import './FontSample.css';
class FontSample extends Component {
constructor(props) {
super(props);
this.state = {
message: props.message,
style:{
fontFamily:props.font.family
},
fontName:props.font.family,
fontUrl:'https://fonts.googleapis.com/css?family='+props.font.family
};
}
render() {
return (
<div>
<link href={this.state.fontUrl} rel="stylesheet" type="text/css"></link>
<div className="font-wrapper">
<div className="font-name">{this.state.fontName}</div>
<div className="font-sample" style={this.state.style}>{this.state.message}</div>
</div>
</div>
);
}
}

export default FontSample;


AddGreeter.js (the component that holds the ui to update the message)

import React, { Component } from 'react';
import './AddGreeter.css';

class AddGreeter extends Component {
constructor(props) {
super(props);
this.state = { greetingName: '' };
this.handleUpdate = function(event) {
this.setState({ greetingName: event.target.value });
}.bind(this);
this.addGreeting = function(){
this.props.addGreeting(this.state.greetingName);
this.setState({ greetingName: '' });
}.bind(this);
}
render() {
return (
<div className="add-greeter">
<input type="text" onChange={this.handleUpdate} value={this.state.greetingName}/>
&nbsp;&nbsp;
<button onClick={this.addGreeting}>Add</button>
</div>
);
}
}

export default AddGreeter;


Edited to include more information about the script.

Answer

The problem is in the FontSample component. You show your message through this.state.message.

class FontSample extends Component {
    constructor(props) {
        super(props);
        this.state = {
            message: props.message,
            style:{
                fontFamily:props.font.family
            },
            fontName:props.font.family,
            fontUrl:'https://fonts.googleapis.com/css?family='+props.font.family
        };
    }
    render() {
        return (
            <div>
                <link href={this.state.fontUrl} rel="stylesheet" type="text/css"></link>
                <div className="font-wrapper">
                    <div className="font-name">{this.state.fontName}</div>
                    <div className="font-sample" style={this.state.style}>{this.state.message}</div>
                </div>
            </div>
        );
    }
}

Here, state is set from the props once, when component is created, because it is set in constructor. When your state for messages is changed, it is passed as prop to the FontSample, but local state of FontSample is never set again. In order to set the state of FontSample, you can use lifecycle method componentWillRecieveProps, like this:

class FontSample extends React.Component {
    constructor(props) {
        super(props);
        console.log(props.message);
        this.state = {
            message: props.message,
            style:{
                fontFamily:props.font.family
            },
            fontName:props.font.family,
            fontUrl:'https://fonts.googleapis.com/css?family='+props.font.family
        };
    }
    componentWillReceiveProps(newProps){
      this.setState({message: newProps.message})
    }
    render() {
        return (
            <div>
                <link href={this.state.fontUrl} rel="stylesheet" type="text/css"></link>
                <div className="font-wrapper">
                    <div className="font-name">{this.state.fontName}</div>
                    <div className="font-sample" style={this.state.style}>{this.state.message}</div>
                </div>
            </div>
        );
    }
}

However, in the FontSample component, you don't need to use state at all, you can use props instead, then there is no need to set state every time new props arrive

Here is codepen sample: http://codepen.io/magnetic/pen/GNNgvX?editors=0010

similar question answered here: How can i keep state in a React component using ES6

Comments