Throw error when creating react component conditionally - javascript

I have a react component called ListChats and another called Chat.
ListChats calls the rendering of a Chat
I need that if an error occurs when rendering the Chat, ListChats knows and comes back with the list of available chats.
A possible error, its capture has not yet been implemented, would be if the user has no name, Chat captures this error and returns to ListChat.
ListChats component:
import React, { Component } from "react";
import Chat from "../Chat";
import { Button } from 'react-bootstrap';
export default class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
chat: <div />
}
this.toSala = this.toSala.bind(this);
}
toSala() {
//this is where I render a Chat component, I need that, if it returns an error, I set this.state.chat to "<div />"
this.setState({chat: <Chat/> });
}
render() {
const { chat} = this.state;
return (
<>
<Button onClick={this.toSala}>abrir chat</Button>
{chat}
</>
)
}
};
Chat component
import React,{ useState, useEffect } from 'react'
const Chat = (props) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [users, setUsers] = useState([]);
useEffect(() => {
//depending on the value of a variable of mine I have the need or not to throw an error when rendering this component
}, []);
return (
<div>
my chat
</div>
);
};
export default Chat;
In short, I need to throw an error in the Chat component and catch it in ListChats component

you can do this in two ways:
first you can pass props from the parent to the child and communicate between the two components by this.set your variable in the parent component and pass that via props to the child component.in this case your child component is responsible to show whatever comes from the parent.
the second one is using a global state management like Redux,ContextApi or mobX to catch error in the child and save it to an state and then use that state in the parent component or wherever you want to use.
depend on the size of your project you can use either way.

Thanks to the responses and comments I managed to elaborate the following solution:
1- I created a function in ListChat that will receive the error message (msg) by parameter:
setError = (msg) => {
this.setState({error:msg});
}
2- I passed the function as props in rendering the chat:
this.setState({chat: <Chat setError={this.setError}/> });
3- When I need to now pass an error by the Chat for ListChat,
I call in the Chat component:
props.setError("Name is null");

Related

Problem when pass data in ReactJS with props and state

I have the problem when I try to pass the props through the function component .In parent component I have a state of currentRow with return an array with object inside, and I pass it to child component. It return a new object with an array inside it. What can I do to avoid it and receive exact my currentRow array.
there is example of the way I do it
Parent component
import React, { useState } from "react";
import ToolBar from "./Toolbar";
function Manage() {
const [currentRow, setCurrentRow] = useState();
console.log("from manage", currentRow);
return (
<div>
<ToolBar currentRow={currentRow} />
</div>
);
}
export default Manage;
Child Componet
import React from 'react'
function ToolBar(currentRow) {
console.log("from toolbar", currentRow);
return(
<div></div>
);
}
export default ToolBar
And this is my Log
enter image description here
Try accessing it like below:
import React from 'react'
function ToolBar({currentRow}) {
console.log("from toolbar", currentRow);
return(
<div></div>
);
}
export default ToolBar
A React component's props is always an object. The reason for this is that otherwise it would be impossible to access the properties of a component which received multiple props.
example:
<SomeComponent prop1={prop1} prop2={prop2} />
---
const SomeComponent = (props) => {
console.log(props.prop1);
console.log(props.prop2);
}
So in order to resolve your issue, you could destructure the props object in your ToolBar component like this:
const ToolBar = ({ currentRows }) => {
...
}
Just keep in mind that a component will always receive its props as an object. There is no way to change that as of right now.

Forcefully update the component on updated value inside the render method react native

I am trying to forcefully rerender the complete component on value change inside the render function.
Below is a sample code to explain my problem. Profile component I am calling after the login and it's called and two functions inside this getting called and the value is set in the state. Now I am on another screen where I am updating mssidnNumber inside the context and that change I can see in the Profile render() function val. let val = this.context.mssidnNumber; and this val value will change on condition so on this basis I am trying to rerender the complete Profile component again so all values will update. How can I do this please help? Is that possible?
import React, { Component } from 'react';
import { View} from 'react-native';
class Profile extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
this.Profilefunction();
}
Profilefunction = () =>{
this.setState({})
await this.profile2fucntion();
}
profile2fucntion =() => {
}
render() {
let val = this.context.mssidnNumber;
return (
<View>
</View>
);
}
}
export default Profile;

Make conditional react component rerender after boolean changes

