0

Attempting to turn a JSON file into an array, then randomly selecting 5 items from it.

I think the issue is my render/return statement at the bottom of ImageContainer.js, but I'm a ReactJS n00b, so it could be anything.

Any help or advice is greatly appreciated.

Console Error

compiled.js:31562 Uncaught ReferenceError: selectImages is not defined
  at ImageContainer.render (compiled.js:31562)
  at compiled.js:20195
  at measureLifeCyclePerf (compiled.js:19475)
  at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (compiled.js:20194)
  at ReactCompositeComponentWrapper._renderValidatedComponent (compiled.js:20221)
  at ReactCompositeComponentWrapper.performInitialMount (compiled.js:19761)
  at ReactCompositeComponentWrapper.mountComponent (compiled.js:19657)
  at Object.mountComponent (compiled.js:4009)
  at ReactDOMComponent.mountChildren (compiled.js:24150)
  at ReactDOMComponent._createInitialChildren (compiled.js:21126)

ImageContainer.js - pulls img info from a .JSON file, randomly selects 5 to render

import React from 'react';
import ReactDOM from 'react-dom';
import { Image } from './Image';

export class ImageContainer extends React.Component {
  constructor(props) {
    super(props);

    this.numImages = 5;

    this.state = {
      data: []
    };
  }

  componentDidMount() {
    $.ajax({
      url: '../app/data/namegame-data.json',
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error('#GET Error', status, err.toString());
      }.bind(this)
    });
  }

  shuffleArray(array) {
    let i = array.length - 1;
    for (; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      const temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
    return array;
  }

  selectImages({ data }) {
    const shuffledImages = shuffleArray(images);
    return (
      shuffledImages.map((data, idx) => {
        for (let i = 0; i < this.numImages; i++) {
            if (this.state.data.length > 0) {
                return <Image key={idx} name={data.name} src={data.src} />;
            }
        }
      })
    );
  }

  render() {
    return {selectImages};
  }
}

Image.js - just the image HTML

import React from 'react';

export class Image extends React.Component {
    render() {
        const key = this.props.id;
        const name = this.props.name;
        const src = this.props.src;
        return (
            <picture>
                <img id="pic-template" className="pic" src="{src}" name="{name}" key="id" />
            </picture>
        );
    }
}

[Main].js - most of the HTML is here, just including the import bits

import React from 'react';
import ReactDOM from 'react-dom';
import { ImageContainer } from './ImageContainer';

export default class NameGame extends React.Component {
  render () {
    return (
      ...
      <div id="images-wrap">
          <ImageContainer />
      </div>
      ...
    );
  }
}

Dependencies

{
  ...
  "dependencies": {
    "json-loader": "^0.5.4",
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  },
  "devDependencies": {
    "css-loader": "^0.28.4",
    "extract-text-webpack-plugin": "^2.1.2",
    "node-sass": "^4.5.3",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "webpack": "^2.2.0",
    "webpack-dev-server": "^2.5.0"
  }
}
n0bodySp3cial
  • 43
  • 1
  • 10

3 Answers3

0

i think "this" is where it goes wrong ;):

selectImages is part of the ImageContainer class, meaning the function is scoped within this Class, so it should be this.selectImages

In the render you call selectimages

render() {
    return {this.selectImages()}; <-- its a function so u should call it as a function
}

Remember selectimage should return a react component, but if it will return a array of items that react will render you should wrap it, you cannot pas multiple react elements to render

so you should something like

render() {
    return (
        <div> <-- give render 1 react component wrapping the multiple components 
            {this.selectImages()}; <-- its a function so u should call it as a function
        </div>
    )
}

selectImages({ data }) <-- where is it getting data from ? i don't think data is needed , only if u gonna pass { data: 'whatever' } to the function

shuffledImages.map((data, idx) => { 
 data will be the items from the shuffledImages array
})


const shuffledImages = shuffleArray(images); should be ->
const shuffledImages = this.shuffleArray(images);

about your other question

shuffleArray() {
  (array) <-- u are mutating the data from the state, when u passed it to the function, u should only change state with setState.

     use another shuffle function where u dont mutate the data ,use concat or es6 spread operator [ ...data ] etc .

read: "Correct modification of state arrays in ReactJS"

}

    selectImages() {
      const shuffledImages = this.shuffleArray(this.state.data); <-- u dont have to pass this.state.data, to the function ,it is accessable within shufflarray -> const shuffledImages = this.shuffleArray();

      return (
          shuffledImages.map((data, idx) => {
            for (let i = 0; i < this.numImages; i++) {
                return <Image key={idx} name={this.state.data.name} src={this.state.data.src} />;
            }
          })
      );
    }

