List is disappearing on refresh in react - reactjs

I'm trying to map out a list to display file information on a grid. I think the issue lies with my keys. When I change the keys, it renders, but on refresh it disappears. I'm using a unique key for each list item.
const FileList = () => {
const { state } = useDB();
return (
<div className='container'>
<div className='fileList-wrapper'>
<div className='fileList-wrapper__header'>Name</div>
<div className='fileList-wrapper__header'>Last modified</div>
<div className='fileList-wrapper__header'>File size</div>
{state.childFolders.map((folder) => {
return <ListItem key={folder.id} folder={folder} />;
})}
</div>
</div>
);
};
Here is the child contents:
const ListItem = ({ folder }) => {
return (
<>
<div
id='fileName'
className='fileList-wrapper__item'
onDoubleClick={handleDoubleClick}>
{folder.name}
</div>
<div className='fileList-wrapper__item'>{folder.timestamp}</div>
<div className='fileList-wrapper__item'>{folder.fileSize}</div>
</>
);
};
Here is the function that sets my state:
const setChildFolders = () => {
const childFolders = [];
database.folders
.where('parentId', '==', state.folderId)
.where('userId', '==', currentUser.uid)
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
childFolders.push({ id: doc.id, ...doc.data() });
});
})
.catch(() => {});
setState((prev) => ({
...state,
childFolders: childFolders,
}));
};
I'm calling that function in a useEffect:
useEffect(() => {
setChildFolders();
}, [currentFolder, currentUser]);
Just to reiterate, on first render, the list shows, but if I refresh, it disappears. If I then change the keys, it reappears.

Related

How to edit data received from a rest call in react?

I am trying to make a rest call, edit the data and then render it. The problem is that, while editing, I am getting a error - undefined even after checking if the data is there.
component I am making the rest call from:
function Header ({timesheetData, loadTimesheet}) {
useEffect(() => {
loadTimesheet(date)
}, [])
return(
<>
<header className="header">
<div className="wrap">
<span className="btn-icon">
<IconPlus onClick={() => setIsOpen(true)} className="icon icon-plus js-modal-init"/>
</span>
<div className="header-blockquote">
<h1 className="header-quote">{currentQuote.quote}</h1>
<div className="header-cite">{currentQuote.author}</div>
</div>
</div>
<div className="header-inner">
<div className="wrap">
<VegaIcon className="logo" alt="VegaIT"/>
<div className="date-wrap">
<IconCalendar className="icon icon-plus js-modal-init"/>
//
<time>{timesheetData.timesheet.loading ? "asd" : edit(timesheetData) }</time>
//
</div>
</div>
</div>
</header>
</>
)
}
function edit(timesheetData){
let newDate = timesheetData.timesheet.date
newDate = newDate.split("-")
newDate = newDate.reverse()
return newDate.join("/")
}
the redux action:
export const loadTimesheet = (date) => {
let url = "http://localhost:8080/api/timesheet/" + date
return (dispatch) => {
dispatch(getTimesheet)
axios.get(url)
.then((response) => {
const timesheet = response.data
dispatch(getTimesheetSuccess(timesheet))
})
.catch(error => {
const errorMsg = error.message
dispatch(getTimesheetFailure)
})
}
}
Edit: added my mapStateToProps and mapDispatchToProps
const mapStateToProps = state => {
return {
timesheetData: state.timesheet
}
}
const mapDispatchToProps = dispatch => {
return {
loadTimesheet: (date) => dispatch(loadTimesheet(date))
}
}
Edit2: The code: https://codesandbox.io/s/adoring-tharp-o9ibe
use mapStateToProps, mapDispatchToProps:
import getTimesheet from '../actions/...'
Header ({timesheetData, loadTimesheet}) => {
}
const mapDispatchToProps = dispatch => {
return {
loadTimesheet: () => dispatch(getTimesheet()),
}
}
const mapStateToProps = (state) => {
return { timesheetData: state.timesheetData };
};
export default connect(mapStateToProps, mapDispatchToProps)(Header);
also in component:
before render:
check that you have:
timesheetData - not undefined
timesheetData.timesheet - not undefined
timesheetData.timesheet.loading - not undefined
const hasData = timesheetData && !timesheetData.timesheet.loading;
const time = hasData ? edit(timesheetData): "asd";
in render:
<time>{time}</time>

Add a Spinner while waiting for promise to resolve in react