I have a react component that conditionally renders JSX according to the user's login state.
<div>
{ boolIsLoggedIn ?
<SomeLoggedInComponent /> : <SomeNotLoggedInComponent /> }
</div>
I think I need to use React.useState() and/or React.useEffect() but I'm not sure exactly how to implement it.
I've tried this:
const [boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn())
useEffect(() => {
const checkLogIn = () => {
setBoolIsLoggedIn(isLoggedIn())
}
checkLogIn()
})
Where isLoggedIn() checks whether the user is logged in or not and returns a boolean.
Currently, I can log out and log in and the isLoggedIn() function works, but the component I want to conditionally re-render doesn't do so until I refresh the page.
So I added [isLoggedin()] as the second parameter to useEffect() and now it almost works. When I log in, the boolIsLoggedIn value changes to true and the component re-renders. However, when I log back out boolIsLoggedIn doesn't change until I refresh the page.
Here is my code for the isLoggedIn() function, which is coded in a seperate file and imported:
let boolIsLoggedIn = false
export const setUser = user => {
//handle login
boolIsLoggedIn = true
export const logout => {
//handle logout
boolIsLoggedIn = false
}
export const isLoggedIn = () =>
return boolIsLoggedIn
}
try this
import React, { Component } from "react";
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: isLoggedIn()
};
}
render() {
let { isLoggedIn } = this.state;
return (
<div className="App">
{(function() {
if (isLoggedIn) {
return <div>Login</div>;
} else {
return <div>with out Login</div>;
}
})()}
</div>
);
}
}
export default App;
You are missing the open [ on defining the state.
I would set the defailt value on false so you won't execute the function twice.
The use effect usually needs a dependencies array as the second parameter but as you only want to run it once (after the component is mounted) just place an empty array as the second parameter.
Another thing is that if the isLoggedIn function is asyncronous you have to wait for it by using await and setting the parent function as async. This would definetly be the problem you have if the function is asyncronous.
Change This:
const boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn())
useEffect(() => {
const checkLogIn = () => {
setBoolIsLoggedIn(isLoggedIn())
}
checkLogIn()
})
To this:
const [boolIsLoggedIn, setBoolIsLoggedIn] = useState(isLoggedIn());
useEffect(() => {
(() => {
setBoolIsLoggedIn(isLoggedIn());
})();
}, [isLoggedIn()]);
Good Luck
You need to be able to share react state, and not just values in order to trigger a react re-render. In your example, you would need a call to setBoolIsLoggedIn() whenever value of isLoggedIn() changes in order to change the state and trigger a re-render of the component.
Another way share state between components is through React Context. You would first need to create a Context Provider like this:
const UserContext = React.createContext();
export function UserProvider({ children }) {
const [ user, setUser ] = useState({ loggedIn: false });
return (
<UserContext.Provider value={[ user, setUser ]}>
{ children }
</UserContext.Provider>
)
}
In this case, the UserProvider is the one maintaining the shared state withuser={ loggedIn }. You would then need to wrap your React App component with this provider in order for components to be able to share state.
To set the shared user state you can use the hook useContext(UserContext) and change state through setUser provided by the hook. In the example below, we have a login button component that sets shared user state:
function LoginButton() {
const [user, setUser] = useContext(UserContext);
/** flip login state when button is clicked */
function logIn() {
setUser(state => ({ ...state, loggedIn: !state.loggedIn }) );
}
return (
<button onClick={logIn}>{ user.loggedIn ? "Log Out" : "Log In"}</button>
);
}
And finally, to be able to use the shared user state and trigger a re-render whenever the user state changes, you would again use the hook useContext(UserContext) in a different component to be able to get the state like this:
export default function App() {
const [user,] = useContext(UserContext);
return (
<div className="App">
<h1>Conditional React component</h1>
<h2>Click on the button to login/logout</h2>
<LoginButton></LoginButton>
<div>
{ user.loggedIn ? <LoggedInComponent/> : <LoggedOutComponent/>}
</div>
</div>
);
}
I've provided an example here to show how this all works together.

How to React re-render list view from database ondelete

