How to console.log the - reactjs

I have a simple React component and inside of it I am fetching data from a remote API, and I want to console.log it in useEffect. I am trying to do it but nothing doesn't get logged into the console, why? What am I missing here? Here is the component:
import React, { useState, useEffect } from 'react';
import { useLocalization } from '#progress/kendo-react-intl';
import { Card, CardHeader, Avatar, CardTitle, CardSubtitle } from '#progress/kendo-react-layout';
import { guid } from '#progress/kendo-react-common';
import { Scheduler } from './../components/Scheduler';
import { employees } from './../resources/employees';
import { images } from './../resources/images';
import { orders, ordersModelFields } from './../resources/orders';
import { teams } from './../resources/teams';
// const orderEmployees = employees.filter(employee => employee.jobTitle === 'Sales Representative');
// const initialFilterState = { };
// orderEmployees.forEach(employee => {
// if(employee.fullName === 'Wait Peperell') {
// initialFilterState[employee.id] = false;
// } else {
// initialFilterState[employee.id] = true;
// }
// });
const Planning = () => {
const localizationService = useLocalization();
const [filterState, setFilterState] = React.useState(initialFilterState);
const [data, setData] = React.useState(orders);
const [fetchedData, setFetchedData] = React.useState(null);
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then(response => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then(data => setFetchedData(data))
console.log(fetchedData)
},[])
// console.log(fetchedData)
const onDataChange = React.useCallback(
({ created, updated, deleted }) => {
setData(old => old
// Filter the deleted items
.filter((item) => deleted.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) === undefined)
// Find and replace the updated items
.map((item) => updated.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) || item)
// Add the newly created items and assign an `id`.
.concat(created.map((item) => Object.assign({}, item, { [ordersModelFields.id]: guid() }))))
},
[]
);
const onEmployeeClick = React.useCallback(
(employeeId) => {
setFilterState({
...filterState,
[employeeId]: !filterState[employeeId]
});
},
[filterState, setFilterState]
);
return (
<div id="Planning" className="planning-page main-content">
<div className="card-container grid">
<h3 className="card-title">{localizationService.toLanguageString('custom.teamCalendar')}</h3>
{
orderEmployees.map(employee => {
return (
<div
key={employee.id}
onClick={() => onEmployeeClick(employee.id)}
style={!filterState[employee.id] ? {opacity: .5} : {}}
>
<Card style={{ borderWidth: 0, cursor: 'pointer'}}>
<CardHeader className="k-hbox" >
<Avatar type='image' shape='circle' size={'large'} style={{
borderWidth: 2,
borderColor: teams.find(({teamID}) => teamID === employee.teamId).teamColor,
}}>
<div className="k-avatar-image" style={{
backgroundImage: images[employee.imgId + employee.gender],
backgroundSize: 'cover',
backgroundPosition: 'center center',
}}
/>
</Avatar>
<div>
<CardTitle style={{color: teams.find(({teamID}) => teamID === employee.teamId).teamColor}}>{employee.fullName}</CardTitle>
<CardSubtitle>{employee.jobTitle}</CardSubtitle>
</div>
</CardHeader>
</Card>
</div>
);
})
}
<div className="card-component" >
<Scheduler
data={data.filter(event => filterState[event.employeeID])}
onDataChange={onDataChange}
modelFields={ordersModelFields}
resources={[
{
name: 'Teams',
data: teams,
field: 'teamID',
valueField: 'teamID',
textField: 'teamName',
colorField: 'teamColor'
}
]}
/>
</div>
</div>
</div>
);
}
export default Planning;
I also tried to place the console.log outside of useEffect but still, nothing gets console.logged.

You need to look how useEffect work, setFetchedData is async.
Create another useEffect only for console.log.
useEffect(() => {
console.log(fetchedData);
},[fetchedData]); // Update at the first render + when fetchedData state change.

You can do it like this
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then((response) => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then((data) => {
setFetchedData(data);
console.log(data);
});
}, []);
or juste create another useEffect that listens to fetchedData change, like this
useEffect(() => {
console.log(fetchedData);
}, [fetchedData]);