const fetchMusic= () => {
return new Promise((resolve) =>
setTimeout(() => {
const music = musicList.sort(() => 0.5 - Math.random()).slice(0, 4);
resolve({ data: music});
}, 300)
);
};
export default fetchMusic;
const getRandomMusic = () => {
return fetchMusic().then((result) => result.data);
};
const Button = (props) => {
return (
<div>
<Button {...props} onClick={getRandomMusic.bind(this)} />
<SomeComponent />
<p>Some text here</p>
</div>
);
};
I want add a spinner while waiting for the promise to resolve .
fetchMusic is in some other file.I m importing it in a component .
TLDR
How about use useState and useCallback for that action
Answer
At terms of react, use State for loading action is right use case.
So, When to start function, use can setLoading(true) and after action you can setLoading(false) for make loading effect
const fetchMusic= () => {
return new Promise((resolve) =>
setTimeout(() => {
const music = musicList.sort(() => 0.5 - Math.random()).slice(0, 4);
resolve({ data: music});
}, 300)
);
};
export default fetchMusic;
const Button = (props) => {
const [loaidng, setLoading] = useState(false);
const getRandomMusic = useCallback(() => {
setLoading(true)
return fetchMusic().then((result) => {
setLoading(false);
result.data
});
},[]);
return (
<div>
<Button {...props} onClick={getRandomMusic.bind(this)} />
{loading && <Sipinner/>}
<SomeComponent />
<p>Some text here</p>
</div>
);
};
Reference
Example of loading
ETC
If you have any other question. Just give me comment please.

Can't render a collection of data successfully fetched from firebase with react hook

