REACT fetch before rendering - reactjs

I try to map the fetched data but I always get an error in my mapping because the data hasn't fetched before I use the map function. I'm able to get a get a specific element in from my fetched data using a click event.
parent class where I fetch my datas, I need the beer data for my mapping.
class App extends Component{
constructor(props){
super(props);
this.props.fetchUser();
this.props.fetchBeers();
}
class where I try to map my beers:
class BeersLanding extends Component{
getBeers = () => {
let beers= this.props.beers;
console.log(beers);
console.log(beers[0]);
console.log(beers[1]);
}
constructor(props){
super(props);
}
render(){
const {loading} =this.props;
console.log(this.props.beers)
return(
<div style={{textAlign:'center'}}>
...
<input type="text" placeholder="Search.." name="search"></input>
<button type="submit" onClick={() =>this.getBeers()} >Submit</button>
<div className={'beersContainer'}>
{this.props.beers.map((beer,index) =>(
<div className={'card'}>
hello
</div>
))}
</div>
</div>
);
}
}
Action method:
export const fetchBeers = () => async dispatch => {
const res = await axios.get('/api/beers');
dispatch({type:FETCH_BEERS, payload:res.data});
};
reducer:
export default function(state=null, action){
// console.log(action);
switch(action.type){
case FETCH_BEERS:
return action.payload;
default:
return state;
}
}

There are tow options to solve this issue, first option and I recommended to use this, by using defaultProps and set default value of bees as array.
the second option by add condition before map your data
{this.props.beers && this.props.beers.map((beer,index) =>(
<div className={'card'}>
hello
</div>
))}

I would recommend using react life cycle hooks for this type of issues.
componentDidMount() {
this.props.YOURACTIONS()
}
So this will happen when component is loaded.

Related

How to resolve list of objects not rendering

Hello I am new to ReactJS so I am just practising on working with states and also so a good practice for a starting point I thought why not the classic TODO App.
So I do not know why the object is not being rendered or being added because when I even console logged the object It did not even show that it's empty or anything the was literally no output so I do not know where I could have went wrong with this methods
Code Below App.js: This is the file that has all the methods and state control of the TODO APP
import React, { Component } from 'react';
import style from './stylesheet/app.css'
import ListItems from './ListItems'
class App extends Component{
constructor(props){
super(props);
this.state = {
items:[],
currentItem:{
notes: '',
key: ''
}
}
this.handleInput = this.handleInput.bind(this);
this.addItem = this.addItem.bind(this);
}
// Handling user Input to save on before I add to the Items
// this.state.currentItems is a temporary store place for TODO'S
handleInput(e){
this.setState({
currentItem: {
notes: e.target.value,
key: Date.now()
}
})
}
// After handling input input once the add button is clicked I want to add
// the the object in the temporary storage into the permanent store place that is the
// this.state.items --> permanent store place
addItem(e){
e.preventDefault()
const newTodo = this.state.currentItem;
if (newTodo.text !== " "){
const newTodos = [...this.state.items, newTodo];
this.setState({
items:newTodos,
currentItem:{
notes:'',
key:''
}
})
}
}
render(){
return(
<div className="container" style={style}>
<div className='todo-form'>
<form id="form">
<input type="text"
placeholder="Enter in your todo's"
value={this.state.currentItem.notes}
onChange={this.handleInput}></input>
<button type="submit" onSubmit={this.addItem}>Add Todo</button>
</form>
<ListItems items={this.state.items}/>
</div>
</div>
)
}
}
export default App
Code Below ListItems.js: This file contains code where I tried to map through the ojects to display the TODO'S
import React from 'react';
const ListItems = (props) =>{
const items = props.items;
const listItems = items.map(item =>{
return <div className="todo-list" key={item.key}> <p>{item.key}</p> </div>
})
return(
<div>
{listItems}
</div>
)
}
export default ListItems
Can you please help me figure out where I could be going wrong?
Your code works perfectly fine. Just add addItem function to form element, so it preventsDefault correctly and doesnt reload whole page:
<form id="form" onSubmit={this.addItem}>
See here: https://codesandbox.io/s/musing-gareth-vlkmx

React - how to map buttons in a list to the object they are in?

I have the following class Component which reads data from localstorage.
The Localstorage has an array of objects. Those objects are rendered in a list as you can see below. In each list item there is a button I added in the code. If a user clicks the <ExtendButton /> I want to extend the {el.infoDays} of 7 days.
Can anyone help me with that, or at least with binding the button to the object it is in, so that if a user clicks the button I will get the whole object (where the button is in) displayed in the console.log?
I tried the following, I tried with e.target, this, etc. The onExtendBtnClick method is not well written.
let uniqid = require('uniqid');
class History extends Component {
state = {
loans: []
};
componentDidMount() {
const rawInfos = localStorage.getItem('infos');
const infos = JSON.parse(rawInfos);
this.setState({
infos: infos
});
}
render() {
const {infos} = this.state;
return (
<Container>
<Header />
<div>
<ul>
{infos.map((el) => (
<li key={uniqid()}>
<small>Requested date: {el.infoRequestTime}</small><br />
<div>
<span ><em>Requested amount</em><em>{el.infoAmount} €</em></span>
<span><em>In days</em><em>{el.infoDays}</em></span>
</div>
<spa>Give back: {el.infoCost} €</span>
<ExtendButton />
</li>
))}
</ul>
</div>
</Container>
);
}
}
export default History;
And I have also the button component:
class ExtendButton extends Component {
onExtendBtnClick = () => {
console.log(this)
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
export default ExtendButton;
Have your button component take in an onClick prop and set that on its own internal button:
class ExtendButton extends Component {
onExtendBtnClick = () => {
this.props.onClick();
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
Then just pass an onClick function to your component:
<ExtendButton onClick={() => {console.log(el)}} />

setState in Const Props ReactJS

I want to setstate of PageSearchByExcel's class but I know that (this) is no longer to PageSearchByExcel.
Have you some way to set state to a totalMatchSample variable.
I bring this code from ant-design Official web.
https://ant.design/components/upload/
I'm very new to react.
Help me, please.
Or If you have another way that is better than this way please give it to me.
import ...
const props = {
name: 'file',
multiple: true,
action: API_URL + '/api/search/excelfile',
onChange(info) {
const status = info.file.status;
const data = new FormData()
data.append('file', info.file.originFileObj, info.file.name)
Axios.post(props.action, data)
.then((Response) => {
this.setState({
totalMatchSample: info.file.response.length
})
})
},
};
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
render() {
return (
<div>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
Since you're declaring props outside the PageSearchByExcel component the this refers to props object itself and not the component. You can define the onChange method on the component and pass it down as a prop to Dragger which then will be correctly bound to PageSearchByExcel.
import ...
const props = {
name: 'file',
multiple: true,
action: API_URL + '/api/search/excelfile',
};
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
// Define onChange here
onChange = (info) => {
const status = info.file.status;
const data = new FormData()
data.append('file', info.file.originFileObj, info.file.name)
Axios.post(props.action, data)
.then((Response) => {
this.setState({
totalMatchSample: info.file.response.length
})
})
}
render() {
return (
<div>
<Dragger {...props} onChange={this.onChange}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
Hope this helps !
#sun, based on what you posted, i will assume that you have some sort of props being passed to PageSearchByExcel component.
having said that, that props object, its an anti-pattern, you really want to pass each key in that props object to PageSearchByExcel down via the props system.
ex:
Class ParentComponent ...
...some code
render () {
..someJSX
<PageSearchByExcel name='file' multiple={true} />
}
this will basically setup your props inside PageSearchByExcel
now thats out of the way, let's talk about setState({}) and loading resources
in your PageSearchByExcel Component, you would have something like this
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
// define your axios call inside your component class NOT OUTSIDE
loadResource = () => {
// ....yourAxiosCodeHere
}
// You then set the state at the first load of the component
componentDidMount () {
this.loadResource()
}
render() {
return (
<div>
<Dragger {...this.props}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
TAKEAWAY::
1.) define your class methods inside the class itself in order the properly reference 'this'
2.) make sure you pass the props down from the parent component down to your PageSearchByExcel
3.) make sure you load your resource using the react life cycle method
This should get you going.

Passing API data from parent container to child component in React

I have a React container with a number of child components. One of which is supposed to be a modal that will show the user their name which is fetched from a user data api in the parent container. I should be able to pass the user data into the child with a prop, but must be missing something, as the display name does not show in the input as the value.
Parent Container
class ParentContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
displayName: this.state.user.displayName
}
this.config = this.props.config
}
async componentDidMount () {
try {
const userData = await superagent.get(`/api/user`)
await this.setState({ user: userData.body })
console.log(userData.body.displayName) <===logs out user display name
} catch (err) {
console.log(`Cannot GET user.`, err)
}
}
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
</div>
)
}
}
export default ParentContainer
Child Component
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text" placeholder={this.props.displayName}/>
<button
onClick={this.submitName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p>{contentStrings.displayNameModal.cancel}</p>
</div>
)
}
}
export default DisplayNameModal
I found that adding displayName: userData.body.displayName to setState and then wrapping the component in the parent with
{this.state.displayName &&
<div>
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
</div>
}
works as the solution.
The prop should be passed by:
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
where you are using:
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
You have set the displayName on state in the parent, anything you refer to from state should be referred to as this.state.foo, where as any method on that component can be referred to as this.foo.
First of all, you fetch the data in wrong way, you can check it here:
componentDidMount () {
superagent.get(`/api/user`).then(res => this.setState({ user: res.body }))
}
the second one, initialize default state for displayName, for example, an empty string, it will be replaced when promise retrieves data data from the server:
constructor (props){
super(props)
this.state = {
displayName: ''
}
}
and pass this state as props to your child component:
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.props.displayName} />
</div>
)
}
in your child component, you can simply call this props:
<input type="text" placeholder={this.props.displayName}/>

State not updating in Component

Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010

Resources