I have 2 files here one is the parent component sending data from the database, the other is the rendered info with a delete button.
Here is my latest attempt to delete the data from the data base (works) and re-render the list (doesn’t re-render) current error is that email is not a valid variable.
import React, { Component } from 'react';
import EmailItem from './EmailItem'
class Admin extends Component {
  constructor(props) {
    super(props);
    this.state = {
    allEmails: []
  }
  this.hideEmail = this.hideEmail.bind(this, email)
}
  componentDidMount() {
    fetch("/api/emails/")
      .then(res => res.json())
      .then(parsedJSON => parsedJSON.map(emails => ({
        email: `${emails.email}`,
        id: `${emails.id}`
      }))).then(emails => this.setState({allEmails: emails}))
  }
  hideEmail = () => this.setState({allEmails:emails})
  render() {
    return (
      <div>
        <h2>Admin Page</h2>
        <div>
          {this.state.allEmails.map((email) => {
            return <EmailItem
              key={email.id}
              email={email.email}
              onDelete = {() => this.onDelete(this, email.email)}/>
          })}
        </div>
      </div>
    );
  }
}
export default Admin;
import axios from 'axios'
import React, { Component } from 'react';
const EmailItem = ({email, onDelete}) => (
  <div>
    <h3>{email}</h3>
    <button
      onClick={(e) => {
      e.preventDefault()
      axios.delete("/api/emails/delete/", {
        data: {email: email}
      })
      onDelete()
      }
    }
    >
      Remove
  </button>
  </div>
)
export default (EmailItem)
It looks like your Admin component doesn't know of the changes that happened on the backend. You fire off a delete, which works, but that component only gets the "allEmails" on mount. With the component already mounted, you aren't calling the fetch again to get the latest email list, so it never re-renders.
There are a few ways to handle this, but basically you'll want to fetch the new list after the axios delete completes successfully.
An option would be to create a new function in your Admin component that will fetch the email list. You can then call that in the componentDidMount function and then also pass it to the child EmailItem component, which it can call when the delete call has been successful.
You are passing an onDelete() function, to that component, but I don't see that defined in your code. If that's the intention of that function, make sure it's refetching the latest list of emails and setting state to force the refresh.

Uncaught RangeError Maximum call stack size exceeded in React App

I'm learning React and for training, I want to create a basic Todo app. For the first step, I want to create a component called AddTodo that renders an input field and a button and every time I enter something in the input field and press the button, I want to pass the value of the input field to another component called TodoList and append it to the list.
The problem is when I launch the app, the AddTodo component renders successfully but when I enter something and press the button, the app stops responding for 2 seconds and after that, I get this: Uncaught RangeError: Maximum call stack size exceeded and nothing happens.
My app source code: Main.jsx
import React, {Component} from 'react';
import TodoList from 'TodoList';
import AddTodo from 'AddTodo';
class Main extends Component {
constructor(props) {
super(props);
this.setNewTodo = this.setNewTodo.bind(this);
this.state = {
newTodo: ''
};
}
setNewTodo(todo) {
this.setState({
newTodo: todo
});
}
render() {
var {newTodo} = this.state;
return (
<div>
<TodoList addToList={newTodo} />
<AddTodo setTodo={this.setNewTodo}/>
</div>
);
}
}
export default Main;
AddTodo.jsx
import React, {Component} from 'react';
class AddTodo extends Component {
constructor(props) {
super(props);
this.handleNewTodo = this.handleNewTodo.bind(this);
}
handleNewTodo() {
var todo = this.refs.todo.value;
this.refs.todo.value = '';
if (todo) {
this.props.setTodo(todo);
}
}
render() {
return (
<div>
<input type="text" ref="todo" />
<button onClick={this.handleNewTodo}>Add to Todo List</button>
</div>
);
}
}
AddTodo.propTypes = {
setTodo: React.PropTypes.func.isRequired
};
export default AddTodo;
TodoList.jsx
import React, {Component} from 'react';
class TodoList extends Component {
constructor(props) {
super(props);
this.renderItems = this.renderItems.bind(this);
this.state = {
todos: []
};
}
componentDidUpdate() {
var newTodo = this.props.addToList;
var todos = this.state.todos;
todos = todos.concat(newTodo);
this.setState({
todos: todos
});
}
renderItems() {
var todos = this.state.todos;
todos.map((item) => {
<h4>{item}</h4>
});
}
render() {
return (
<div>
{this.renderItems()}
</div>
);
}
}
export default TodoList;
First time componentDidUpdate is called (which happens after first change in its props/state, which in your case happens after adding first todo) it adds this.props.addToList to this.state.todo and updates state. Updating state will run componentDidUpdate again and it adds the value of this.props.addToList to 'this.state.todo` again and it goes infinitely.
You can fix it with some dirty hacks but your approach is a bad approach overall. Right thing to do is to keep todos in parent component (Main), append the new todo to it in setNewTodo (you may probably rename it to addTodo) and pass the todos list from Main state to TodoList: <TodoList todos={this.state.todos}/> for example.
The basic idea of react is whenever you call setState function, react component get updated which causes the function componentDidUpdate to be called again when the component is updated.
Now problem here is you are calling setState function inside componentDidUpdate which causes the component to update again and this chain goes on forever. And every time componentDidUpdate is called it concat a value to the todo. So a time come when the memory gets full and it throws an error. You should not call setState function inside functions like componentWillUpdate,componentDidUpdate etc.
One solution can be to use componentWillReceiveProps instead of componentDidUpdate function like this:
componentDidUpdate(nextProps) {
var newTodo = nextProps.addToList;
this.setState(prevState => ({
todos: prevState.todos.concat(newTodo)
}));
}

Categories

Resources