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.
Related
I am using the useIsDirty hook in two components, CustomCodeEditor and EditorFooter, to track whether the code in the Editor has been modified. The hook returns an isDirty state and a setIsDirty function to update it. When I call setIsDirty(true) in the CustomCodeEditor component, the state is updated, but when I call setIsDirty(false) in the EditorFooter component, it doesn't seem to update the isDirty state. I believe this is because the EditorFooter component does not have access to the updated state. Anyone, please help me with this.
useIsDirty:
import { useEffect, useState } from "react"
const useIsDirty = () => {
const [isDirty, setIsDirty] = useState(false)
useEffect(() => {
const handleBeforeUnload = (event) => {
if (isDirty) {
event.preventDefault()
event.returnValue = ""
alert("You have unsaved changes, are you sure you want to leave?")
}
}
console.log("Diryt:", isDirty)
window.addEventListener("beforeunload", handleBeforeUnload)
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload)
}
}, [isDirty])
return { isDirty, setIsDirty }
}
export default useIsDirty
CustomCodeEditor
import Editor from "#monaco-editor/react"
import useIsDirty from "../../hooks/useIsDirty"
const CustomCodeEditor = () => {
const { isDirty, setIsDirty } = useIsDirty()
console.log("isDirty:", isDirty)
return (
<div className="bg-[#1e1e1e] h-full">
<Editor
onChange={(value) => {
updateCode(value || "")
setIsDirty(true) // updating state
}}
/>
</div>
)
}
export default CustomCodeEditor
EditorFooter
import Button from "../reusable/Button"
const EditorFooter = () => {
const { setIsDirty } = useIsDirty()
const handleSave = async () => {
setIsDirty(false)
}
return (
<div>
<Button
onClick={handleSave}
>
Save
</Button>
<Button
onClick={handleSave}
>
Submit
</Button>
</div>
)
}
export default EditorFooter
Hooks are not singleton instances.. when you use useIsDirty somewhere.. it always create new instance, with unrelated states to other ones. If you want to share this state you need to use Context
const IsDirtyContext = createContext(undefined);
const IsDirtyProvider = ({ children }): ReactElement => {
const [isDirty, setIsDirty] = useState(false)
return <IsDirtyContext.Provider value={{isDirty, setIsDirty}}>{children}</IsDirtyContext.Provider>;
};
and then you should wrap your commponent tree where you wanna access it with IsDirtyProvider
after that, you can even create your custom hook that will just return that context:
const useIsDirty = () => {
return useContext(IsDirtyContext)
}
Looking at your question, it looks like you are trying to use the same state in both components. However, the state doesn't work like that. A new instance is created whenever you make a call to useIsDirty from a different component.
If you want to use the state value across two components. You can do that using one of the following ways.
1 - Use a parent and child hierarchy.
Steps
Create a parent component and wrap the two components inside the parent component.
Manage the state in the parent component and pass it using props to the child component.
Create a function in child components that will execute a function from the parent component. The parent component function will hold the code to update the state based on whatever value you receive from the child component.
Now you should be able to share your state between both components.
2 - Use the context api.
If you are not familiar with what context api is, below is a brief explanation.
Context api helps you share data between components, without the need of passing them as a prop to each and every component.
You can use createContext and useContext hooks from context api.
createContext is used to create a new context provider.
useContext hook is used to manage the state globally.
You can get the value from context using this function.
Whenever the state is updated the value will be reflected globally.
Note - Any component that needs to use the value inside the useContext should be wrapped inside the useContext provider component.
Steps to create a context provider.
To create a context you just need to use the react hook createContext
Create a context using below code
const isDirtyContext = createContext();
Wrap your components in the context provider
import {IsDirtyContext} from './path/filename'
<IsDirtyContext.Provider value={[isDirty, setIsDirty]}>{children}</IsDirtyContext.Provider>
If your context is in a separate file, then you can import it into any child component using the import statement.
import {IsDirtyContext} from './path/filename'
Use the context
const [isDirty] = useContext(IsDirtyContext);
Now the isDirty state value is available globally in all components.
Hope this information helps you. Please upvote if this helps you understand and solve the problem.
I have a rails (7.0.2) application and just installed React. I'm very new to react and can't seem to understand why it looks like my component is loading multiple times, the first time with an empty value for props and the second time with the correct values for props.
App.js:
import "./App.css";
import axios from "axios";
import Customers from "./components/customers";
import { useEffect, useState } from "react";
const API_URL = "http://localhost:3000/internal_api/v1/customers";
function getAPIData() {
return axios.get(API_URL).then((response) => response.data);
}
function App() {
const [customers, setCustomers] = useState([]);
useEffect(() => {
let mounted = true;
getAPIData().then((items) => {
if (mounted) {
setCustomers(items);
}
});
return () => (mounted = false);
}, []);
console.log('LOADED App.js');
return (
<div className="App">
<h1>Hello</h1>
<Customers customers={customers} />
</div>
);
}
export default App;
and customers.js:
import React from "react";
function Customers(props) {
console.log('LOADED customers.js');
return (
<div>
<h1>These customers are from the API</h1>
{props.customers.data.map((customer) => {
return (
<div key={customer.id}>
<h2>{customer.id}</h2>
</div>
);
})}
</div>
);
}
export default Customers;
When I remove this part of the code and reload the page, my props come through correctly when looking in console. Then, when I put the code back and save (without reloading), it displays correctly.
{props.customers.data.map((customer) => {
return (
<div key={customer.id}>
<h2>{customer.id}</h2>
</div>
);
However, as soon as I reload again, I get the same following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
It seems as though the first time everything renders, props is empty. Then the second time, it is full with the data. I checked my rails app and it only hits the API once. What am I doing wrong?
More log outputs:
React component rendering multiple times?
React will render fast before completing the request in use Effect
so in first render customers array will be empty
when request is fulfilled, you are changing state, So react will re-render the component
Only component that uses state reloads when the state is changed this is required else UI will not update
failing when reloading the page? | Failed on Initial Load
Since in Initial render customers will have no data customers.data will be undefined so it will not have map
to bypass this error use props.customers?.data && props.customers.data?.map() addding question mark means expression will be evaluated if not undefined
Source - Optional_chaining
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");
I want to make a PUT request to my server, but in order to do so I need an identifier for the specific object I need to update. And that is my problem, I don't know how to get the components id so I can fulfill my PUT request. Here's the code at the moment:
import axios from 'axios'
import settings from '../../../../settings'
axios.defaults.baseURL = settings.hostname
export const updateSettings = function(id, item) {
return dispatch => {
axios
.put(`${settings.hostname}/locks/${id}`, item)
.then(res => res.data)
.catch(err => console.log(err))
}
}
When console.log item I can see all the new thing I've typed in my input fields (the things I want to change), but I'm getting this also:
And sometimes 404. So my question is how can I get the id so I can make this put request. Thank you.
This is where I call updateSettings:
import React, { Component } from 'react'
import { updateSettings } from './redux/actions/updateSettingsAction'
import DoorSettingsForm from './components/doorsSettingsForm'
import { connect } from 'react-redux'
class DoorSettingsContainer extends Component {
submit(values) {
this.props.updateSettings(values)
}
render() {
return (
<div>
<DoorSettingsForm
onSubmit={this.submit.bind(this)}
item={this.props.location.state.item}
/>
</div>
)
}
}
function mapStateToProps(state) {
return { data: state.data }
}
export default connect(mapStateToProps, { updateSettings })(
DoorSettingsContainer
)
You've missed the id on the updateSettings() function
look at this line: export const updateSettings = function(id, item) {};
and then the line where you call it:
submit(values) {
this.props.updateSettings(values)
}
your item is your id here, and the item is nowhere to be found, i think this is where you get most of your problems atleast for now from.
What you are currently doing is passing the event object received by the onSubmit handler to the updateSettings method.
Mind that in your event handler you have access to both state and props:
submit() {
// this comes from the redux binding
const { data } = this.props
// this is local state, not sure what else of interest is in there
const item = this.state.location.item
// Just a guess, inspect your data to see what's appropriate here.
this.props.updateSettings(data.id, item)
}
Inspect your data, you probably need to access data.id or item.id to get the proper id updateSettings needs.
Also pay attention that if you are dispatching async actions this way, then depending on what middleware you are using, you will probably have to call dispatch when the async data comes in (e.g. where you can access res.data).
I am using this starter kit https://github.com/davezuko/react-redux-starter-kit and am following some tutorials at the same time, but the style of this codebase is slightly more advanced/different than the tutorials I am watching. I am just a little lost with one thing.
HomeView.js - This is just a view that is used in the router, there are higher level components like Root elsewhere I don't think I need to share that, if I do let me know, but it's all in the github link provided above.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { searchListing } from '../../redux/modules/search'
export class HomeView extends React.Component {
componentDidMount () {
console.log(this.props)
}
render () {
return (
<main onClick={this.props.searchListing}>
<NavBar search={this.props.search} />
<Hero/>
<FilterBar/>
<Listings/>
<Footer/>
</main>
)
}
}
I am using connect() and passing in mapStateToProps to tell the HomeView component about the state. I am also telling it about my searchListing function that is an action which returns a type and payload.
export const searchListing = (value) => {
console.log(value)
return {
type: SEARCH_LISTINGS,
payload: value
}
}
Obviously when I call the method inside the connect() I am passing in an empty object searchListing: () => searchListing({})
const mapStateToProps = (state) => {
return {
search: { city: state.search }
}
}
export default connect((mapStateToProps), { searchListing: () => searchListing({}) })(HomeView)
This is where I am stuck, I am trying to take the pattern from the repo, which they just pass 1, I think anytime that action is created the logic is just add 1 there is no new information passed from the component.
What I am trying to accomplish is input search into a form and from the component pass the users query into the action payload, then the reducer, then update the new state with the query. I hope that is the right idea.
So if in the example the value of 1 is hardcoded and passed into the connect() method, how can I make it so that I am updating value from the component dynamically? Is this even the right thinking?
You almost got it right. Just modify the connect function to pass the action you want to call directly:
const mapStateToProps = (state) => ({
search: { city: state.search }
});
export default connect((mapStateToProps), {
searchListing
})(HomeView);
Then you may use this action with this.props.searchListing(stringToSearch) where stringToSearch is a variable containing the input value.
Notice : You don't seem to currently retrieve the user query. You may need to retrieve it first and then pass it to the searchListing action.
If you need to call a function method, use dispatch.
import { searchListing } from '../../redux/modules/search';
const mapDispatchToProps = (dispatch) => ({
searchListing: () => {
dispatch(searchListing());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
Then, you have made the function a prop, use it with searchListing.