Accessing JSON from public folder in ReactApp - javascript

I have a a JSON file which I would like to use its content into my React App.
This is an excerpt from my code
export default class App extends Component{
constructor(props){
super(props);
this.state = {
entry : []
}
}
componentDidMount(){
fetch(process.env.PUBLIC_URL + `./js/data.json`)
.then(res => res.json())
.then(json => this.setState({ entry: json }));
}
render(){
return(
<div>
<ul>
{this.state.entry.map( x => (
<li>
<div className='centering'>
<span>
<img alt='' key={x.img} src={process.env.PUBLIC_URL + `./img/${x.img}.jpg`}/>
</span>
<span className='txt'>
{ x.date }
</span>
<span>
<p>{ x.ctx }</p>
</span>
</div>
<div className='hr-line'></div>
</li>
))}
</ul>
</div>
)
}
}
this is the content of my data.json
{
"entry" : {
"2" : [
{
"date":"1/1/1",
"img":"profile",
"ctx":"as"
}
],
"1" : [
{
"date":"1/1/1",
"img":"profile",
"ctx":"as"
}
]
}
When I save the file, on the browser it shows TypeError: this.state is null
onrender {this.state.entry.map (x => ...
Is there something missing, or it is impossible to do so?
Thank you in advance.

Rendering is done before componentDidmount. You need to wait for your data to load before to do your map. Something like :
export default class App extends Component{
constructor(props){
super(props);
this.state = {
entry : [],
anyData: false
}
}
componentDidMount(){
fetch(process.env.PUBLIC_URL + `./js/data.json`)
.then(res => res.json())
.then(json => this.setState( prevState => ({ ...prevState, entry: json, anyData: true })));
}
render(){
if(!anyData) {
return <Loader />
}
return(
<div>
<ul>
{this.state.entry.map( x => (...))}
</ul>
</div>
)
}
}
There is also a possibility to use async lifecycle method :
async componentDidMount() {
const res = await fetch(...);
const json = await res.json();
this.setState( prevState => ({ ...prevState, entry: json, anyData: true }));
}
But it does not change the fact that it will be done after rendering.

React expects the .map for iterative rendering to work on an array of items only.
Therefore, changing the json input to return an array of items will help resolve the problem.
Existing input that does not work with .map:
"entry" : { "1" : [....], "2" : [....] } --> Input is not an array of items.
--> .map does not support this format
How to make it work with .map?
"entry" : [ {"1" : {....} , {"2" : {....} ] --> Input is an array of items.
--> .map works with this format
Additional: How to avoid 'null value' error in render method?
The render method must explicitly check for null condition before trying to render elements. This can be done as follows:
render(){
return(
<div>
<ul>
{ (this.state.entry) &&
this.state.entry.map( x => (
<li>
...
</li>
))
}
</ul>
</div>
)
}

Related

React | Reusable dropdown component | how to get selected option?

Fairly new with react. I'm creating a dropdown button for a Gatsby project. The button toggle works, but I'm having trouble getting the selected value to the parent where I need it.
-Tried lifting the state up, but this resulted in the button not appearing at all. I was a bit confused here so maybe I was doing something wrong.
-Also tried using refs although I wasn't sure if this was the right use case, it worked, however it seems the value is grabbed before it's updated in the child component and I'm not sure how to change or work around this. (the code is currently set up for this)
Are either of these options right? or could anybody steer me in the right direction, thanks.
Dropdown in parent:
this.dropdownRef1 = React.createRef();
componentDidUpdate(){
console.log("Color Option:" + this.dropdownRef1.current.state.ColorOption)
}
<DropdownBtn ref={this.dropdownRef1} mainText="Color" options={this.props.pageContext.colors || ['']} />
DropdownBtn:
export default class refineBtn extends React.Component {
constructor(props) {
super(props);
}
state = {
open: false,
[this.props.mainText + "Option"]: "all",
};
dropdownBtnToggle = () => {
this.setState((prevState)=> {
return{open: !prevState.open};
});
};
optionClickHandler = (option) => {
this.setState(() => {
console.log(this.props.mainText + " updated to " + option)
return {[this.props.mainText + "Option"] : option}
});
};
render(){
const options = this.props.options
console.log("open: " + this.state.open)
return(
<div>
<button onClick={this.dropdownBtnToggle} >
{this.props.mainText}:
</button>
<div className={this.state.open ? 'option open' : "option"} >
<p key={"all"} onClick={() => this.optionClickHandler("all")}> all</p>
{options.map(option => (
<p key={option} onClick={() => this.optionClickHandler(option)}>{option}</p>
))}
</div>
</div>
);
}
}
You can respond to selection by allowing your component to accept a callback.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {open: false, value: ''}
}
render() {
return (
<div>
<div onClick={() => this.setState({open: true})}>{this.state.value}</div>
<div style={{display: this.state.open ? 'block' : 'none'}}>
{this.props.options.map((option) => {
const handleClick = () => {
this.setState({open: false, value: option})
this.props.onChange(option)
}
return (
<div key={option} onClick={handleClick} className={this.state.value === option ? 'active' : undefined}>{option}</div>
)
})}
</div>
</div>
)
}
}
<MyComponent onChange={console.log} options={...}/>

How to implement `Search box` through `props items` using ReactJs

I want to implement search box from my set of props data .
I tried to follow this article https://dev.to/iam_timsmith/lets-build-a-search-bar-in-react-120j but i guess i m doing some silly mistakes.
any help to correct my mistake would be helpful for me.
//allbook.js
class AllBook extends Component {
constructor(props){
super(props);
this.state = {
search : ""
}
}
updateSearch(e){
this.setState({search: e.target.value.substr(0, 20)});
}
render(){
let filteredBooks = this.props.posts.filter(
(posts) => {
return posts.title.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
}
);
return(
<div>
{Object.keys(filteredBooks).length !== 0 ? <h1 className="post-heading">All books</h1> : <h1 className="post-heading">No Books available</h1>} {/*To check if array is empty or not*/}
{Object.keys(filteredBooks).length !== 0 ?
<input className="post-heading" type="text" value={this.state.search} onChange={this.updateSearch.bind(this)}/> : ""}
{/*Arrow function to map each added object*/}
{filteredBooks.map((post) =>(
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id}/> :
<Post key={post.id} post={post}/>}
</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => {
return{
posts: state
}
}
export default connect(mapStateToProps)(AllBook);
Your updated code seems to be pretty close. I think you might experience a problem with using indexOf() though, since that will only find the index of a single-character within a string (title). This would not be good for multi-character searches (like full-words).
Try using .includes() instead so that you can at least search against complete words and titles. It's essentially a better version of .indexOf()
See sandbox for example: https://codesandbox.io/s/silly-currying-3zpvk
Working code:
class AllBook extends Component {
constructor(props){
super(props);
this.state = {
search : ""
}
}
updateSearch(e){
this.setState({search: e.target.value.substr(0, 20)});
}
render(){
let filteredBooks = this.props.posts.filter(
(posts) => {
return posts.title.toLowerCase().includes(this.state.search.toLowerCase());
}
);
return(
<div>
{Object.keys(filteredBooks).length !== 0 ? <h1 className="post-heading">All books</h1> : <h1 className="post-heading">No Books available</h1>} {/*To check if array is empty or not*/}
{Object.keys(filteredBooks).length !== 0 ?
<input className="post-heading" type="text" value={this.state.search} onChange={this.updateSearch.bind(this)}/> : ""}
{/*Arrow function to map each added object*/}
{filteredBooks.map((post) =>(
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id}/> :
<Post key={post.id} post={post}/>}
</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => {
return{
posts: state
}
}
export default connect(mapStateToProps)(AllBook);
Updated code which is workable react search.

Component not re rendering even after calling the setState

I have a react component in which user can upload Image and he's also shown the preview of uploaded image. He can delete the image by clicking delete button corresponding to Image. I am using react-dropzone for it. Here's the code:
class UploadImage extends React.Component {
constructor(props) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.state = {
filesToBeSent: [],
filesPreview: [],
printCount: 10,
};
}
onDrop(acceptedFiles, rejectedFiles) {
const filesToBeSent = this.state.filesToBeSent;
if (filesToBeSent.length < this.state.printCount) {
this.setState(prevState => ({
filesToBeSent: prevState.filesToBeSent.concat([{acceptedFiles}])
}));
console.log(filesToBeSent.length);
for (var i in filesToBeSent) {
console.log(filesToBeSent[i]);
this.setState(prevState => ({
filesPreview: prevState.filesPreview.concat([
<div>
<img src={filesToBeSent[i][0]}/>
<Button variant="fab" aria-label="Delete" onClick={(e) => this.deleteImage(e,i)}>
<DeleteIcon/>
</Button>
</div>
])
}));
}
} else {
alert("you have reached the limit of printing at a time")
}
}
deleteImage(e, id) {
console.log(id);
e.preventDefault();
this.setState({filesToBeSent: this.state.filesToBeSent.filter(function(fid) {
return fid !== id
})});
}
render() {
return(
<div>
<Dropzone onDrop={(files) => this.onDrop(files)}>
<div>
Upload your Property Images Here
</div>
</Dropzone>
{this.state.filesToBeSent.length > 0 ? (
<div>
<h2>
Uploading{this.state.filesToBeSent.length} files...
</h2>
</div>
) : null}
<div>
Files to be printed are: {this.state.filesPreview}
</div>
</div>
)
}
}
export default UploadImage;
My Question is my component is not re-rendering even after adding or removing an Image. Also, I've taken care of not mutating state arrays directly. Somebody, please help.
Try like this, I have used ES6
.

How can I grab the key of a list item generated from a map function?

So I am learning React, and I've tried searching for solutions to my problem both on stackoverflow and on React's own documentation, but I am still stumped.
Essentially, I have a list of 10 subreddits that is being mapped to list items in the form of the subredditsArray variable.
I render the results, and try to pass the selected item when I click that list item to my getSubredditInfo function. However, this doesn't work - event.target.key is undefined. (To clarify, I am looking to grab the key of the single list element that I have clicked).
When I try to just get event.target, I get the actual htmlElement (ex: <li>Dota2</li>), where as I want to get the key, or at least this value into a string somehow without the tags. I also tried putting my onClick method in the list tag of the map function, but that did not work.
Here is the relevant code:
//this is where I get my data
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(event){
//console.log(event.target.key); <-- DOESNT WORK
}
render() {
var subredditsArray = this.state.subreddits.map(function(subreddit){
return (<li key={subreddit.toString()}>{subreddit}</li>);
});
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul onClick={this.getSubredditInfo}>{subredditsArray}</ul>
</div>
);
}
My questions essentially boil down to:
How do I grab the key value from my list object?
Additionally, is there a better way to generate the list than I currently am?
Thank you in advance.
EDIT: Added my componentDidMount function in hopes it clarifies things a bit more.
try the following code:
class App extends React.Component {
constructor(props){
super(props);
this.state = {subreddits:[]};
}
componentDidMount(){
fetch('https://www.reddit.com/api/search_reddit_names.json?query=dota2')
.then(results => {
return results.json();
})
.then(redditNames => {
//this is there I set my subreddits state variable to the array of strings
this.setState({subreddits: redditNames.names});
})
}
getSubredditInfo(subreddit){
console.log(subreddit);
}
render() {
return <div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>
{
this.state.subreddits.map((subreddit)=>{
return (<li key={subreddit.toString()} onClick={()=>this.getSubredditInfo(subreddit)}>{subreddit}</li>);
})
}
</ul>
</div>;
}
}
ReactDOM.render(
<App/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
please check the onClick event handler now. its an arrow function and its calling the getSubredditInfo function with your subreddit now. so you will get it there.
so its basically different way of calling the handler to pass data to the handler.
it works as you expect it to.
You can use lamda function or make component for item list which have own value for getSubredditInfo function
getSubredditInfo(value) {}
render() {
var subredditsArray = this.state
.subreddits.map((subreddit, i) =>
(<li key={i}
onClick={() => this.getSubredditInfo(subreddit)}>{subreddit}</li>));
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
1) Key should be grabbed either by the id in your object in array. Or you can combine the 2 properties to create a unique key for react to handle re-renders in a better way.
If you have a string array, you may use a combination of string value + index to create a unique value, although using index is not encouraged.
Given a quick example for both below.
2) A better way could be to move your map function into another function and call that function in render function, which will return the required JSX. It will clean your render function.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
subredditsObjArray: [
{ id: 1, value: 'A'},
{ id: 2, value: 'B'},
{ id: 3, value: 'C'},
{ id: 4, value: 'D'}
],
subredditsArray: ['A', 'B', 'C', 'D'],
selectedValue: ''
};
}
getSubredditInfo = (subreddit) => {
console.log(subreddit)
this.setState({
selectedValue: ((subreddit && subreddit.id) ? subreddit.value : subreddit),
});
}
render() {
return (
<div className="redditResults">
<p>Selected Value: {this.state.selectedValue}</p>
<h1>Top {this.state.subredditsArray.length || '0'} subreddits for that topic</h1>
<p>With Objects Array</p>
<ul>
{
this.state.subredditsObjArray
&& this.state.subredditsObjArray.map(redditObj => {
return (<li key={redditObj.id}><button onClick={() => this.getSubredditInfo(redditObj)}>{redditObj.value || 'Not Found'}</button></li>);
})
}
</ul>
<br />
<p>With Strings Array</p>
<ul>
{
this.state.subredditsArray
&& this.state.subredditsArray.map((reddit, index) => {
return (<li key={reddit + '-' + index}><button onClick={() => this.getSubredditInfo(reddit)}>{reddit || 'Not Found'}</button></li>);
})
}
</ul>
</div>
);
}
}
ReactDOM.render(
<App etext="Edit" stext="Save" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Are you trying to do this? I'm not sure what you want to do.
getSubredditInfo(e, subreddit) {
console.log(subreddit)
}
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
this.getSubredditInfo(e, subreddit)
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}
The key purpose is to pass your subreddit to the onClick function so you will receive the value while you click the item.
If you still get error try this and tell me what's happened.
render() {
const { subreddits } = this.state
var subredditsArray = subreddits.map(subreddit => (
<li
key={subreddit.toString()}
onClick={(e) => {
console.log(subreddit.toString())
}}
>
{subreddit}
</li>
))
return (
<div className="redditResults">
<h1>Top 10 subreddits for that topic</h1>
<ul>{subredditsArray}</ul>
</div>
);
}

The '.onChange' method doesn't get the newest info of 'Input' in React.js

import reqwest from './public/reqwest.js'
const PAGE_SIZE = 10
class App extends Component {
constructor() {
super()
this.state = {
page: 1,
arr: []
}
}
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: this.state.page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}
changeState(newState) {
this.setState(newState)
this.singleInfo(newState.page)
}
render() {
return (
<div>
<Menu val={this.state.page} changeParentState={(state) => this.changeState(state)} />
<List arr={this.state.arr} />
</div>
);
}
}
class Menu extends Component {
handleChange(event) {
if(event.target.value) {
this.props.changeParentState({
page: event.target.value
})
}
}
render() {
console.log(this)
return <input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
}
}
class List extends Component {
render() {
return <ul>
{
this.props.arr.map((ele) => {
return (
<li key={ ele.id }>
<p className="title">{ ele.title }</p>
<p className="date">{ ele.create_at }</p>
<p className="author">{ ele.author.loginname }</p>
</li>
)
})
}
</ul>
}
}
I can't get the current value of the input by onChange in Menu module.
In my code, the App has two child components - List & Menu.
You can input the page in Menu component, so it will send Ajax() to get the info of the page. But the truth is: After I change the value of input like 1 -> 10, the ajax get the value of 1.
Before this, I know the difference between keyup or keydown and keypress. They have the difference cross browser. But I just want get the current value of the input By React.js.
First, change:
<input type="text" defaultValue={this.props.val} onChange={(event) => this.handleChange(event)} />
To:
<input type="text" value={this.props.val} onChange={(event) => this.handleChange(event)} />
so that your input will update to the correct value on re-render.
Second, remember that setState is often asynchronous. So do not expect the state to be changed right after calling setState.
Your changeState method is good in this respect since it passes newState to singlePageRequest. However singlePageRequest does not use the supplied value and instead uses this.state.page. Change it to use the supplied value and you should be OK:
singleInfo(page) {
reqwest({
url: 'https://cnodejs.org/api/v1/topics',
data: {
limit: PAGE_SIZE,
page: page
},
success: function (data) {
this.setState({
arr: data.data
})
}.bind(this)
})
}

Categories

Resources