Error Boundry not catching some errors in NextJS - reactjs

I'm trying to add a ErrorBoundry component to my NextJS project called ErrorShield
ErrorShield:
import { Component, useState } from "react";
export class Catch extends Component {
componentDidCatch(error, info) {
const { component } = this.props;
this.props.setErrorInfo({
hasError: true,
error,
info,
component,
});
}
render() {
const { hasError, children, errorMessage } = this.props;
if (hasError) errorMessage();
return hasError ? null : children;
}
}
const ErrorShield = (props) => {
const { children } = props;
const [errorInfo, setErrorInfo] = useState({
component: "",
error: null,
info: null,
hasError: false,
});
const errorMessage = () => {
if (errorInfo.hasError) {
console.groupCollapsed(
`%c ⛔ Error in the component: ${errorInfo.component} `,
"color:#ff3333;",
);
console.groupCollapsed("%c 📖 Props:", "color:#039BE5;");
console.table(children.props);
console.groupEnd();
console.groupCollapsed("%c 🚨 Error:", "color:#F44336;");
console.log(errorInfo.error);
console.groupEnd();
console.groupEnd();
}
};
return (
<Catch
{...props}
hasError={errorInfo.hasError}
errorMessage={errorMessage}
setErrorInfo={setErrorInfo}
/>
);
};
export default ErrorShield;
At first it was working as intended but I notices that sometimes if I break some components the ErrorShield won`t catch the error, I tried to see if the behavior had any consistency but I cant find it.
For example, my project has a Header like this:
const Header = (props) => {
const { navigation } = props;
const [titles, setTitles] = useState(navigation);
.....
{/*HeaderLinkContainer, HeaderLinkBox, HeaderLiveLink and HeaderSubLink are styled components*/}
<HeaderLinkContainer variant="Transparent" as="nav">
<HeaderLinkBox variant="Transparent">
{titles.map((title) => {
let LinkTag;
if (title.live) {
LinkTag = HeaderLiveLink;
} else if (title.subnav) {
LinkTag = HeaderSubLink;
} else {
LinkTag = HeaderLink;
}
return (
<LinkTag
href={title.path}
key={title.name}
active={SECTION[0] === title.path}
>
{title.name}
</LinkTag>
);
})}
</HeaderLinkBox>
</HeaderLinkContainer>
And I call that component in the layout:
<ErrorShield component="layout/LayoutResponsive -> Header">
<Header
breadcrumbs={breadcrumbs}
social={social}
viewTickerAmp={viewTickerAmp}
/>
</ErrorShield>
If I break this component by changing this:
{titles.map((title) => {
into this:
{/*Added an extra t*/}
{titles.map((ttitle) => {
The ErrorShield does work as intended, it renders nothing and prints this on the console:
However if I break it like this:
{/*Added an extra t at the beginning*/}
{ttitles.map((title) => {
It does not catch the error and I get this message in the console:
I have another component named ContentCarrusel with a similar .map() in it but the ErrorShield does work in it without problems:
{/*ContentCarouselStyles, ContentCarouselTitleStyles, ContentCarouselThumbStyles are styled components */}
{/*ItemCarousel is a child component*/}
<ContentCarouselStyles>
<ContentCarouselTitleStyles variant="h3">Para Ti</ContentCarouselTitleStyles>
{items.map((item, index) => {
return (
<ContentCarouselThumbStyles key={item.key}>
<ItemCarousel {...item} isAmp={isAmp} />
</ContentCarouselThumbStyles>
);
})}
</ContentCarouselStyles>
Here it doesn't matter if I break it like this:
{iitems.map((item, index) => {
or this:
{items.map((iitem, index) => {
The ErrorShield still catches the error and works as intended.
I have a lot more of examples of inconsistent behavior if you need them, also let me know if you need more info. Thank you

Related

How React pass props from api to component?

I have a parentA and childB component
I want it click a button in parentA
then execute function to get data from api
then show on B component. that seems simple.
parent:
let profileData = {
avatar: '',
first_name: 'hey',
...
};
const handleClickProfileOpen = () => {
setIsProfileOpen(true);
getProfileData();
};
const getProfileData = async() => {
let res;
try {
res = await....;
if (res.code === 200) {
profileData = res.data.data;
...
} else {
...
}
} catch...
};
return (
<>
<UserInfo openProfilePage={ handleClickProfileOpen } />
<Profile profileData={profileData} />
</>
)
child(profile)
export default function Profile({profileData}) {
return (
<>
<p>{profileData.first_name}</p>
</>
)}
and I run it, the profileData is not re-render when called API, the last_name is always 'hey',
I tried setState in getProfileData code === 200, but cause error
Can't perform a React state update on an unmounted component.
I'm a new react programer, if you answer, I appreciate it.
Assuming you are using functional component, you can use useState from react, refer to https://reactjs.org/docs/hooks-state.html
E.g.,
function ParentComponent() {
const [profileData, setProfileData] = useState({
// default value if you needed, otherwise use `null`
avatar: '',
first_name: 'hey',
...
})
const handleClickProfileOpen = () => {
setIsProfileOpen(true);
getProfileData();
};
const getProfileData = async() => {
let res;
try {
res = await....;
if (res.code === 200) {
setProfileData(res.data.data);
...
} else {
...
}
} catch...
};
return (
<>
<UserInfo openProfilePage={ handleClickProfileOpen } />
<Profile profileData={profileData} />
</>
)
}
Above is the easiest way to handle simple react state, you can find other state management libraries when you state become more complicated.

How to use TypeScript generics with React render props

I'm trying to type a property of a React component using generics. The problem is when I try to render the component as a render prop through another component, it seems to no longer be able to "infer" the correct type - instead it defaults to unknown and I have to use "as any" to make things work.
I've put together a working example here. Note the comments and "as any"'s: https://stackblitz.com/edit/react-ts-4oyi3d?file=index.tsx
Here's the code:
type TabProps<T> = {
data?: T;
};
type TabsProps<T> = {
children: ({
childrenArr
}: {
childrenArr: React.ReactElement<TabProps<T>>[];
}) => React.ReactElement<TabProps<T>>[];
items: React.ReactElement<TabProps<T>>[];
};
type Data = { hello: string };
const Tab = <T extends unknown>({ data }: TabProps<T>) => <div>...</div>;
const Tabs = <T extends unknown>({ children, items }: TabsProps<T>) => {
const childrenArr = useMemo(() => (Array.isArray(items) ? items : [items]), [
items
]);
// ...
// In real application this is where manipulation of the elements in the childrenArr would take place
// Since this is just an example I'll just do this
const manipulatedchildrenArr = childrenArr.map(child => {
return {
...(child as any),
props: { data: { hello: "world is manipulated" } }
};
});
return (
<div>
<div>Hello</div>
<div>
{typeof children === "function"
? children({ childrenArr: manipulatedchildrenArr })
: children}
</div>
</div>
);
};
const App = () => {
const data: Data = { hello: "World" };
return (
<div className="App">
<Tabs items={[<Tab data={data} />]}>
{({ childrenArr }) =>
childrenArr.map(child => (
// Remove "as any" and it will be type unknown and result in an error
<div>{(child.props as any).data.hello}</div>
))
}
</Tabs>
</div>
);
};
As you can see the type of the data prop is lost.
Now I'm not sure if I went outside the scope of what you were looking for and If I did please let me know and I'll adjust the solution..
Update: I forgot to add code for Single tab.
import React from "react";
import ReactDOM from "react-dom";
export interface ITabProps<T> {
data?: T;
handleProcessData: (data: T) => string;
}
export function Tab<T>(props: ITabProps<T>) {
const data = props.data ? props.handleProcessData(props.data) : "None";
return <div>Hello {data}</div>;
}
export type TabElement<T> = React.ReactElement<ITabProps<T>> | React.ReactElement<ITabProps<T>>[]
export interface ITabsProps<T> {
handleManipulation: (data: T) => T;
children: TabElement<T>
}
export function Tabs<T>(props: ITabsProps<T>) {
const array = [] as TabElement<T>[];
if (Array.isArray(props.children))
props.children.forEach((child) => {
let mChild = <Tab<T> handleProcessData={child.props.handleProcessData} data={props.handleManipulation(child.props.data)} /> as TabElement<T>;
array.push(mChild)
})
else {
let mChild = <Tab<T> handleProcessData={props.children.props.handleProcessData} data={props.handleManipulation(props.children.props.data)} /> as TabElement<T>;
array.push(mChild)
}
return <div>{array.map((item) => (item))}</div>;
}
export type Data = { hello: string };
export function App() {
//B.C. it's generic you going to have to have some form of generic control functions
const handleProcessData = (data: Data) => {
//Here you have to specifiy how this specific data type is processed
return data.hello;
};
const handleManipulation = (data: Data) => {
//here you would have all your manipulation logic
return { hello: data.hello + " is manipulated" };
}
//To Make this easier to use you could nest handleProcessData inside the Tabs component
return (
<div>
<Tabs<Data> handleManipulation={handleManipulation}>
<Tab<Data> handleProcessData={handleProcessData} data={{hello: "world1"}} />
<Tab<Data> handleProcessData={handleProcessData} data={{hello: "world2"}} />
</Tabs>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));

Cannot access ___ before initialization reactjs useState useTracker Subscriptions Form state meteor

I have a form that takes its state from a react useState hook, that hooks default value I would like to come from a useTracker call, I am using pub sub in Meteor to do this. I get a error Cannot access '' before initialization I know it has something to do with the lead not being ready yet and returning undefined and the hook not being able to use that, at least I think so. But I am not sure how to solve that.
Here is my code thus far
import React, { useState } from "react";
import Dasboard from "./Dashboard";
import { Container } from "../styles/Main";
import { LeadsCollection } from "../../api/LeadsCollection";
import { LeadWalkin } from "../leads/LeadWalkin";
import { useTracker } from "meteor/react-meteor-data";
const Walkin = ({ params }) => {
const [email, setEmail] = useState(leads.email);
const handleSubmit = (e) => {
e.preventDefault();
if (!email) return;
Meteor.call("leads.update", email, function (error, result) {
console.log(result);
console.log(error);
});
setEmail("");
};
const { leads, isLoading } = useTracker(() => {
const noDataAvailable = { leads: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leads");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leads = LeadsCollection.findOne({ _id: params._id });
return { leads };
});
console.log(leads);
//console.log(params._id);
const deleteLead = ({ _id }) => {
Meteor.call("leads.remove", _id);
window.location.pathname = `/walkin`;
};
return (
<Container>
<Dasboard />
<main className="split">
<div>
<h1>Edit a lead below</h1>
</div>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<LeadWalkin
key={params._id}
lead={leads}
onDeleteClick={deleteLead}
/>
<form className="lead-form" onSubmit={handleSubmit}>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Type to edit lead"
/>
<button type="submit">Edit Lead</button>
</form>
</>
)}
</main>
</Container>
);
};
export default Walkin;
It should work if you change the order of these two hooks, but it's probably better to break this into two components so that you can wait until your subscription is ready before you try to use leads.email as default value. It's not possible to branch out ('return loading`) in between hooks, because React doesn't like it when the number of hooks it finds in a component change in-between re-renderings.
const Walkin = ({ params }) => {
const { leads, isLoading } = useTracker(() => {
const noDataAvailable = { leads: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leads");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leads = LeadsCollection.findOne({ _id: params._id });
return { leads };
});
if (isLoading || !leads) {
return <div>loading..</div>;
} else {
return <SubWalkin params=params leads=leads />;
}
};
const SubWalkin = ({ params, leads }) => {
const [email, setEmail] = useState(leads.email);
...
};

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

How to show validation message on <TagsInput> react premade component on unique value

I have an input tag component from react-tagsinput as follows:
const onTagChange = (tags) => {
const noDuplicateTags = tags.filter((v, i) => tags.indexOf(v) === i);
const duplicateEntered = tags.length !== noDuplicateTags.length;
if (duplicateEntered) {
onTagChange(tags);
console.log('duplicate');
}
onTagChange(noDuplicateTags);
};
function TagContainer({
tags,
}) {
return (
<div>
<Header>Meta:</Header>
<TagsInput value={tags} onChange={onTagChange} />
</div>
);
}
TagContainer.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
};
TagContainer.defaultProps = {
tags: [],
};
export default TagContainer;
and the implementation on the onTagChange method which is passed as a prop to the <TagContainer> component in another component.
export class Modal extends React.Component {
...
...
onTagChange = (tags) => {
this.props.onTagChange(tags);
}
...
...
render() {
return(
<TagContainer
tags={tags}
onTagChange={this.onTagChange}
/>
);
}
}
Problem: onlyUnique prop in the <TagsInput> component is set to true to avoid duplicate entries. But I need to display an error message saying "duplicate values" as soon as user enters a duplicate value. How can this be done especially on the third party component.
I think you're going to have to handle dealing with duplicates in your component because you are getting no feedback from <TagInput /> component.
At a higher level, I would do something like this
class Example extends React.Component {
constructor() {
super();
this.state = {
showDuplicateError: false
};
}
handleTagChange(tags) {
const uniqueTags = removeDuplicates(tags);
const duplicateEntered = tags.length !== uniqueTags.length;
if (duplicateEntered) {
this.showDuplicateError();
}
// add unique tags regardless, as multiple tags could've been entered
const { onTagChange } = this.props;
onTagChange(uniqueTags);
}
showDuplicateError() {
this.setState({
showDuplicateError: true
});
}
render() {
const { showDuplicateError } = this.state;
const { tags } = this.props;
return (
<React.Fragment>
{ showDuplicateError && <div>Duplicate entered</div>}
<TagsInput value={ tags } onTagChange={ this.handleTagChange } />
</React.Fragment>
);
}
}

Resources