Related

React: How update children component hidden to show?

I have a problem that children component does not update hidden status when project is selected, it should then display all the the tasks included in selected projects. How ever when getTasks is done and it updates hidden state to false and it passes state to children component props but children component never reintialize select component and remains hidden. What I need to change to make my selectbox class RtSelect to display hidden state changes?
My master component:
import React, { useState, useEffect, useRef } from 'react';
import RtSelect from './RtSelect';
import api, { route } from "#forge/api";
function Projects() {
const projRef = useRef();
const taskRef = useRef();
const [projects, setProjects] = useState(undefined)
const [tasks, setTasks] = useState(undefined)
const [projectid, setProjectid] = useState(undefined)
const [taskid, setTaskid] = useState(undefined)
const [hidden, setHidden] = useState(true)
//haetaan atlasiansita projectit array
useEffect(() => {
let loadedProject = true;
// declare the async data fetching function
const fetchProjects = async () => {
// get the data from the api
const response = await api.asUser().requestJira(route`/rest/api/3/project`, {
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
//Mapataa hausta tarvittavat tiedot
const result = data.map(function (item) {
console.log('test');
return [
{
label: item.name,
value: item.id,
avatar: item.avatarUrls['16x16']
}
]
})
// set state with the result if `isSubscribed` is true
if (loadedProject) {
setProjects(result);
}
}
//asetetaan state selectbox muutokselle
// call the function
fetchProjects()
// make sure to catch any error
.catch(console.error);;
// cancel any future `setData`
return () => loadedProject = false;
}, [param])
const getTasks = async (p) => {
// get the data from the api
const response = await api.asUser().requestJira(route`/rest/api/3/issuetype/project?projectId={p}`, {
headers: {
'Accept': 'application/json'
}
});
const data = await response.json();
//Mapataa hausta tarvittavat tiedot
const result = data.map(function (item) {
console.log('test');
return [
{
value: item.id,
label: item.description,
avatar: item.iconUrl
}
]
})
setTasks(result)
setHidden(false)
}
useEffect(() => {
projRef.current.addEventListener("onChange", (e) => {
setProjectid(e.target.value)
console.log("Project select boxin arvo on: " + e.target.value);
getTasks(projectid)
});
});
useEffect(() => {
taskRef.current.addEventListener("onChange", (e) => {
setTaskid(e.target.value)
console.log("Select task boxin arvo on: " + e.target.value);
});
});
return (
<div>
<div className='projects'>
<RtSelect info="Choose project:" options={projects} hidden={false} ref={projRef} />
</div>
<div className='tasks'>
<RtSelect info="Choose Task:" options={tasks} hidden={hidden} ref={taskRef} />
</div>
</div>
);
}
export default Projects
Here is my RtSelect class code:
import React from "react";
import Select from "react-select";
class RtSelect extends React.Component {
state = {
info: this.props.info,
options: this.props.options,
hidden: this.props.hidden,
menuIsOpen: '',
menuWidth: "",
IsCalculatingWidth: ''
};
constructor(props) {
super(props);
this.selectRef = props.ref
this.onMenuOpen = this.onMenuOpen.bind(this);
this.setData = this.setData.bind(this);
}
componentDidMount() {
if (!this.state.menuWidth && !this.state.isCalculatingWidth) {
setTimeout(() => {
this.setState({IsCalculatingWidth: true});
// setIsOpen doesn't trigger onOpenMenu, so calling internal method
this.selectRef.current.select.openMenu();
this.setState({menuIsOpen: true});
}, 1);
}
}
onMenuOpen() {
if (!this.state.menuWidth && this.state.IsCalculatingWidth) {
setTimeout(() => {
const width = this.selectRef.current.select.menuListRef.getBoundingClientRect()
.width;
this.setState({menuWidth: width});
this.setState({IsCalculatingWidth: false});
// setting isMenuOpen to undefined and closing menu
this.selectRef.current.select.onMenuClose();
this.setState({menuIsOpen: undefined});
}, 1);
}
}
styles = {
menu: (css) => ({
...css,
width: "auto",
...(this.state.IsCalculatingWidth && { height: 0, visibility: "hidden" })
}),
control: (css) => ({ ...css, display: "inline-flex " }),
valueContainer: (css) => ({
...css,
...(this.state.menuWidth && { width: this.state.menuWidth })
})
};
setData (props) {
if (props.info) {
this.setState({
info: props.info
})
}
if (props.options) {
this.setState({
options: props.options
})
}
if (props.hidden) {
this.setState({
hidden: props.hidden
})
}
}
render () {
return (
<div style={{ display: "flex" }}>
<div style={{ margin: "8px" }}>{this.state.info}</div>
<div style={{minWidth: "200px"}}>
<Select
ref={this.selectRef}
onMenuOpen={this.onMenuOpen}
options={this.state.options}
menuIsOpen={this.state.menuIsOpen}
styles={this.styles}
isDisabled={this.state.hidden}
formatOptionLabel={(options) => (
<div className="select-option" style={{ display: "flex", menuWidth: "200px"}}>
<div style={{ display: "inline", verticalAlign: "center" }}>
<img src={options.avatar} width="30px" alt="Avatar" />
</div>
<div style={{ display: "inline", marginLeft: "10px" }}>
<span>{options.label}</span>
</div>
</div>
)}
/>
</div>
</div>
);
}
}
export default RtSelect;
Ok I found from other examples that I can use the ref to acces child method so here is they way to update component:
useEffect(() => {
projRef.current.addEventListener("onChange", (e) => {
setProjectid(e.target.value)
console.log("Project select boxin arvo on: " + e.target.value);
getTasks(projectid)
//Using RtSelect taskRef to locate children component method to update component
taskRef.current.setData({hidden: false})
});
});

PayPal Checkout not showing correct total amount in react app

Good morning. I am trying to integrate PayPal check out in my React app. Everything works fine except that the Paypal Checkout amount is not showing the same as the total cart amount. Checkout amount is fixed at $0.01 and not changing as cart amount changes. Please what could i be doing wrong ??? This is the code
#cart.js code
import React, { useContext, useState, useEffect } from "react";
import { GlobalState } from "../../../GlobalState";
import axios from "axios";
import PayPalButton from "./PaypalButton";
function Cart() {
const state = useContext(GlobalState);
const [cart, setCart] = state.userAPI.cart;
const [token] = state.token;
const [total, setTotal] = useState(0);
useEffect(() => {
const getTotal = () => {
const total = cart.reduce((prev, item) => {
return prev + item.price * item.quantity;
}, 0);
setTotal(total);
};
getTotal();
}, [cart]);
const addToCart = async () => {
await axios.patch(
"/user/addcart",
{ cart },
{
headers: { Authorization: token },
}
);
};
const increment = (id) => {
cart.forEach((item) => {
if (item._id === id) {
item.quantity += 1;
}
});
setCart([...cart]);
addToCart();
};
const decrement = (id) => {
cart.forEach((item) => {
if (item._id === id) {
item.quantity === 1 ? (item.quantity = 1) : (item.quantity -= 1);
}
});
setCart([...cart]);
addToCart();
};
const removeProduct = (id) => {
if (window.confirm("Do you want to delete this product?")) {
cart.forEach((item, index) => {
if (item._id === id) {
cart.splice(index, 1);
}
});
setCart([...cart]);
addToCart();
}
};
const tranSuccess = async (payment) => {
console.log(payment);
};
if (cart.length === 0)
return (
<h2 style={{ textAlign: "center", fontSize: "5rem" }}>Cart Empty</h2>
);
return (
<div>
{cart.map((product) => (
<div className="detail cart" key={product._id}>
<img src={product.images.url} alt="" />
<div className="box-detail">
<h2>{product.title}</h2>
<h3>${product.price * product.quantity}</h3>
<p>{product.description}</p>
<p>{product.content}</p>
<div className="amount">
<button onClick={() => decrement(product._id)}> - </button>
<span>{product.quantity}</span>
<button onClick={() => increment(product._id)}> + </button>
</div>
<div className="delete" onClick={() => removeProduct(product._id)}>
X
</div>
</div>
</div>
))}
<div className="total">
<h3>Total: $ {total}</h3>
<PayPalButton total={total} tranSuccess={tranSuccess} />
</div>
</div>
);
}
export default Cart;
PayPal button code
import React from "react";
import { PayPalScriptProvider, PayPalButtons } from "#paypal/react-paypal-js";
export default class PayPalButton extends React.Component {
render() {
const onSuccess = (payment) => {
console.log("The payment was succeeded!", payment);
this.props.tranSuccess(payment);
};
const onCancel = (data) => {
console.log("The payment was cancelled!", data);
};
const onError = (err) => {
console.log("Error!", err);
};
let env = "sandbox"; // you can set here to 'production' for production
let total = this.props.total;
let currency = "USD";
const client = {
sandbox:
"ARhnfWu_QrcGQa-PdvaY1RVriEmqGiSkfEWf-plauZQpQN_gyxaLjH9RXOhdQw7fxxxxxxxxxxxx",
production: "YOUR-PRODUCTION-APP-ID",
};
let style = {
size: "small",
color: "blue",
shape: "rect",
label: "checkout",
tagline: false,
};
return (
<PayPalScriptProvider
options={{
"client-id": "ARhnfWu_QrcGQa-PdvaY1RVriEmqGiSkfEWf-plauZQpQN_gyxaLjH9RXOhdQw7fxxxxxxxxxxxx",
}}
>
<PayPalButtons
env={env}
client={client}
commit={true}
total={total}
currency={currency}
onError={onError}
onSuccess={onSuccess}
onCancel={onCancel}
style={style}
/>
</PayPalScriptProvider>
);
}
}
I don't see a createOrder function anywhere. You need one, and it needs to invoke actions.order.create() with a JSON object that uses your total to set the amount.
See examples in the react-paypal-js storybook.

How to make data persist on refresh React JS?

I have a code where I mount a table with some firebase data but for some reason the values disappear and I been struggling for the next 2 weeks trying to solve this issue I haven't found a solution to this and I have asked twice already and I have try everything so far but it keeps disappearing.
Important Update
I just want to clarify the following apparently I was wrong the issue wasn't because it was a nested collection as someone mentioned in another question. The issue is because my "user" is getting lost in the process when I refresh.
I bring the user from the login to the app like this:
<Estudiantes user={user} />
and then I receive it as a props
function ListadoPedidos({user})
but is getting lost and because is getting lost when I try to use my firebase as:
estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
since the user is "lost" then the uid will be null. Since is null it will never reach the collection and the docs.
I have a simple solution for you. Simply raise the parsing of localStorage up one level, passing the preloadedState into your component as a prop, and then using that to initialize your state variable.
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
Then initialize state with the prop
const initialState = props.preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
And finally, update the useEffect hook to persist state any time it changes.
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
Full Code
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { useHistory } from 'react-router-dom';
import './ListadoEstudiantes.css';
import {
DataGrid,
GridToolbarContainer,
GridToolbarFilterButton,
GridToolbarDensitySelector,
} from '#mui/x-data-grid';
import { Button, Container } from '#material-ui/core';
import { IconButton } from '#mui/material';
import PersonAddIcon from '#mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '#mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined';
import { Box } from '#mui/system';
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
const Listado = ({ user, preloadedState }) => {
const history = useHistory('');
const crearEstudiante = () => {
history.push('/Crear_Estudiante');
};
const initialState = preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
const parseData = {
pathname: '/Crear_Pedidos',
data: estudiantesData,
};
const realizarPedidos = () => {
if (estudiantesData == 0) {
window.alert('Seleccione al menos un estudiante');
} else {
history.push(parseData);
}
};
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</GridToolbarContainer>
);
}
const [estudiantes, setEstudiantes] = useState([]);
const [selectionModel, setSelectionModel] = useState([]);
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'nombre', headerName: 'Nombre', width: 200 },
{ field: 'colegio', headerName: 'Colegio', width: 250 },
{ field: 'grado', headerName: 'Grado', width: 150 },
{
field: 'delete',
width: 75,
sortable: false,
disableColumnMenu: true,
renderHeader: () => {
return (
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
estudiantes
.filter((x) => selectedIDs.has(x.id))
.map((x) => {
db.collection('usuarios')
.doc(user.uid)
.collection('estudiantes')
.doc(x.uid)
.delete();
});
}}
>
<DeleteOutlinedIcon />
</IconButton>
);
},
},
];
const deleteProduct = (estudiante) => {
if (window.confirm('Quiere borrar este estudiante ?')) {
db.collection('usuarios').doc(user.uid).collection('estudiantes').doc(estudiante).delete();
}
};
useEffect(() => {}, [estudiantesData]);
const estudiantesRef = db.collection('usuarios').doc(user.uid).collection('estudiantes');
useEffect(() => {
estudiantesRef.onSnapshot((snapshot) => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes);
});
}, []);
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
return (
<Container fixed>
<Box mb={5} pt={2} sx={{ textAlign: 'center' }}>
<Button
startIcon={<PersonAddIcon />}
variant="contained"
color="primary"
size="medium"
onClick={crearEstudiante}
>
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{ height: '390px', width: '850px', textAlign: 'center' }}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange={(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id));
setEstudiantesData(selectedRowData);
}}
{...estudiantes}
/>
</Box>
<Button
startIcon={<ShoppingCartSharpIcon />}
variant="contained"
color="primary"
size="medium"
onClick={realizarPedidos}
>
Crear pedido
</Button>
</Box>
</Container>
);
};
I suspect that it's because this useEffect does not have a dependency array and is bring run on every render.
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
})
Try adding a dependency array as follows:
useEffect (() => {
if (estudiantes && estudiantes.length>0)
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
},[estudiantes])
This will still set the localStorage to [] when it runs on the first render. But when the data is fetched and estudiantes is set, the localStorage value will be updated. So I've added a check to check if it's not the empty array.
Change the dependency array of this useEffect to []:
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes)
})
}, []);
The data flow in your code is somewhat contradictory, so I modify your code, and it works fine.
You can also try delete or add button, it will modify firebase collection, then update local data.
You can click refresh button in codesandbox previewer (not browser) to observe the status of data update.
Here is the code fargment :
// Set value of `localStorage` to component state if it exist.
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("estudiantes");
localStorageEstData && setEstudiantes(JSON.parse(localStorageEstData));
}, []);
// Sync remote data from firebase to local component data state.
useEffect(() => {
// Subscribe onSnapshot
const unSubscribe = onSnapshot(
collection(db, "usuarios", user.id, "estudiantes"),
(snapshot) => {
const remoteDataSource = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
console.info(remoteDataSource);
setEstudiantes(remoteDataSource);
}
);
return () => {
//unSubscribe when component unmount.
unSubscribe();
};
}, [user.id]);
// when `estudiantes` state update, `localStorage` will update too.
useEffect(() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes));
}, [estudiantes]);
Here is the full code sample :
Hope to help you :)