I am fetching a docs collection from firebase and then passing it to a state so react re-renders it whenever it gets updated.
const [posts, setPosts] = useState([])
const fetchPosts = () => {
firebase.firestore().collection('posts').get()
.then(snap => {
snap.docs.forEach(doc => {
setPosts([...posts, doc.data()])
console.log(doc.data())
})
})
}
useEffect(() => {
fetchPosts()
}, [])
I also passed this state to other components so they also re-render with updated state
But react is only rendering the first doc of collection and giving error in console: 'every child should have a unique key prop'. My each doc object has a unique id inside and i am passing this as key with each post
<div className="posts section">
{posts.map(post=>{
return <Link to={'/posts/'+post.id}><PostCard post={post} key={post.id} /></Link>
})}
</div>
Google does not recommend using document/array data as a key, as subsequent renders can be inefficient. A lovely React function can take care of the unique key problem.
<div className="posts section">
{React.Children.toArray(
posts.map(post => {
return (
<Link to={"/posts/" + post.id}>
<PostCard post={post} />
</Link>
);
})
)}
</div>
Another problem you may be having is useEffect MUST be synchronous. you may want to explicitly declare fetchPosts as asynchronous. I use the following to process the querySnapshot:
return query
.get() //get the resulting filtered query results
.then(querySnapshot => {
return Promise.resolve(
querySnapshot.docs.map(doc => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
})
The best reason for the .map is you cannot guarantee the last "setPosts" has actually completed before your next loop, so your state ("posts", in this case), may be stale.
So, net of all of this, my pattern would be:
const [posts, setPosts] = useState([])
const fetchPosts = () => {
return firebase.firestore().collection('posts').get()
.then(snap => {
snap.docs.map(doc => {
console.log(doc.data())
return {
...doc.data(),
id: doc.id,
ref: doc.ref
};
})
});
}
useEffect(() => {
(async () => {
const newPosts = await fetchPosts();
setPosts(newPosts);
})();
}, [])
//[etc, etc]
return
//[etc, etc]
<div className="posts section">
{React.Children.toArray(
posts.map(post=>{
return <Link to={'/posts/'+post.id}><PostCard post={post} key={post.id} /></Link>
})
}
</div>
Add the key prop to the Link component.
Example
const fetchPosts = () => {
firebase
.firestore()
.collection("posts")
.get()
.then(snap => {
let docs = [];
snap.docs.forEach(doc => {
docs.push(doc.data());
});
setPosts( prevState => [...prevState, ...docs]);
});
};
<div className="posts section">
{posts.map(post => {
return (
<Link key={post.id} to={"/posts/" + post.id}>
<PostCard post={post} />
</Link>
);
})}
</div>
Hope this will fix the issue.

react component is no updateing the view after deleting an item

this the onDelete function which supposed to update the state and the item should disappear from the list once the delete button is pressed
onDelete = async (id) => {
const messages = await Api.deleteMessage(id);
const messageId = id;
const filterdMessages =this.state.messages.filter((message) => {
return message.id !== messageId
});
this.setState({
messages: [...filterdMessages]
});
}
and this what is being rendered
render() {
const {
messages
} = this.state;
const $messages = messages.map((message) => <MessageItem onDelete={this.onDelete} key={message.id} {...message} />);
console.log( $messages);
return (
<section className="messages">
<ul>
{$messages}
</ul>
</section>
)
}
this the messageItem
export default ({ id, body, onResolve, onDelete, license_plate }) => {
const onResolveClick = (event) => {
event.preventDefault();
onResolve(id);
};
const onDeleteClick = (event) => {
event.preventDefault();
onDelete(id);
};
return (
<li className="MessageItem">
<span> <b>{license_plate} </b>{body}</span>
<button onClick={onDeleteClick}>Delete</button>
</li>
)
}
You are using the key as key={message._id} but filtering by message.id.
// v Always undefined, the expression always false.
message.id !== messageId
const filterdMessages = this.state.messages.filter(message => {
// v message.id
return message._id !== messageId;
});
Should be onDelete={() => this.onDelete(message._id)}
There are 2 problems in your code
1) You are not passing the ID in argument
2) You are using _id instead of id for key prop
_onDelete = async (id) => {
await Api.deleteMessage(id);
const { messages } = this.state;
const filterdMessages = messages.filter((message) => message.id !== messageId);
this.setState({ messages: [...filterdMessages]});
}
render() {
const { messages } = this.state;
return (
<section className="messages">
<ul>
{
messages.map(message => (
<MessageItem
onDelete={() => this._onDelete(message.id)}
key={message.id}
{...message}
/>
))
}
</ul>
</section>
)
}

Populating stateless child component props from parent component state to display nested unordered list in React (with Firebase)

I'm probably missing something very simple here, but I can't get my code to run properly. I'm trying to display a nested unordered list of referenceListItems for every referenceList. The main question I guess is how do I pass the state variable referenceListItems into the child component ReferenceListItems?
const ReferencePage = () => (
<div>
<h1>Reference</h1>
<Reference />
</div>
);
class ReferenceBase extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
referenceLists: [],
referenceListItems: [],
};
}
componentDidMount() {
this.onListenForReferenceLists();
this.onListenForReferenceListItems();
}
onListenForReferenceLists() {
this.setState({ loading: true });
this.unsubscribeReferenceLists = this.props.firebase
.referenceLists()
.onSnapshot(snapshot => {
if (snapshot.size) {
let referenceLists = [];
snapshot.forEach(doc =>
referenceLists.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
referenceLists: referenceLists,
loading: false
});
} else {
this.setState({ referenceLists: null, loading: false });
}
});
}
onListenForReferenceListItems() {
this.unsubscribeReferenceListsItems = this.props.firebase
.referenceListItems()
.onSnapshot(snapshot => {
if (snapshot.size) {
let referenceListItems = [];
snapshot.forEach(doc =>
referenceListItems.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
referenceListItems: referenceListItems,
loading: false
});
} else {
this.setState({ referenceListItems: null, loading: false });
}
});
}
componentWillUnmount() {
this.unsubscribeReferenceLists();
this.unsubscribeReferenceListsItems();
}
render() {
const { referenceLists, referenceListItems, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{referenceLists ? (
<ReferenceLists referenceLists={referenceLists} />
):(
<div>There are no reference items ...</div>
)}
</div>
);
}
}
const Reference = withFirebase(ReferenceBase);
const ReferenceLists = ({ referenceLists }) => (
<ul className="reference-lists">
{referenceLists.map( referenceList => (
<ReferenceList key={referenceList.uid} referenceList={referenceList} />
))}
</ul>
);
const ReferenceList = ({ referenceList }) => (
<li className="reference">
<strong>{referenceList.userId}</strong> {referenceList.name}
<ReferenceListItems />
</li>
);
const ReferenceListItems =({ referenceListItems }) => (
<ul className="reference-list-items">
{referenceListItems.map( referenceListItem => (
<ReferenceListItem key={referenceListItem.uid} referenceListItem={referenceListItem} />
))}
</ul>
);
const ReferenceListItem = ({ referenceListItem }) => (
<li className="reference-list-item">
<strong>{referenceListItem.userId}</strong> {referenceListItem.name}
</li>
);
You do not explicitly use your ReferenceListItems inside the parent ReferenceBase component. So you'll just have to pass it down as a property throughout the component tree.
render() {
const { referenceLists, referenceListItems, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{referenceLists ? (
<ReferenceLists referenceLists={referenceLists} referenceListItems={referenceListItems} />
):(
<div>There are no reference items ...</div>
)}
</div>
);
ReferenceLists
const ReferenceLists = ({ referenceLists, referenceListItems }) => (
<ul className="reference-lists">
{referenceLists.map( referenceList => (
<ReferenceList key={referenceList.uid} referenceList={referenceList} referenceListItems={referenceListItems} />
))}
</ul>
ReferenceList
const ReferenceList = ({ referenceList, referenceListItems }) => (
<li className="reference">
<strong>{referenceList.userId}</strong> {referenceList.name}
<ReferenceListItems referenceListItems={referenceListItems}/>
</li>
);
By redeclaring referenceList
let referenceLists = [];
it never gets set in your class. you either need to return the referenceLists inside the closure or set the class level variable in your callback
this.referenceLists.push({ ...doc.data(), uid: doc.id })

Resources