Search code examples
reactjsblogsmapstatetoprops

React/Props/State: Uncaught TypeError: Cannot read properties of null (reading 'userId') at Function.mapStateToProps


I have a blog webapp that I'm developing.

The theme is a car mechanic's workshop called "Bob's Garage".

I have a "blog" page, then the rest of the pages are basically the same as the "blog" page but with different content (e.g "User Feedback page". "User reviews page", etc..

The "blog" page is working, but the "feedback" and "review" page are not.

The code is virtually identical between the pages, just some different names (e.g "AddBlog()" becomes "AddFeedback()")

Here is the code:

AddFeedback.js:

//rafcp
// import fragment, useState
import React, {Fragment, useState} from 'react';
import PropTypes from 'prop-types';
// import useNavigate
import {useNavigate} from 'react-router-dom';
// import connect
import { connect } from 'react-redux';
// import our addFeedback function.
import { addFeedback} from '../../actions/feedbackActions';


const AddFeedback = ({ addFeedback, userId }) => {

  // Create component state.
  const [formData, setFormData] = useState({
    Author: '',
    Heading:'',
    Feedback:'',
    Location:'',
    errors: {
      body: 'You must enter your feedback in the box provided'
    }
  });

  console.log("formData.body = " + formData.body);  // formData.body is the user input in the textbox of the Add New Blog Entry page

  // Use destructuring to pull the variables out of our state.
  const { Author, Heading, Feedback, Location, errors } = formData;
  
  // Create our navigate variable.
  const navigate = useNavigate();

  // on Change function.
  const onChange = e => {
    setFormData({
      ...formData, [e.target.name]: e.target.value
    });
  }

  // Create our on submit
  const onSubmit = async(e) => {
    e.preventDefault();

    // Test that the onSubmit is called.
    console.log('Onsumbit - Add running...');

    // Check for errors / validation.
    if( Feedback === '' || Feedback === undefined){
      console.log('feedback empty');
      // Save an error message to the state using the errors object.
      // remember that we also need to include everything that is in the state.
      setFormData({
        ...formData,
        errors:{
          body: 'You must enter feedback in the box above',
          Heading: 'You must enter your heading in the box above',
          Author: 'You must enter your name in the box above',
          Feedback: 'You must enter your feedback in the box above',
          Location: 'You must enter your location in the box above'
        }
      });
      // stop the onSubmit running.
      return;
    } else {
      setFormData({
        ...formData,
        errors:{
          Author: '',
          Heading: '',
          Feedback: '',
          Location: '',
          body: ''
        }
      });
    }

    let publishDate = new Date();
    let publishDay = publishDate.getDate();
    let publishMonth = publishDate.getMonth() + 1;
    let publishYear = publishDate.getFullYear();

    console.log("AddFeedback.js: userId = " + userId);
    // create a newItem to add to our feedback list.
    const newItem = {
      Author: Author,
      Heading: Heading,
      Feedback: Feedback,
      PublishDate: publishYear + "-" + publishMonth + "-" + publishDay,
      Location: Location,
      Id: userId,
      UserUserId: userId 
      //UserUserId: userUserId     
      //UserUserId:auth.user.userId
    }
 
    console.log(newItem);
    // Send our newItem to an API or state managemeht system.
    // Call our addFeedback function.
    addFeedback(newItem);
    // We can do other things after this, like redirect the browser.
    navigate('/feedback');
  }; // end of onSubmit.

  return (
    <Fragment>
      <h2 className='text-primary'>Add New Feedback</h2>
      <div className='card mb-3'>
        <div className='card-header'>
          Add Your Feedback
        </div>
        <div className='card-body'>
          <form onSubmit={e => onSubmit(e)}>

          <div className='mb-3'>
              <label htmlFor='body'>Heading:</label>
              <textarea 
                className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
                id='heading'
                placeholder='Your Heading'
                name='Heading'
                value={Heading}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.heading && <div  className='invalid-feedback'>
                {errors.heading}
              </div>}
            </div>

            <div className='mb-3'>
              <label htmlFor='body'>Author:</label>
              <textarea 
                className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
                id='author'
                placeholder='Author'
                name='Author'
                value={Author}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.heading && <div  className='invalid-feedback'>
                {errors.heading}
              </div>}
            </div>

            <div className='mb-3'>
              <label htmlFor='body'>Location:</label>
              <textarea 
                className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
                id='location'
                placeholder='Location'
                name='Location'
                value={Location}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.heading && <div  className='invalid-feedback'>
                {errors.heading}
              </div>}
            </div>

            <div className='mb-3'>
              <label htmlFor='body'>Have your say below:</label>
              <textarea 
                className={`form-control ${errors.body ? "is-invalid" : 'is-valid'}`}
                id='feedback'
                placeholder='Your Feedback'
                name='Feedback'
                value={Feedback}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.body && <div  className='invalid-feedback'>
                {errors.body}
              </div>}
            </div>
            {/*  <div className='mb-3'>
            //   <label htmlFor='name'>Name</label>
            //   <input 
            //     type='text'
            //     className='form-control'
            //     id='name'
            //     placeholder='Name'
            //     name='name'
            //     value={name} 
            //     onChange={e => onChange(e)}
            //   />
            </div> */}
            {/* <div className='mb-3'>
              <label htmlFor='image'>Image</label>
              <input 
                type='text'
                className='form-control'
                id='image'
                placeholder='URL for image'
                name='image'
                value={image} 
                onChange={e => onChange(e)}
              />
            </div> */}
            <div className='d-grid gap-2'>
              <input type='submit' value='Add Feedback' className='btn btn-light '/>
            </div>
          </form>
        </div>
      </div>
    </Fragment>
  )
}

