I'am trying to fetch data from an API, then set it on my State and display that state in a table. The issue is that the render method is called first and causes my state to be undefined which causes this issue:
The console.log()
https://i.gyazo.com/642f8d6fe3481d2db9763091618e19de.png
state = {
loading: true,
data: [],
customColumns: [],
}
componentDidMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
axios.get('http://localhost:8080/lagbevakning/company?id=' + sessionStorage.getItem("id")).then(response2 => {
this.setState({
customColumns: response2.data
})
})
}
displayCustomColumn = (columnInput) => {
if(columnInput === null) {
return
} else {
return <Table.HeaderCell>{columnInput}</Table.HeaderCell>
}
}
displayList = () => {
return (
<div>
<Table celled>
<Table.Header>
<Table.Row>
{this.displayCustomColumn(this.state.customColumns.customHeaderName1)}
{this.displayCustomColumn(this.state.customColumns.customHeaderName2)}
</Table.Row>
</Table.Header>
{this.state.data.map((item, i) => (
<Table.Body key={i}>
<Table.Row>
<Table.Cell>{item}</Table.Cell>
<Table.Cell>{item}</Table.Cell>
</Table.Row>
</Table.Body>
))}
</Table>
</div>
)}
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
)
}
}
Any suggestions on how to solve this issue is much appreciated, thank you.
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
)
}
I think you are expecting data to be array so you can't access name and id from data state. Check structure of your response and set it accordingly.
Can you try adding the following :
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.state.data.length > 0 && this.displayList()}
</div>
)
}
You can probably try this.
render() {
if (this.state.loading) return <div><h1>LOADING...</h1></div>;
return (
<div>
{this.state.data && this.state.data.length &&
<div>
<h2> Company Name: {this.state.customColumns.companyName} <br />
Revision Name: {this.state.data.name} <br />
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
</div>
)
}
}
You can return immediately if loading is true.
Below that you can check for data which is array of objects.
Related
I'm fetching data from my backend using Axios in ReactJS. The response should be an array of data with I believe my backend returns 5 elements and I have proved it using postman.
Here is my code :
export const getRoomMessages = async (chatRoomId, limit = 20, page = 1) => {
try {
const response = await backendApi.get(
`/chat_rooms/${chatRoomId}/chat_room_messages?limit=${limit}&page=${page}`,
);
console.log('reponse', response.data.data);
console.log('data0', response.data.data[0]);
console.log('data1', response.data.data[1]);
console.log('data2', response.data.data[2]);
console.log('data3', response.data.data[3]);
console.log('data4', response.data.data[4]);
return [null, response.data];
} catch (e) {
return [e, null];
}
};
And here is the screenshot of the console log:
Before I expand the response data it was detected as Array with have 5 elements. But after I expand the response data it became 4 elements ? I'm missing the one element. So I tried to console log all the array element from index 0 to 4, and surprisingly it was no error and I got the missing element. But there is one more strange thing, the console log order is also random if we look at the data[0] in the response tab (which I border with blue) is the data with id "241" but when I console the log separately (which I border with red) it becomes "254" which is the missing element.
I'm still not sure what's causing this problem and I'm not sure it's a bug in Axios.
Full Source Code (How I updated my State) :
componentDidMount() {
this.initialFetch();
this.connectToWebSocket();
}
componentWillUnmount() {
this.disconnectFromWebsocket();
}
initialFetch = async () => {
this.fetchRoomDetail();
this.fetchMessages();
};
fetchMessages = async () => {
this.setState({ loading: true, error: null });
const [err, res] = await getRoomMessages(
this.roomId,
this.state.pagination.size,
1,
);
if (res) {
this.setState({
messages: res.data.reverse(),
pagination: {
...this.state.pagination,
current: 2,
},
noMoreLoad: res.paging.total_page <= 1 ? true : false,
loading: false,
});
this.scrollToBottom();
await this.readMessage(res.data.pop().id);
} else {
this.setState({ error: errorToString(err), loading: false });
}
};
render() {
return (
<AdminLayout>
{this.state.error ? (
<Error500Page
errMessage={this.state.error}
tryAgain={this.initialFetch}
/>
) : this.state.loading ? (
<ActivityIndicator
number={3}
diameter={10}
borderWidth={1}
duration={200}
activeColor={theme.colors.ui.primary}
/>
) : (
<div className="card bg-theme-secondary mxH75">
<div className="customCardHeader">
<i
className="bx bx-arrow-back mr-2p sizeLarge pointer"
onClick={() => this.props.navigate('/chats/')}
></i>
<Avatar
src={`https://ui-avatars.com/api/?background=${theme.colors.ui.primary.substring(
1,
)}&name=${
this.state.room?.chat_room_title
}&size=56&color=${theme.colors.text.inverse.substring(1)}`}
alt={this.state.room?.chat_room_title + "'s Profile Picture"}
size="medium"
type="circle flexible"
/>
<h5 className="noMarginBottom">
{this.state.room?.chat_room_title}
</h5>
</div>
<div className="customContent" id="vertical-example">
{this.state.noMoreLoad ? null : (
<div className="rce-container-smsg m-1">
{this.state.loadingMore ? (
<div
className="spinner-border spinner-border-sm text-success"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : (
<Button
onClick={() => this.fetchMoreMessages()}
color={theme.colors.text.inverse}
backgroundColor={theme.colors.ui.primary}
text="Muat Pesan Sebelumnya"
/>
)}
</div>
)}
{this.state.messages?.map((x) =>
x.user ? (
<MessageBox
key={x.id.toString()}
onDownload={
x.image_url
? () => this.saveAs(x.image_url)
: x.file_url
? () => this.saveAs(x.file_url)
: null
}
removeButton={
x.user.id === this.currentUserId && !x.is_deleted
? true
: false
}
onRemoveMessageClick={() => this.unsentMessage(x.id)}
forwarded={!x.is_deleted ? true : false}
title={this.state.room?.type_id === 1 ? x.user.name : null}
position={
x.user.id === this.currentUserId ? 'right' : 'left'
}
type={x.image_url ? 'photo' : x.file_url ? 'file' : 'text'}
text={
x.is_deleted
? Parser(
'<i class="bx bx-comment-x" style="color:red;"></i><i style="color:red;"> Pesan ini telah dihapus</i>',
)
: x.is_forwarded
? Parser(
`<i class="bx bx-subdirectory-right" style="color:blue;"></i><i style="color:blue;"> Forwarded</i><br/>${x.message}`,
)
: x.file_url
? x.file_url.split('/').pop()
: x.message
}
data={
x.image_url
? {
uri: x.image_url,
alt: x.image_url.split('/').pop(),
width: 300,
height: 300,
status: {
autoDownload: false,
error: false,
download: true,
click: true,
loading: false,
},
}
: x.file_url
? {
name: x.file_url.split('/').pop(),
extension: x.file_url.split('.').pop(),
uri: x.file_url,
status: {
autoDownload: false,
error: false,
download: false,
click: false,
loading: false,
},
}
: null
}
date={new Date(x.created_at_timestamp * 1000)}
/>
) : (
<SystemMessage text={x.message} key={x.id.toString()} />
),
)}
<div ref={this.messagesEndRef} />
</div>
<div className="sendMessageContainer">
<Input
value={this.state.focusedMessage}
onChange={(e) =>
this.setState({ focusedMessage: e.target.value })
}
// referance={this.inputReferance}
placeholder="Masukkan Pesan"
multiline={true}
className="form-control"
leftButtons={
<>
{/* <input type={'file'} />
<input type={'file'} /> */}
</>
}
rightButtons={
this.state.sendingMessage ? (
<div className="spinner-border text-success" role="status">
<span className="visually-hidden">Loading...</span>
</div>
) : (
<Button
onClick={() => this.sendMessage()}
color={theme.colors.text.inverse}
backgroundColor={theme.colors.ui.primary}
text="Kirim"
/>
)
}
/>
</div>
{/* <div>{JSON.stringify(this.state.messages)}</div> */}
</div>
)}
</AdminLayout>
);
}
return [null, response.data];
Shouldn't it be
return [null, response.data.data];
?
Finally I found my mistake, when I want to get the last element's id, I used pop() method. I have updated my code with res.data[res.data.length-1] and it works now !
I'm unable to show conditional output with the ternary operator. I want to pass a value to a function and show only related info from the state. My code:
import React, {useState} from 'react';
function Tasks({taskId, index}){
{task.parentId == taskId : } //Unable to code this.
return( //show only tasks where parentId == taskId
<div>
<div> {task.title} </div>
<div> {task.body} </div>
</div>
)
}
function App(){
const[tasks, setTasks] = useState([
{
taskId: 1,
title: 'Task1',
body: 'This is the body of the task1',
isComplete: false,
parentId: 0
},
{
taskId: 2,
title: 'Task2',
body: 'This is the body of the task2',
isComplete: false,
parentId: 1
},
{
taskId: 3,
title: 'Task3',
body: 'This is the body of the task3',
isComplete: false,
parentId: 1
},
{
taskId: 4,
title: 'Task4',
body: 'This is the body of the task4',
isComplete: false,
parentId: 3
}
])
return(
<div style={{marginLeft: 20}}>
<h1>ToDo</h1>
{tasks.map((task, index)=>
<Tasks
taskId=1
/>
)}
</div>
)
}
export default App;
So, I want to only show the tasks that have the parentId as 1. How should I go about this?
If you're trying to render only those tasks with the specified id, you may not have to use the ternary operator.
function renderTasks(id) {
return tasks
.filter(({ taskId }) => taskId == id)
.map(({ title, body }) => (
<div>
<div> {title} </div>
<div> {body} </div>
</div>
));
}
For the least modification to the code, you can return an empty fragment or null:
function Tasks({ task }) {
return task.parentId == task.taskId
? (
<div>
<div> {task.title} </div>
<div> {task.body} </div>
</div>
)
: null;
}
(make sure to use parentId, not pasrentId, and task.taskId, not taskId - you aren't passing task as a prop currently, so change the code to do so: <Tasks task={task} />)
But I think it'd make more sense to use .filter in the caller:
return (
<div style={{ marginLeft: 20 }}>
<h1>ToDo</h1>
{tasks
.filter(task => task.parentId === task.taskId)
.map(task => <Task task={task} />)
}
</div>
)
(since Tasks renders a single task, consider calling it Task instead of Tasks)
I'm trying to make a components props(sideBarInfo) details show up on the left column of a Page after clicking on a corresponding component(thumbnail) on the right column of the same Page.
Please note that all imports and exports are used in the main project(i removed them here).
I've also imported all components into the main (Page.js). Yet i keep getting a Type error for the onClick.
This is the first component - Thumbnail
class Thumbnail extends React.Component {
render(){
return (
<div className="Work" onClick={(e) => this.props.click(this.props.work)} >
<div className="image-container">
<img src={this.props.work.imageSrc} alt={this.props.work.imageSrc}/>
</div>
<div className="Work-information">
<p> {this.props.work.work}</p>
</div>
</div>
);
} }
This is the ThumbnailList
class ThumbnailList extends React.Component {
constructor(props){
super(props);
this.state= {
works: [
{
id: 0,
work: 'Work 1',
imageSrc: W1,
view: '#',
selected: false
},
{
id: 1,
work: 'Work 2',
imageSrc: W2,
view: '#',
selected: false
},
]
}
}
handleCardClick = (id,card) => {
console.log(id);
let works= [...this.state.works];
works[id].selected = works[id].selected ? false : true ;
works.forEach(work=>{
if(work.id !== id){
work.selected = false;
}
});
this.setState({
works
})
}
makeWorks = (works) => {
return works.map(work => {
return <Thumbnail work={work} click={(e => this.handleCardClick(work.id,e ))} key={work.id} />
})
}
render(){
return(
<div>
<div className="scrolling-wrapper-flexbox">
{this.makeWorks(this.state.works)}
</div>
</div>
);
}
}
This is the sidebarInfo
function SidebarInfo(props) {
return (
<img width="370" height="370" src= {props.imageSrc} />
<p> {props.work} </p>
);}
This is the problematic Page - the boldened keeps giving a Type error(cannot read property 'selected' of undefined.)
class Page extends React.Component {
render() {
return (
<span>
<div className="column left">
<div className="">
**{this.props.work.selected && <SidebarInfo imageSrc={this.props.imageSrc} /> }**
</div>
</div>
<div className="column right" >
<div>
<ThumbnailList />
</div>
</div>
</span>
)
}
}
You may need to add a check in this.props.work.selected like this this.props.work && this.props.work.selected
To ensure that this.props.work is defined before checking on this.props.work.selected
I was able to answer this for anyone interested.
there were some foundational errors in my file arrangement which have been corrected.
After refactoring, i was able to achieve what i wanted using 3 classes/functions in; App.js, Thumbnail.js and SideBarInfo.js
see one working solution with styling on this sandbox: https://codesandbox.io/s/modern-sky-y9d3c
See Solution below;
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
works: [
{
id: 0,
work: "Work 1",
imageSrc:
"https://images.unsplash.com/photo-1584608168573-b6eec7a04fd7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
view: "#",
selected: false
},
{
id: 1,
work: "Work 2",
imageSrc:
"https://images.unsplash.com/photo-1581665269479-57504728e479?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
view: "#",
selected: false
}
]
};
this.handleCardClick = this.handleCardClick.bind(this);
this.makeWorks = this.makeWorks.bind(this);
this.showWorks = this.showWorks.bind(this);
}
handleCardClick = (id, Thumbnail) => {
console.log(id);
let works = [...this.state.works];
works[id].selected = works[id].selected ? false : true;
works.forEach((work) => {
if (work.id !== id) {
work.selected = false;
}
});
this.setState({
works
});
};
makeWorks = (works) => {
return works.map((work) => {
return (
<Thumbnail
work={work}
click={(e) => this.handleCardClick(work.id, e)}
key={work.id}
/>
);
});
};
showWorks = (works) => {
let i = 0;
var w = [];
while (i < works.length) {
if (works[i].selected) {
w = works[i];
}
i++;
}
return ( <SidebarInfo imageSrc={w.imageSrc} work={w.work} /> );
};
render() {
return (
<div className="App">
<span>
<div> { this.showWorks(this.state.works)} </div>
<div className="">
<div className="scrolling-wrapper-flexbox">
{this.makeWorks(this.state.works)}
</div>
</div>
</span>
</div>
);
}
}
class Thumbnail extends React.Component {
render() {
return (
<div>
<div className="column left">
{/* this.props.work.selected && <SidebarInfo work={this.props.work.work} imageSrc={this.props.work.imageSrc}/> */}
</div>
<div className="column right">
<div className="Work" onClick={(e) => this.props.click(this.props.work)}>
<div className="image-container">
<img src={this.props.work.imageSrc} alt={this.props.work.imageSrc} />
</div>
<div className="Work-information">
<p> {this.props.work.work}</p>
</div>
</div>
</div>
</div>
);
}
}
export default Thumbnail;
function SidebarInfo(props) {
return (
<div className="one">
<div className="Work">
<h1> NAME </h1>
<div className="image-container">
<img
width="370"
height="370"
src={props.imageSrc}
alt={props.imageSrc}
/>
</div>
<p> {props.work} </p>
<p> {props.view} </p>
</div>
</div>
);
}
export default SidebarInfo;
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.
The following code is running smoothly, but I want to implement it in a way that when I do a getNextPers() and there is no info, it hides/removes the Ver Mais button. I've been looking for solutions but have found none, so any help is good. Thank you.
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class List extends React.Component {
constructor(props){
super(props);
this.state = {
personagens: [],
page: 1,
showBtn: true,
};
this.getNextPers = this.getNextPers.bind(this);
}
getNextPers(){
const peopleApiEndpoint = `https://swapi.co/api/people/${this.state.page}`;
axios.get(peopleApiEndpoint).then((p) =>
if(p=={}){
this.setState({ showBtn: false });
}
else {
this.setState({ personagens: this.state.personagens.concat(p), page: this.state.page+1 })
}
);
}
render(){
return (
<div>
<p><b>Personagens:</b></p>
{this.state.personagens.map((pers, i) => (
<div key={i}>
<br />
<p><i>Name:</i> {pers.data.name}</p>
<p><i>Height:</i> {pers.data.height} cm</p>
<p><i>Mass:</i> {pers.data.mass} kg</p>
</div>
))}
<button onClick={this.getNextPers}>Ver Mais</button>
</div>
);
}
}
ReactDOM.render(<List />, document.getElementById('root'));
The real problem is here:
axios.get(peopleApiEndpoint).then((p) => {
if (p == {}) { // THIS WILL NEWER WORK AS EXPECTED
this.setState({showBtn: false});
} else {
this.setState({
personagens: this.state.personagens.concat(p),
page: this.state.page + 1
});
}
});
Also swapi return 404 when there is no more results instead of empty object so you need to add catch block to your axios.get as described in docs: https://github.com/axios/axios#handling-errors
axios.get(peopleApiEndpoint).then((p) => {
this.setState({
personagens: this.state.personagens.concat(p),
page: this.state.page + 1
});
}).catch((err) => {
this.setState({showBtn: false});
});
Now you can use conditional rendering like:
{(this.state.showBtn && <button onClick={this.getNextPers}>Ver Mais</button>)}
First thing getNextPers does not return anything and you can achieve the show/hide by using condintion in your code
{ this.your_condition ?
<button onClick={this.getNextPers}>Ver Mais</button> : ''
}
As addition to Ramya answer you can also use
render(){
return (
<div>
<p><b>Personagens:</b></p>
{this.state.personagens.map((pers, i) => (
<div key={i}>
<br />
<p><i>Name:</i> {pers.data.name}</p>
<p><i>Height:</i> {pers.data.height} cm</p>
<p><i>Mass:</i> {pers.data.mass} kg</p>
</div>
))}
{ this.state.showBtn && <button onClick={this.getNextPers}>Ver Mais</button> }
</div>
);
}
Since you're storing the showBtn state in your component, you can use it to conditionally render the button as follows:
render(){
return (
<div>
<p><b>Personagens:</b></p>
{this.state.personagens.map((pers, i) => (
<div key={i}>
<br />
<p><i>Name:</i> {pers.data.name}</p>
<p><i>Height:</i> {pers.data.height} cm</p>
<p><i>Mass:</i> {pers.data.mass} kg</p>
</div>
))}
{ (this.state.showBtn) ?
<button onClick={this.getNextPers}>Ver Mais</button>
:
null
}
</div>
);
}