Ivo
Ivo

Reputation: 2510

Meteor / React / React-Router /CreateContainer - can't get this.state to be set according to props when using params

I am using Meteor with React, react-Router and createContainer. I have an issue with one component, when I go to the link :

127.0.0.1:27017/testimonial/edit/id

It seems that in the constructor the props is still undefined while trying to define the state: how come ? Shouldn't the component rerender when the props are sent anyway ?

I tried to solve the probleme with the componentWillReceiveProps(nextProps) hook but it didnt work either.

it gives me the following error (line 14 is the this.state function in the constructor method):

Uncaught TypeError: Cannot read property 'author' of undefined
    at new TestimonialEditItem (TestimonialEditItem.js:14)
    at modules.js?hash=74f0dc7…:20359
    at measureLifeCyclePerf (modules.js?hash=74f0dc7…:20140)
    at ReactCompositeComponentWrapper._constructComponentWithoutOwner (modules.js?hash=74f0dc7…:20358)
    at ReactCompositeComponentWrapper._constructComponent (modules.js?hash=74f0dc7…:20344)
    at ReactCompositeComponentWrapper.mountComponent (modules.js?hash=74f0dc7…:20252)
    at Object.mountComponent (modules.js?hash=74f0dc7…:13083)
    at ReactCompositeComponentWrapper.performInitialMount (modules.js?hash=74f0dc7…:20435)
    at ReactCompositeComponentWrapper.mountComponent (modules.js?hash=74f0dc7…:20322)
    at Object.mountComponent (modules.js?hash=74f0dc7…:13083)

Exception from Tracker recompute function: Invariant Violation: Attempted to update component `TestimonialEditItem` that has already been unmounted (or failed to mount).
    at invariant (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:4095:15)
    at ReactCompositeComponentWrapper.updateComponent (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20648:63)
    at ReactCompositeComponentWrapper.receiveComponent (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20611:10)
    at Object.receiveComponent (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:13162:22)
    at ReactCompositeComponentWrapper._updateRenderedComponent (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20818:23)
    at ReactCompositeComponentWrapper._performComponentUpdate (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20788:10)
    at ReactCompositeComponentWrapper.updateComponent (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20709:12)
    at ReactCompositeComponentWrapper.performUpdateIfNecessary (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:20625:12)
    at Object.performUpdateIfNecessary (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:13194:22)
    at runBatchedUpdates (http://127.0.0.1:3000/packages/modules.js?hash=74f0dc773a707103dd7edec08a76b26338e4d966:12769:21)

The important parts of the files are the following:

AppRouter:

const AppRouter = () => (
    <BrowserRouter>
        <div>
            <Header />
            <Switch>
                <Route path="/" component={Index} exact={true} />
                <Route path="/admin" render={() => (
                                                              isAdmin() ? (
                                                                <Admin />
                                                              ) : (
                                                                <Redirect to="/login"/>
                                                              )
                                                            )}/>
                <Route path="/testimonial/edit/:id" component={TestimonialEditItem} />
                <Route path="/reference/edit/:id" component={ReferenceEditItem} />
                <Route path="/team" component={Team} />
                <Route path="/references" component={References} />
            </Switch>
            <Footer />
        </div>
    </BrowserRouter>
);

TestimonialEditItem.js :

import React from 'react';
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';
import { createContainer } from 'meteor/react-meteor-data';

import { Testimonials } from './../../api/testimonials';

export class TestimonialEditItem extends React.Component {
    
    constructor(props){
        super(props);
        this.state = {
            author: props.testimonial.author,
            body: props.testimonial.body,
            company: props.testimonial.company,
            position: props.testimonial.position
        }
    }

    componentWillReceiveProps(nextProps){
        this.setState(() => ({
            author: nextProps.testimonial.author,
            body: nextProps.testimonial.body,
            company: nextProps.testimonial.company,
            position: nextProps.testimonial.position
        }))
    }

    handleBodyChange(e) {
        const body = e.target.value;
        this.setState(() => ({  body }));
    }

    handleAuthorChange(e) {
        const author = e.target.value;
        this.setState(() => ({  author  }));
    }

    handleCompanyChange(e) {
        const company = e.target.value;
        this.setState(() => ({ company }));
    }

    handlePositionChange(e) {
        const position = e.target.value;
        this.setState(() => ({ position }));
    }

    onSubmit(e) {
        e.preventDefault();
        console.log(this.state.body);
        this.props.call('testimonials.update', this.props.testimonial._id, this.state.body, this.state.author, this.state.company, this.state.position);
        this.props.history.push('/admin');
    }
    
    renderEditForm() {
        console.log(this.props.ready);
        return(
            <div className="container editTestimonial__form">
                Author: <input onChange={this.handleAuthorChange.bind(this)} value={this.state.author} placeholder="Author" type="text"/>
                Company: <input onChange={this.handleCompanyChange.bind(this)} value={this.state.company} placeholder="Company" type="text"/>
                Position: <input onChange={this.handlePositionChange.bind(this)} value={this.state.position} placeholder="Position" type="text"/>
                Body: <textarea onChange={this.handleBodyChange.bind(this)} value={this.state.body} placeholder="Body"></textarea>
                <button className="editTestimonial__button" onClick={this.onSubmit.bind(this)}>Edit Testimonial</button>
            </div>
        )
    }

    render() {
        return (
            <div className="editTestimonial">
                { this.props.ready ? this.renderEditForm() : 'Pas de testimonial' }
            </div>
        );
    }
}

export default createContainer((props) => {
    const sub = Meteor.subscribe('testimonials');
    console.log(props.match.params.id);

    return {
        ready: sub.ready(),
        testimonial: Testimonials.findOne(props.match.params.id),
        call: Meteor.call
    }   
}, TestimonialEditItem)

Thanks a lot for any help / idea.

Upvotes: 0

Views: 214

Answers (1)

bennygenel
bennygenel

Reputation: 24660

Your problem is that props.testimonial will be undefineduntil the subscription completes. Because props.testimonial is undefined you are getting can not read error while trying to get props.testimonial.author. What you can do is to set the initial state with a default value.

Example

constructor(props){
    super(props);
    this.state = {
        author: props.testimonial ? props.testimonial.author : '',
        body: props.testimonial ? props.testimonial.body : '',
        company: props.testimonial ? props.testimonial.company : '',
        position: props.testimonial ? props.testimonial.position : ''
    }
}

Upvotes: 2

Related Questions