if ur ajax call fails array is undefined. and u cannot call length on undefined. (thats what the error says)- about ur call

u need to tell ur ajax call what to do, is it a post or get? add a 'type' (it should be type: 'get', )

or use the shorthand (i see u are using jquery)

$.getJSON('whateverjson file.json').done(function(response) {
    console.log("Success");
}).fail(function() {
    console.log("Error");
});

About the data ->

u should parse the json setstate({ data: JSON.parse(data) }) or setstate({ data: $.parseJSON(data) }); 

I must say using jquery, in combination with react, is not something i should recommend, jquery is mostly used for dom manipulation, react uses its virtual dom, and to include the whole jquery library for only the ajax requests is a waste.

try 'fetch' <==

PS look into https://facebook.github.io/react/docs/react-api.html (react docs)

Its also best to pass results of api calls to the component as props, but u will figure that out once u dive into the docs! i hope this helps u, good luck m8

  • Thanks for the quick reply! Definitely a few steps closer. The error is now: *Uncaught TypeError: Cannot read property 'length' of undefined* In addition to making your change and adding a few more "this." prefixes, I changed componentDidMount() to componentWillMount(), which loads, but the ajax call does not succeed. – n0bodySp3cial Jul 08 '17 at 14:52
  • Further reading indicates that componentDidMount() is correct, but the JSON may *not* be getting parsed into anything usable - continuing to hack at this. – n0bodySp3cial Jul 08 '17 at 15:23
0

Here is the updated (mostly) working version of ImageContainer.js, which incorporates Danillo's changes and solves the array problem that followed. The issues were mainly related to missing "this." and "this.state."

ImageContainer.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Image } from './Image';

export class ImageContainer extends React.Component {
    constructor(props) {
        super(props);

        this.numImages = 5;

        this.state = {
          data: []
        };
    }

    componentWillMount() {
      $.ajax({
          url: './app/data/namegame-data.json',
          dataType: 'json',
          success: function(data) {
            this.setState({data: data});
          }.bind(this),
          error: function(xhr, status, err) {
            console.error('#GET Error', status, err.toString());
          }.bind(this)
        });
    }

    shuffleArray(array) {
      let i = array.length - 1;
      for (; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        const temp = array[i];
        array[i] = array[j];
        array[j] = temp;
      }
      return array;
    }

    selectImages() {
      const shuffledImages = this.shuffleArray(this.state.data);
      return (
          shuffledImages.map((data, idx) => {
            for (let i = 0; i < this.numImages; i++) {
                return <Image key={idx} name={this.state.data.name} src={this.state.data.src} />;
            }
          })
      );
    }

    render() {
        return (
            <div>
                {this.selectImages()};
            </div>
        )
    }
}
n0bodySp3cial
  • 43
  • 1
  • 10
0

i quickly changed ur code at 00:45 :D, but try something like this, explenation in my other answer. gl!

import React from 'react';
import ReactDOM from 'react-dom';
import { Image } from './Image';

export class ImageContainer extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
          data: [],
          numImages: 5
        };
    }

    componentWillMount() {
      $.ajax({
          url: './app/data/namegame-data.json',
          dataType: 'json',
          type: 'get',
          success: function(data) {
            this.setState({data: JSON.parse(data) });
          }.bind(this),
          error: function(xhr, status, err) {
            console.error('#GET Error', status, err.toString());
          }.bind(this)
        });
    }

    randomPickFromArray() {
      // * as asked: " Attempting to turn a JSON file into an array, then randomly selecting 5 items from it. "

      // lets not shuffle, and just random pick 5 items from array,
      const { data, numImages } = this.state;
      const arr = [];
      for (let i = 0; i < numImages; i++) {
        arr.push(data[Math.floor(Math.random() * data.length)]);
      }
      return arr;
    }

    selectImages() {
      const shuffledImages = this.randomPickFromArray();
      return shuffledImages.map((item, idx) => <Image key={idx} name={item.name} src={item.src} />);
    }

    render() {
        return (
            <div>
                {this.selectImages()};
            </div>
        )
    }
}