I get an error when using reactjs useState

I am using reactjs. I am using the material-table to get the data with the editable table.
But I get an error like the picture, how can I fix this error?
I use useState for the edit settings of the table.
Please can you help with the error?
I do not receive any errors while receiving data. I just use editing on the table as active / inactive.
But
     const [, forceUpdate] = useState (false);
     const [data, setData] = useState (drBounty);
gives error for lines.
screenshot of the error and my source code below
import React, { Component, useState } from "react";
import withAuth from "../../components/helpers/withAuth";
import AlertMessageBox from "../../components/helpers/AlertMessageBox";
import { connect } from "react-redux";
import { Button, Col, Row, Table, Input } from "reactstrap";
import MaterialTable, { MTableEditRow } from "material-table";
import icons from '#material-ui/core/Icon';
import DeleteOutline from '#material-ui/icons/DeleteOutline';
import Edit from '#material-ui/icons/Edit';
class Bounty extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: true,
drBounty: [],
drList: [],
columns: [
{ title: 'Name', field: 'doctorName',
cellStyle:{padding: "1px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "1px"},
editComponent: (props) => (
<Input
type="text"
placeholder={props.columnDef.title}
defaultValue={props.value}
onChange={(e) => props.onChange(
this.setState({
doctorName: e.target.value
})
)}
/>
)
},
{ title: 'LastName', field: 'doctorLastName',
cellStyle:{padding: "1px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "5px"},
editComponent: (props) => (
<Input
type={"text"}
placeholder={"Doktor soyadı"}
defaultValue={props.value}
onChange={(e) => props.onChange(
this.setState({
doctorLastName: e.target.value
})
)}
/>
)
}
]
};
this.getBountyList = this.getBountyList.bind(this);
}
async componentDidMount() {
await fetch(
`${this.domain}/api/user/groupusers?groupCode=`+
this.props.account_profile.profile.profile.groupCode,
{
headers: {
Authorization: `Bearer ${localStorage.getItem("id_token")}`,
"Content-Type": "application/json"
}
}
)
.then(res => {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => Promise.reject(err));
}
})
.then(json => {
console.log(json)
})
.catch(error => {
console.log(error)
return error;
});
}
async getBountyList(id) {
await fetch(`${this.domain}/api/bounty/list?groupCode=${this.props.account_profile.profile.profile.groupCode}&doctor=${id}`,{
headers: {
Authorization: `Bearer ${localStorage.getItem("id_token")}`,
"Content-Type": "application/json"
}
})
.then(res => {
console.log(res);
if (res.ok) {
return res.json();
} else {
return res.json().then(err => Promise.reject(err));
}
})
.then(json => {
console.log(json)
})
.catch(error => {
console.log(error);
return error;
});
}
render() {
const {isLoaded, drList, drBounty} = this.state;
const [, forceUpdate] = useState(false);
const [data, setData] = useState(drBounty);
const isRowUpdating = (rowData, status) => {
rowData.tableData.editing = status ? "update" : undefined;
forceUpdate(status);
};
if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className={"animated fadeIn "}>
<Row>
<div> </div>
<Col sm={{span:1, offset:0.9}}>
<Table>
<thead>
<tr>
<th width={"20"} />
<th width={"50"}>Adı</th>
<th width={"70"}>Soyadı</th>
</tr>
</thead>
<tbody>
{
drList
.map(item => (
<tr key={item.id}>
<td>
<Button
block
outline
color="info"
onClick={() => this.getBountyList(item.id)}
>
Aç
</Button>
</td>
<td>{item.first_name} </td>
<td>{item.last_name}</td>
</tr>
))}
</tbody>
</Table>
</Col>
<MaterialTable
Icons={icons}
style={{height: "50", width: "50"}}
columns={ this.state.columns }
data={ this.state.drBounty }
actions={[
rowData => ({
icon: Edit,
tooltip: "Edit row",
onClick: (event, rowData) => {
isRowUpdating(rowData, true);
this.setState({
id: rowData.id,
user: rowData.user,
doctor: rowData.doctor,
doctorName: rowData.doctorName,
doctorLastName: rowData.doctorLastName,
totalBounty: rowData.totalBounty,
description: rowData.description,
customerName: rowData.customerName,
bountyDate: rowData.bountyDate,
createdDate: rowData.createdDate,
groupCode: rowData.groupCode
});
}
})
]}
components={{
EditRow: props => {
const newRowData = {
...drBounty, // *MUST INCLUDE tableData FROM ORIGINAL props.data!!*
id: "DEFAULT VALUES", // <-- // Set whatever default data you want here
doctorName: "ON EDIT" // <-- // (or pull from state, etc.. whatever you want)
};
return (
<MTableEditRow
{...props}
data={newRowData}
onEditingCanceled={(mode, rowData) => {
isRowUpdating(rowData, false);
}}
onEditingApproved={(mode, newData, oldRowData) => {
const dataCopy = [...drBounty];
const index = drBounty.indexOf(props.data);
dataCopy[index] = newData;
setData(dataCopy);
isRowUpdating(props.data, false);
}}
/>
);
}
}}
/>
</Row>
</div>
);
}
}
}
export default connect(withAuth( Bounty ));
You are trying to use the Hook (useState()) inside the render() method. Hooks can only be used inside of function components. However, you are using a class component so you have no need of this Hook.
Suggested Reading: https://reactjs.org/docs/hooks-state.html
Instead of using a Hook, you can use the following in your class component to accomplish the same results. Let's have a look :)
Initialize State in Constructor
this.state = { foo: bar };
You have already done this!
Update State with this.setState()
const [data, setData] = useState(drBounty);
Becomes ..
this.setState({data:drBounty});
However, you want to update the drBounty prop that you set up in the constructor, so you will want something more like this ..
this.setState({drBounty:someData})
Since that prop is an array, you will most likely want to spread (...) that data using the current array.
Re-Render without Updating State
As for your other implementation of useState() it appears you want to re-render without making any updates to state.
const [, forceUpdate] = useState(false);
However, instead you will want to simply use ...
this.render()