// Proptypes
AddFeedback.propTypes = {
  addFeedback: PropTypes.func.isRequired
}

//mapState to props
//user from state(auth)
//get the user id of the person logged in
//auth.user
//auth.user.userId
//pass that userId through in onSubmit function

const mapStateToProps = state => ({
  userId: state.auth.user.userId
})

// add in connect.
// We do not need anything from the state or store. 
// that means null, as we do not need mapState to props.
export default connect(mapStateToProps, {addFeedback})(AddFeedback)

AddBlog.js:

//rafcp
// import fragment, useState
import React, {Fragment, useState} from 'react';
import PropTypes from 'prop-types';
// import useNavigate
import {useNavigate} from 'react-router-dom';
// import connect
import { connect } from 'react-redux';
// import our addFeedback function.
import { addBlog} from '../../actions/blogActions';

const AddBlog = ({ addBlog, userId }) => {

  // Create component state.
  const [formData, setFormData] = useState({
    //Post: '',
    //Author: '',
    Heading: '',
    errors: {
      Post: 'You must enter your blog post in the box provided'
    }
  });

  console.log("formData.Post = " + formData.Post);  // formData.Post is the user input in the textbox of the Add New Blog Entry page

  // Use destructuring to pull the variables out of our state.
  const { Post, Author, ImageURL, errors, Heading } = formData;
  
  // Create our navigate variable.
  const navigate = useNavigate();

  // on Change function.
  const onChange = e => {
    setFormData({
      ...formData, [e.target.name]: e.target.value
    });
  }

  // Create our on submit
  const onSubmit = async(e) => {
    e.preventDefault();

    // Test that the onSubmit is called.
    console.log('Onsumbit - Add running...');

    // Check for errors / validation.
    if( Post === '' || Post === undefined){
      console.log('post empty');
      // Save an error message to the state using the errors object.
      // remember that we also need to include everything that is in the state.
      setFormData({
        ...formData,
        errors:{
          Post: 'You must enter blog entry in the box above'
        }
      });
      // stop the onSubmit running.
      return;
    } else {
      setFormData({
        ...formData,
        errors:{
          Post: ''
        }
      });
    }

    let publishDate = new Date();
    let publishDay = publishDate.getDate();
    let publishMonth = publishDate.getMonth() + 1;
    let publishYear = publishDate.getFullYear();

    console.log("AddBlog.js: userId = " + userId);
    // create a newItem to add to our feedback list.
    const newItem = {
      Post: Post,
      Author: Author,
      Heading: Heading,
      ImageURL: ImageURL,
      PublishDate: publishYear + "-" + publishMonth + "-" + publishDay, // create function
      UserUserId: userId 
      //UserUserId: userUserId     
      //UserUserId:auth.user.userId
    }
 
    console.log(newItem);
    // Send our newItem to an API or state managemeht system.
    // Call our addFeedback function.
    addBlog(newItem);
    // We can do other things after this, like redirect the browser.
    navigate('/blog');
  }; // end of onSubmit.

  return (
    <Fragment>
      <h2 className='text-primary'>Add New Blog Entry</h2>
      <div className='card mb-3'>
        <div className='card-header'>
          Add Your Blog Entry
        </div>
        <div className='card-body'>
          <form onSubmit={e => onSubmit(e)}>

          <div className='mb-3'>
              
              {/* Heading */}
              <label htmlFor='body'>Heading:</label>
              <textarea 
                className={`form-control ${errors.heading ? "is-invalid" : 'is-valid'}`}
                id='heading'
                placeholder='Your Heading'
                name='Heading'
                value={Heading}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.heading && <div  className='invalid-feedback'>
                {errors.heading}
              </div>}
              </div>

              {/* Image URL */}
              <div className='mb-3'>
              
              <label htmlFor='body'>Image URL:</label>
              <textarea 
                className={`form-control ${errors.ImageURL ? "is-invalid" : 'is-valid'}`}
                id='imageurl'
                placeholder='Your image URL'
                name='ImageURL'
                value={ImageURL}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.ImageURL && <div  className='invalid-feedback'>
                {errors.ImageURL}
              </div>}
              </div>

              {/* Author */}
          <div className='mb-3'>
              <label htmlFor='body'>Author:</label>
              <textarea 
                className={`form-control ${errors.Author ? "is-invalid" : 'is-valid'}`}
                id='author'
                placeholder='Your Heading'
                name='Author'
                value={Author}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.heading && <div  className='invalid-feedback'>
                {errors.heading}
              </div>}
              </div>

            {/* Author */}
            <div className='mb-3'>
              <label htmlFor='body'>Have your say below:</label>
              <textarea 
                className={`form-control ${errors.Post ? "is-invalid" : 'is-valid'}`}
                id='post'
                placeholder='Your Blog Post'
                name='Post'
                value={Post}
                // Add in our onChange event
                onChange={ e => onChange(e)}
              ></textarea>
              {errors.Post && <div  className='invalid-feedback'>
                {errors.Post}
              </div>}
            </div>
            {/*  <div className='mb-3'>
            //   <label htmlFor='name'>Name</label>
            //   <input 
            //     type='text'
            //     className='form-control'
            //     id='name'
            //     placeholder='Name'
            //     name='name'
            //     value={name} 
            //     onChange={e => onChange(e)}
            //   />
            </div> */}
            {/* <div className='mb-3'>
              <label htmlFor='image'>Image</label>
              <input 
                type='text'
                className='form-control'
                id='image'
                placeholder='URL for image'
                name='image'
                value={image} 
                onChange={e => onChange(e)}
              />
            </div> */}
            <div className='d-grid gap-2'>
              <input type='submit' value='Add Blog Entry' className='btn btn-light '/>
            </div>
          </form>
        </div>
      </div>
    </Fragment>
  )
}

// Proptypes
AddBlog.propTypes = {
  addBlog: PropTypes.func.isRequired
}

//mapState to props
//user from state(auth)
//get the user id of the person logged in
//auth.user
//auth.user.userId
//pass that userId through in onSubmit function

const mapStateToProps = state => ({
  userId: state.auth.user.userId
})

// add in connect.
// We do not need anything from the state or store. 
// that means null, as we do not need mapState to props.
export default connect(mapStateToProps, {addBlog})(AddBlog)

I cannot understand why 'userId' is evaluating to 'null'...

Do any of you have any ideas as to why this error might be occurring?

Here is a screenshot of the Redux Dev Tools 'State' windows whilst on the 'AddFeedback.js' page. Here is a screenshot of the Redux Dev Tools 'State' windows whilst on the 'AddFeedback.js' page.

Please let me know if you need me to post up any other source files, such as Context.js or similar.

Kind Regards,

John Melbourne, Australia


Solution

  • I found the solution to my problem.

    As a React newbie I had inadvertently used an < a > tag to navigate to the AddFeedback.js page, which caused state to be lost, which is why userId became null.

    So, to all you React newbies out there:

    Use a React < Link to=/your/path/here > Your link text < / Link>

    Kind Regards, John Melbourne, Australia