I have a structure of src/resource/file.json.
1.By installing load-json and using require:
class App extends Component{
constructor(props) {
super(props);
this.state = {summaryData: [], sortBy: null};
this.sortContent = this.sortContent.bind(this);
}
componentWillMount() {
require('../resource/file.json')
.then(response => {
// Convert to JSON
return response;
})
.then(findresponse => {
// findresponse is an object
console.log(findresponse);
this.setState({summaryData: findresponse});
})
.catch(norespone => {
console.log('Im sorry but i could not fetch anything');
});
}
And appears the message :
Module not found: Can't resolve '../resource/file.json' in 'C:\Xampp\htdocs\path\to\app\src\pages'
Through myJSON:
request('https://api.myjson.com/bins/{id..}').then(sumres => {
if (sumres) {
this.setState({summaryData: sumres});
console.log(this.state.summaryData);
}
});
}
But nothing appears in the console or the network tab. Cans someone propose a solution?
Is it possible to load the json file without installing a local server?
Yes! It is possible to load JSON into your page. At the top of script where you import your modules, import your json file.
Example:
import React from 'react';
import jsonData from '../resource/file.json';
etc...
And in your case, if you're trying to set it to state, just set the state when the component initializes.
constructor(props) {
super(props);
this.state = {
summaryData: jsonData.myArray,
sortBy: null
};
this.sortContent = this.sortContent.bind(this);
}
Hope this helps!
Related
Getting undefined data type error while fetching data from JSON
I have searched at many places but didn't get the suitable answer
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0].description); //not able to get data
return (
<div>
{/* {this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))} */}
</div>
);
}
}
Someone help me so that I can proceed
Just like Dave Newton has pointed out in the comments, the render is triggered before the request completes. This is normal and you just need to handle it properly.
If you see the console logs of this codesandbox, you can see that initially this.state.datas is just an empty array [] - so any attempt to access this.state.datas[0].description will be undefined. Only after the state is updated when the request completes, the logs show the data retrieved - this is because according to the mount lifecycle of a React Component, the render() is called before the componentDidMount() and also the request being async.
This is very common and it is even recommended by the official React docs to make HTTP calls in componentDidMount(). The docs also has provided an example to handle this issue.
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true, // we initially set this to true
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({
datas: res,
loading: false // when the request is complete, we set this to false
});
});
}
render() {
if (this.state.loading) {
// during the first render, loading will be true and we
// can return a loading message or a spinner
return (
<div>Loading...</div>
);
}
// when render is called after the state update, loading will be false
// and this.state.datas will have the fetched data
console.log(this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))}
</div>
);
}
}
Your datas state is initially an empty array until your componentDidMount fires and sets the state. As a result, your console log will then be undefined until the state is set. In order to combat this you must wait for this.state.datas[0] to be true before accessing the first objects description within the array. The following code seems to work as expected
import React from "react";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: []
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0] && this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items, i) => (
<div key={i}>
<div> title={items.title}</div>
<div> desc={items.description}</div>
</div>
))}
</div>
);
}
}
I am currently trying to unit test a container that pulls in a static JSON file of phone numbers and passes it to the component to display, however I am not sure how I should go about testing it. The code for the container is as follows:
import React from 'react';
import data from *JSON file location*
import CountryInfo from *component for the country information* ;
class CountryInfoContainer extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
numbersJson: null
};
}
async componentWillMount() {
const numbersJson = data;
this.setState({ numbersJson });
}
render() {
return (
<CountryInfo json={this.state.numbersJson} showText={this.props.showText} />
);
}
}
export default CountryInfoContainer;
I currently have my unit test to look like this
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { mount, configure } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import CountryInfoContainer from './CountryInfoContainer';
configure({ adapter: new Adapter() });
describe('Successful flows', () => {
test('checks if json has null entries', () => {
const wrapper = mount(<MemoryRouter><CountryInfoContainer /></MemoryRouter >);
const data = wrapper.find(numbersJson);
// eslint-disable-next-line no-console
console.log(data.debug);
});
});
Obviously, it doesn't work now because I am not sure how to use the variable numbersJson in the container in the test file or how to check if it is null.
The variable numbersJson is not defined in the scope of your test. If I understand correctly, you are testing that when you first mount the component, that it's state contains a null value for the numbersJson key.
First of all, you need to mount your component directly without MemoryRouter:
const wrapper = mount(<CountryInfoContainer />);
Then you can write an expect() for the state:
expect(wrapper.state().numbersJson).toBeNull();
I have a json file named autofill.json and it's created to autofill a search bar when pressed on.
the autofill.json is a test file that's why it looks like this.
[
{
"a": {
"apple": {
"name": "apple",
"href": "https://www.apple.com/"
},
"armadillo": {
"name": "armadillo",
"href": "https://www.armadillo.com/"
}
},
"b": {
"box": {
"name": "apple",
"href": "https://www.berserk.com/"
},
"berserk": {
"name": "berserk",
"href": "https://www.berserk.com/"
}
}
}
]
The .json file is then fetched in the file named FetchAndParseResults.js
import fetch from 'isomorphic-fetch'
const FetchAndParseResults = (url) => {
return fetch(url).then(response => {
const parsedJson = response.json()
return parsedJson
})
}
export default FetchAndParseResults
The data that gets fetched is used in searchcontainer.js where everything gets placed in, the search etc.
import React from 'react'
import Searchbar from './index.js'
import FetchAndParseResults from './FetchAndParseResults.js'
class SearchContainer extends React.Component {
state = {
results: []
}
performSearch = event => {
return FetchAndParseResults('static/autofill.json').then(data => {
this.setState({ results: data })
})
}
render () {
console.log('performSearch event', this.performSearch)
console.log('data inside performSearch', this.state.results)
return (
<Searchbar
performSearch={this.performSearch}
results={this.state.results}
/>
)
}
}
export default SearchContainer
Then to map through the data that is in autofill.json there is a file named autofill.js
import React from 'react'
import PropTypes from 'prop-types'
import Styles from './searchbar.scss'
const AutoFill = (props) => {
console.log('proppppppsss', props)
const results = props.results || []
return (
<ul className={Styles.searchUl}>
{results.map(({ name, href }) => (
<li className={Styles.searchLi} key={href}>
<a className={Styles.searchA} href={href} target='_blank' rel='noopener noreferrer' key={href}>
{name}
</a>
</li>
))}
</ul>
)
}
AutoFill.propTypes = {
results: PropTypes.array
}
export default AutoFill
the Searchbar component in (index.js) that is being used in searchcontainer.js
import React from 'react'
import Styles from './searchbar.scss'
import Icon from '../../components/icon/icon'
import Search from '../../components/form-input/search'
import AutoFill from './autofill'
import PropTypes from 'prop-types'
export default class Searchbar extends React.Component {
constructor (props) {
super(props)
this.state = {
className: Styles.input,
icon: Styles.icon__wrapper,
value: []
}
this.input = React.createRef()
}
openInput = () => {
this.setState({
className: Styles.input__active,
icon: Styles.iconWidth
}, () => {
this.input.focus()
})
this.props.onOpen && this.props.onOpen()
}
closeInput = () => {
this.setState({
className: Styles.input,
icon: Styles.icon__wrapper
})
this.props.onClose && this.props.onClose()
}
handleChange = event => {
let value = event.target.value
this.setState({ value })
this.props.performSearch(value)
}
handleSubmit = event => {
event.preventDefault()
}
render () {
console.log('results', this.props.results)
console.log('state.value', this.state.value)
return (
<div>
<form onSubmit={this.handleSubmit} className={Styles.search}>
<div className={this.state.icon}>
<Icon className={Styles.icon__wrapper} iconName='faSearch' onClick={this.openInput} />
</div>
<Search autoComplete='off' value={this.state.value} onChange={this.handleChange} id='search' tabIndex='0' myref={input => { this.input = input }} className={this.state.className} onBlur={this.closeInput} placeholder='Search' />
</form>
<div>
<AutoFill results={this.props.results} />
</div>
</div>
)
}
}
Search.propTypes = {
performSearch: PropTypes.func,
results: PropTypes.array
}
When i try to refer to a what is in the json file from the search i receive the error,
GET http://localhost:3000/[object%20Object] 404 (Not Found)
And
about:1 Uncaught (in promise) SyntaxError: Unexpected token < in JSON
at position 0
The second error is fixed by doing
const parsedJson = response.text(
instead of
const parsedJson = response.json()
to get more information where/what the error takes place. But by doing this i receive the error,
searchcontainer.js:12 Uncaught (in promise) TypeError: Cannot read property 'results' of undefined
I've tried to run it from npm build instead of running it in a dev environment which didn't fix it.
I read that a mock url should work but then again i want to acces it from a file and not from a url?
Any help would be highly appreciated and looked into.
The problem is most likely in the fetch call. If you look at the error message GET http://localhost:3000/[object%20Object] 404 (Not Found)
You can see that it is trying to append an object to the URL localhost:3000/.
You are getting the Unexpected token < in JSON at position 0 error because the response of your fetch request is probably a 404 page. The < is most likely the first char of <html>
To access the JSON object in your React files, you can simply do an importation like so;
import * as autofillData from 'autofill.json';
It will be returned as a JSON object.
I believe you are using the isomorphic-fetch package wrongly, if you look at their source code, https://github.com/matthew-andrews/isomorphic-fetch/blob/master/fetch-npm-node.js#L5 , they are accepting a URL to make a call to the API URL which will return a promise or a JSON object depending on the implementation of the API that you are calling.
If you were to dive deeper into the open-source code here (https://github.com/matthew-andrews/isomorphic-fetch/blob/master/fetch-npm-node.js#L8) , you will notice that isomorphic-fetch package is using another package node-fetch to do their fetch call, which accepts the API URL and the method request options to call the API with. (As stated here; https://github.com/bitinn/node-fetch/blob/master/src/index.js#L34)
To continue with your test, perhaps this might be the solution you'd prefer?
import fetch from 'isomorphic-fetch';
import * as autofillData from 'autofill.json'; //test data
const FetchResults = event => {
return fetch('/https://jsonplaceholder.typicode.com/todos/1'') //mockURL, to be replaced with real API
.then(response => {
// const parsedJson = response.json(); // TODO: un-comment this line when the real API url is usable
const parsedJson = autofillData; // TODO: remove this line when mocking is done and the real API URL is ready
return parsedJson;
})
}
export default FetchResults;
To have a mock URL placeholder, I would suggest https://jsonplaceholder.typicode.com/ to prevent your fetch result to return an unexpected error during test mocking.
Hope this is helpful.
The question has been solved, The main issue was with defining const names such as const results = [] which should've been const results = props.results || [].
The code has been updated incase you have problems aswell.
In our web app we have a few JSON files that are ~10-80k lines each. These are getting included in our main bundle. These are used by an animation plugin called react-lottie.
An example of our webpack.config.js
module.exports = {
entry: ["./src/index.js"],
module: {
rules: [
{ test: /\.(js|jsx)$/, exclude: /node_modules/, use: ["babel-loader"] },
{
test: /\.(jpg|png|gif|ico)$/,
use: {
loader: "file-loader",
options: { name: "[path][name].[hash].[ext]" }
}
}
]
},
resolve: { extensions: ["*", ".js", ".jsx"] },
output: {
path: __dirname + "/dist",
publicPath: "/",
filename: "[name].[hash].js"
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({ hash: false, template: "src/index.html" }),
new DashboardPlugin(),
new CopyWebpackPlugin([
{
from: "src/components/Assets/BookingBar.js",
to: "assets/BookingBar.js"
}
]),
new BundleAnalyzerPlugin()
],
devServer: {
contentBase: "./dist",
hot: true,
historyApiFallback: true,
port: 4000
}
};
What is the expected behavior?
There should be a way to exclude .json files from the main bundle. I've tried File-Loader, json-loader, and const someJson = require(./someJson)
Other relevant information:
webpack version: 4.16.1
Node.js version: 10.12.0
Operating System: Mac OS 10.14 Mojave
ANSWER BELOW (AT LEAST FOR HOW I SOLVED IT). I couldn't initialize the lottie without any data.
The expected behavior is that the JSON will get bundled because it's, presumably, needed synchronously at runtime. JSON data differs from something like image files which are loaded asynchronously by the browser as they are rendered on the page via src attributes etc.
As the comments mentioned, you should be using code splitting. The latest version of Webpack supports dynamic imports if you install and use the #babel/plugin-syntax-dynamic-import plugin.
npm install --save-dev #babel/plugin-syntax-dynamic-import
Then in babel.config.js:
module.exports = {
...
plugins: [
"#babel/plugin-syntax-dynamic-import"
]
...
};
Example
Say you have a React component that might need some JSON data, but doesn't need to load it synchronously as part of the bundle. Your non-code splitted version might look something like this:
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
render() {
return <div>{JSON.stringify(myJSON, null, 2)}</div>
}
}
Instead you can use a dynamic import - basically a runtime import that returns a Promise you can use to asynchronously load some data chunked separately from your bundle:
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
state = {data: {}};
componentDidMount() {
import(/* webpackChunkName: 'myJSON' */ './myJSON.json')
.then((data) => {
this.setState({data});
});
}
render() {
return <div>{JSON.stringify(this.state.data, null, 2)}</div>
}
}
Alternately, you can use React's new lazy and Suspense API (v16.6.0 and higher) to dynamically import React components that get chunked separately from the bundle. This might be preferable if you want to chunk a component and its corresponding JSON data together, but separately from the main bundle:
// MyComponent.jsx
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
render() {
return <div>{JSON.stringify(myJSON, null, 2)}</div>
}
}
// SomeParent.jsx
import React, {lazy, Suspense} from 'react';
const MyComponent = lazy(() => import(/* webpackChunkName: 'MyComponent' */ './MyComponent'));
export default class SomeParent extends React.Component {
render() {
return <div>
<Suspense fallback={<div>Loading...<div>} >
<MyComponent />
</Suspense>
</div>;
}
}
In the above example, <MyComponent /> and its corresponding code -- including the JSON data -- will only be loaded when the component is actually rendered at runtime.
Ultimately I took the answer above below me but wasn't able to initialize the lottie without any JSON data. I ended up doing this:
import React, { PureComponent } from "react"
import Lottie from 'react-lottie'
export default class AnimationAutomatedCommunication extends PureComponent {
constructor(props) {
super(props)
this.state = {
animation: <div />
}
}
async componentDidMount() {
const animation = await import(/* webpackChunkName: "AnimationAutomatedCommunication" */ './JsonData/AnimationAutomatedCommunication.json')
const defaultOptions = {
loop: true,
autoplay: true,
animationData: animation.default
}
this.setState({
animation: <div className={this.props.className}>
<Lottie key="lottie-win-jobs" options={defaultOptions}
isStopped={this.props.isStopped} />
</div>
})
}
render() {
return (
<React.Fragment>
{this.state.animation}
</React.Fragment>
)
}
}
The problem might be common but I am asking because I couldn't fix it. I am getting "null is not an object(evaluating 'this.state.albums')" at line 22
And also I am a bit not clear about the type of data that was returned after the call and how to handle it. Please, can someone help me in explaining this? I am in the learning phase. when I am directly alert the response.data I'm getting [object][object] I have to do JSON stringify to see the data. Why should we do this?
import React, { Component } from 'react';
import {Text} from 'react-native';
import axios from 'axios';
export default class AlbumList extends Component{
constructor(props) {
super(props);
state = { albums : []};
}
componentWillMount(){
console.log('in componentWillMount');
//debugger;
//alert("first"+this.state);
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then((response) => {
//console.log(response);
//alert(JSON.stringify(response));
this.setState({albums : response.data});
//alert(JSON.stringify(this.state.albums));
})
.catch((error) => {
alert(error);
});
}
renderAlbums(){
return this.state.albums.map( album => <Text>{album.title}</Text>); //line 22
}
componentDidMount() {
console.log('I was triggered during componentDidMount')
}
render(){
return(
<Text>{this.renderAlbums()}</Text>
//<Text>Hiii</Text>
);
}
}
You're missing this in your constructor.
constructor(props) {
super(props);
this.state = { albums : []};
}
As for the alert, you can't alert an object it has to be a string. So alerting a JSON object is just [object object]. If you use JSON.stringify it converts the object to a string that can be used for the alert. In your console you can log objects fine, and their structure is more readable. I would stick to console.log for debugging.