I am trying to figure out how to create a clean up function as I keep getting an error

I am trying to figure out how to create a clean up function as I keep getting an error, if I remove "comments" from the useEffect dependencies, the error goes away, but then the app doesn't update in realtime, which is a problem. If anyone has worked with React and the realtime database or even Firestore and have any ideas on what I should do please let me know.
import React, { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import User from '../assets/images/user.svg';
import { AuthContext } from '../helpers/firebaseAuth';
import firebase from '../helpers/Firebase';
import Loading from '../helpers/Loading';
export const Comments = ({ match, history }) => {
const { register, handleSubmit, reset } = useForm();
const slug = match.params.slug;
const {...currentUser} = useContext(AuthContext);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = () => {
const data = firebase.database().ref(`/posts/${slug}/comments`)
data.once('value')
.then((snapshot) => {
if (snapshot) {
let comments = [];
const snapshotVal = snapshot.val();
for (let comment in snapshotVal) {
comments.push(snapshotVal[comment]);
}
setComments(comments);
setLoading(false);
}
});
}
fetchData();
}, [slug, comments])
if (loading) {
return <Loading />;
};
const postComment = (values) => {
console.log(!!currentUser.currentUser)
if (!!currentUser.currentUser) {
const comment = {
commentText: values.commentText,
commentCreator: currentUser.currentUser.displayName,
currentUserId: currentUser.currentUser.uid,
}
const postRef = firebase.database().ref(`posts/${slug}/comments`);
postRef.push(comment);
reset();
} else {
toast.error('You are not authenticated 😕');
}
};
const deleteComment = () => {
console.log(comments[0].commentUserId);
console.log(currentUser.currentUser.uid);
if (currentUser.currentUser.uid === comments[0].commentUserId) {
console.log('correct');
}
const key = firebase.database().ref(`posts/${slug}/comments`).once('value');
key.then(snapshot => {
console.log(snapshot.val());
}).catch((error) => {
console.log(error);
});
};
const back = () => {
history.push('./');
};
return (
<div className='main' style={{ maxWidth: '600px' }}>
<div className='see-user-comments' onClick={back} style={{ cursor: 'pointer', height: '50px' }}>
Commenting on the post: {slug}
</div>
<div className='see-user-comments' style={{ padding: '10px 0' }}>
<div>
<img src={User} alt='Profile' style={{ width: '30px' }} />
<span className='usertag-span'>{currentUser.displayName}</span>
</div>
<div>
<form onSubmit={handleSubmit(postComment)}>
<textarea
name='commentText'
rows='3'
style={{ margin: '10px 0' }}
placeholder='Add to the conversation!'
ref={register}
/>
<span style={{ width: '90%' }}>
<button>Comment</button>
</span>
</form>
</div>
</div>
{comments.map((comment, index) =>
<div key={index} className='see-user-comments' style={{ padding: '15px 0' }}>
<div style={{ height: '30px' }}>
<img src={User} alt='Profile' style={{ width: '30px' }} />
<div style={{ flexDirection: 'column', alignItems: 'flex-start', justifyItems: 'center' }}>
<span className='usertag-span'>{comment.commentCreator}</span>
</div>
</div>
<span className='commentText-span'>{comment.commentText}
{ !!currentUser?.currentUser?.uid === comments[0].commentUserId ?
(<button onClick={deleteComment}>Delete</button>) : null
}
</span>
</div>
)}
</div>
)
}
export default Comments;
Without seeing the error in question, I can only assume it's because using the following pattern causes an infinite loop because the effect is re-triggered every time count changes:
const [count, setCount] = useState(0);
useEffect(() => setCount(count + 1), [count]);
When you add comments to your effect, you are doing the same thing.
To solve this, you must change your effect to rely on Firebase's realtime events to update your comments array instead. This can be as simple as changing once('value').then((snap) => {...}) to on('value', (snap) => {...});. Because this is now a realtime listener, you must also return a function that unsubscribes the listener from inside your useEffect call. The least amount of code to do this correctly is:
const [postId, setPostId] = useState('post001');
useEffect(() => {
const postRef = firebase.database().ref('posts').child(postId);
const listener = postRef.on(
'value',
postSnapshot => {
const postData = postSnapshot.val();
// ... update UI ...
},
err => {
console.log('Failed to get post', err);
// ... update UI ...
}
)
return () => postRef.off('value', listener);
}, [postId]);
Applying these changes to your code (as well as some QoL improvements) yields:
const { register, handleSubmit, reset } = useForm();
const slug = match.params.slug;
const { ...authContext } = useContext(AuthContext); // renamed: currentUser -> authContext (misleading & ambiguous)
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(true);
let _postCommentHandler, _deleteCommentHandler;
useEffect(() => {
// don't call this data - it's not the data but a reference to it - always call it `somethingRef` instead
const postCommentsRef = firebase.database().ref(`/posts/${slug}/comments`);
// create realtime listener
const listener = postCommentsRef.on(
'value',
querySnapshot => {
let _comments = [];
querySnapshot.forEach(commentSnapshot => {
const thisComment = commentSnapshot.val();
thisComment.key = commentSnapshot.key; // store the key for delete/edit operations
_comments.push(thisComment);
});
setComments(_comments);
setLoading(false);
},
err => {
console.log(`Error whilst getting comments for post #${slug}`, err);
// TODO: handle error
});
// update new comment handler
_postCommentHandler = (formData) => {
console.log({
isLoggedIn: !!authContext.currentUser
});
if (!authContext.currentUser) {
toast.error('You are not authenticated 😕');
return;
}
const newComment = {
commentText: formData.commentText, // suggested: commentText -> content
commentCreator: authContext.currentUser.displayName, // suggested: commentCreator -> author
currentUserId: authContext.currentUser.uid, // suggested: commentUserId -> authorId
}
postCommentsRef.push(newComment)
.then(() => {
// commented successfully
reset(); // reset form completely
})
.catch(err => {
console.log(`Error whilst posting new comment`, err);
// TODO: handle error
reset({ commentText: formData.commentText }) // reset form, but leave comment as-is
})
}
// update delete handler
_deleteCommentHandler = () => {
if (!comments || !comments[0]) {
console.log('Nothing to delete');
return;
}
const commentToDelete = comments[0];
console.log({
commentUserId: commentToDelete.commentUserId,
currentUser: authContext.currentUser.uid
});
if (authContext.currentUser.uid !== commentToDelete.commentUserId) {
toast.error('That\'s not your comment to delete!');
return;
}
postCommentsRef.child(commentToDelete.key)
.remove()
.then(() => {
// deleted successfully
})
.catch(err => {
console.log(`Error whilst deleting comment #${commentToDelete.key}`, err);
// TODO: handle error
});
};
// return listener cleanup function
return () => postCommentsRef.off('value', listener);
}, [slug]);
const postComment = (values) => _postCommentHandler(values);
const deleteComment = () => _deleteCommentHandler();
Because I renamed currentUser to authContext, this will also need updating:
<div>
<img src={User} alt='Profile' style={{ width: '30px' }} />
<span className='usertag-span'>{authContext?.currentUser?.displayName}</span>
</div>

Resources