Why does this not print anything on website React.js? - reactjs

I'm quite new to web development, I am wondering why when I call readContacts() in Contacts.js why the readContacts() function for ContactsContext.js does not output the return to my website? Thank you in advanced.
ContactsContext.js
import React, { useContext } from "react"
import { db } from "../firebase"
import { useAuth } from "./AuthContext"
const ContactsContext = React.createContext()
export function useContacts() {
return useContext(ContactsContext)
}
export function ContactsProvider({ children }) {
const { currentUser } = useAuth()
function createContact(email, firstName, lastName) {
db.ref("users/" + currentUser.uid + "/contacts/" + firstName + " " + lastName).set({
Email: email,
FirstName: firstName,
LastName: lastName
})
}
function readContacts() {
db.ref("users/" + currentUser.uid + "/contacts/").on("value", (snapshot) => {
return (snapshot.val())
})
}
const value = {
createContact,
readContacts
}
return (
<ContactsContext.Provider value={value}>
{children}
</ContactsContext.Provider>
)
}
Contacts.js
import React from "react"
import { ListGroup } from "react-bootstrap"
import { useContacts } from "../contexts/ContactsContext"
export default function Contacts() {
const { readContacts } = useContacts()
return (
<ListGroup variant="flush">
<ListGroup.Item>
{ readContacts() }
</ListGroup.Item>
</ListGroup>
)
}

readContacts function isn't returning anything. But even if you did add return db.ref(..... in the function, you're calling it in the render return. This won't work since React renders are synchronous, pure functions (without side-effects).
Typically you should setup these subscriptions in an useEffect hook and typically you'd update some local state with the snapshot value. Your code is so far from this it's difficult to say what your goal is. My best guess is that you want some contacts state in ContactsProvider and you should be providing this out to the app.
const ContactsContext = React.createContext({
// Don't forget to provide default context value
contacts: [],
createContact: () => {},
});
export function useContacts() {
return useContext(ContactsContext);
}
export function ContactsProvider({ children }) {
const { currentUser } = useAuth();
const [contacts, setContacts] = useState([]); // <-- add state
function createContact(email, firstName, lastName) {
db.ref("users/" + currentUser.uid + "/contacts/" + firstName + " " + lastName).set({
Email: email,
FirstName: firstName,
LastName: lastName
})
}
useEffect(() => {
db.ref("users/" + currentUser.uid + "/contacts/")
.on(
"value",
snapshot => setContacts(snapshot.val()), // <-- update state
);
// NOTE: you likely need to also unsubscribe when component unmounts
}, []);
const value = {
createContact,
contacts, // <-- provide contacts state
}
return (
<ContactsContext.Provider value={value}>
{children}
</ContactsContext.Provider>
)
}
Now context consumers access the contacts array value and render accordingly.
const { contacts } = useContacts();
...
contacts.map(contact => .....

Related

useEffect is being called twice even if it state is changed once

i made login hook called "useMakeQueryString". which is responsble for making queryString from Object.
import { useEffect, useState } from "react";
export default function useMakeQueryString(obj) {
const [queryString, setQueryString] = useState("");
const makeQueryString=(obj)=> {
let queryString1 = "";
for (let key in obj) {
//if (key instanceof Object) queryString1 += makeQueryString(obj);
queryString1 += key + "=" + obj[key] + "&";
}
setQueryString(queryString1.slice(0, queryString1.length - 1));
}
useEffect(()=>{
makeQueryString(obj)
},[obj])
return { queryString, makeQueryString };
}
then i imported that hook to my Google Component. on click of Component it calls the performAuth function and that function set the option state. and useEffect on option change is called. inside useEffect which is being called on option change i try to change queryString State. but the problem is useEffect on queryString change is being Called Twice
import useMakeQueryString from "../Login/LoginHook";
import { useEffect,useState } from "react";
export default function Google() {
const g_url = "https://accounts.google.com/o/oauth2/v2/auth";
const {queryString,makeQueryString} = useMakeQueryString({});
let [option,setOption] = useState({})
useEffect(() => {
console.log("length"+Object.keys(option).length)
if(Object.keys(option).length!=0) {
makeQueryString(option); // setQueryString(query);
}
}, [option])
useEffect(()=>{
if(queryString)
window.location = `${g_url}?${queryString}`;
},[queryString])
const performAuth = () => {
console.log("perform cliked")
const option1 = {
client_id: "432801522480-h02v02ivvti9emkd019fvreoisgj3umu.apps.googleusercontent.com",
redirect_uri: "http://localhost:3000/glogin",
response_type: "token",
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
].join(" "),
}
setOption(option1);
}
return (
<>
<button className="google-btn social-btn" onClick={() => performAuth()}>SignUp With Google</button>
</>
)
}

Very simple react destructuring issue with a custom hook

I'm amazed that I can't solve this myself, but I'm having a basic issue here. Essentially I just want to destructure the user variable in the useSetWelcome hook to prevent the use of verbose chaining such as user.user.email - for instance, const { email } = user does not work and instead needs user.user.
I tried changing from const useSetWelcome = user => { to const useSetWelcome = ({ user }) => {, but that results in an infinite loop.
Where am I going wrong here? My code demo: https://stackblitz.com/edit/react-b1jroe
And the code:
import React, { useState, useEffect } from 'react';
const joe = {
name: 'Joe',
email: 'joe#bloggs.com'
};
// const useSetWelcome = ({ user }) => { // infinite loop problem
const useSetWelcome = user => {
const [name, setName] = useState(null);
const [welcomeMsg, setWelcomeMsg] = useState('No user detected');
// const { email } = user; // needs user.user
// console.log('user', user);
// console.log('{user}', { user });
// console.log('user.email', user.email); // should be joe#bloggs.com
// console.log('email', email); // should be joe#bloggs.com
console.log('user?.user?.email', user?.user?.email); // works
if (user.name) {
setName(user.name);
setWelcomeMsg('welcome ' + user.name);
}
return { name, welcomeMsg };
};
const App = () => {
// const [user, setUser] = useState(joe); // joe or {joe}? and why?
const [user, setUser] = useState(joe);
console.log('state user', user);
const toggleLogin = user => {
if (user) {
setUser(null);
} else {
setUser(joe);
}
};
const loginMsg = user ? 'Logout' : 'Login';
const Welcome = user => {
const { name, welcomeMsg } = useSetWelcome(user);
return (
<p>
{welcomeMsg} {name}
</p>
);
};
return (
<div>
<Welcome user={user} />
<button onClick={() => toggleLogin(user)}>{loginMsg}</button>
</div>
);
};
export default App;
The problem is, <Welcome /> is a component. A component receives only one parameter, props. So, when you write this: const Welcome = user => {, its actually const Welcome = props => {.
Long story short, change this line to const Welcome = ({ user }) => { (so you destruct user from props) and it will work.
P.S.: You're getting an infinite loop because inside your useSetWelcome hook, you have this condition:
if (user.name) {
setName(user.name)
}
When you use setName, the entire hook rerenders, and the condition is tested again. Again, user.name will exist, and setName will get called again, and again, and forever. To achieve what I think you intended to, you have to improve the condition to something like this:
if (user.name && user.name !== name) {
setName(user.name);
setWelcomeMsg('welcome ' + user.name);
}

UseEffect doesn't trigger on second change, but trigger twice on launch.(React hooks and apollo-graphql hooks)

useEffect doesn't trigger on second change, but triggers twice on launch (React hooks and apollo-graphql hooks).
In console.logs I described when the changes are triggered and when not.
I don't have any more clue to add.
Here's my page (Next.js pages)
import React, { useEffect, useState } from 'react'
import Calendar from '../components/Calendar';
import { useHallsQuery } from '../generated/graphql';
const schedule = () => {
const { data, loading, error, refetch:refetchSessions } = useHallsQuery();
const [sessions, setSessions] = useState([] as any);
const [owners, setOwners] = useState([] as any);
useEffect(() => {
console.log('On launch trigger twice, on first change trigger once, on second doesn't trigger and later trigger correctly, but it's one change delay');
if(loading === false && data){
const colors: any = [
'#FF3333',
'#3333FF',
'#FFFF33',
'#33FF33',
'#33FFFF',
'#9933FF',
'#FF9933',
'#FF33FF',
'#FF3399',
'#A0A0A0'
];
let pushSessions:any = [];
let owners:any = [];
data?.halls?.map(({sessions, ...o}, index) =>{
owners.push({id:o.id,
text:o.name,
color: colors[index%10]});
sessions.map((session:any) => {
pushSessions.push({...session,
ownerId: o.id});
})
})
setSessions(pushSessions);
setOwners(owners);
}
}, [loading, data])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<Calendar sessions={sessions} owners={owners} refetchSessions={refetchSessions} />
</div>
)
}
export default schedule
and my component part where I get props and trigger refetchSessions.
const Calendar = (props: any) => {
let { sessions, owners, refetchSessions } = props;
const [moveSession] = useMoveSessionMutation();
...
const commitChanges = ({ added, changed, deleted }: any) => {
if (added) {
//
}
if (changed) {
console.log('trigger on all changes! Correct')
const id = Object.keys(changed)[0];
moveSession({variables:{
input: {
id: parseInt(id),
...changed[id]
}
}, refetchQueries: refetchSessions
})
}
if (deleted !== undefined) {
//
}
};
return (
// Some material-ui components and #devexpress/dx-react-scheduler-material-ui components in which commitChanges function is handled
)
export default Calendar;
Hook was generated with graphql-codegen(generated/graphql.tsx):
export function useHallsQuery(baseOptions?: Apollo.QueryHookOptions<HallsQuery, HallsQueryVariables>) {
return Apollo.useQuery<HallsQuery, HallsQueryVariables>(HallsDocument, baseOptions);
}
export function useHallsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<HallsQuery, HallsQueryVariables>) {
return Apollo.useLazyQuery<HallsQuery, HallsQueryVariables>(HallsDocument, baseOptions);
}
export type HallsQueryHookResult = ReturnType<typeof useHallsQuery>;
and here's the schema(graphql/hall.graphql):
query Halls {
halls {
id
name
sessions{
id
title
startDate
endDate
}
}
}

React Hooks reducer causing memory leak

I have a React component that calls a reducer on initialisation to populate an array with as many blank records as a number specified in a firebase db. If the firebase db field myArray length is 5, the component will initialise and call the reducer to create 5 blank records in an array in the context:
import React, { useState, useEffect, useContext } from 'react';
import { useHistory, useLocation } from 'react-router';
import ArrayCard from '../../../containers/ArrayCard';
import firebase from '../../../Utils/firebase';
import MyContext from '../../../Utils/contexts/MyContext ';
import { initialiseMyArray, changeArray} from '../../../Utils/reducers/MyActions';
function MyComponent() {
const { push } = useHistory();
const location = useLocation();
const context = useContext(MyContext);
const dispatch = context.dispatch;
const session = location.state.currentSession;
const sessionRef = firebase.firestore().collection("sessions").doc(session);
const [ loaded, setLoaded ] = useState(false);
const [ myArray, setMyArray] = useState([]);
const [ myArrayCurrentIndex, setMyArrayCurrentIndex ] = useState();
const getSessionData = () => {
sessionRef.get().then(function(doc) {
if (doc.exists) {
const myArrayLength= parseInt(doc.data().myArrayLength);
dispatch({ type: initialisePensionsFromFirebase, myArrayLength: myArrayLength});
setMyArrayCurrentIndex (0);
setLoaded(true);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
useEffect(() => {
if (sessionRef && !loaded && myArray.length < 1) {
getSessionData();
}
if (context) {
console.log("CONTEXT ON PAGE: ", context)
}
}, [loaded, currentIndex])
The index currentIndex of the newly-created myArray in the context is used to populate a component inside this component:
return (
<innerComponent
handleAmendSomeproperty={(itemIndex, field, value) => handleAmendSomeproperty(itemIndex, field, value)}
itemIndex={currentIndex}
someproperty={context.state.someitem[currentIndex].someproperty}
></innerComponent>
)
I want to be able to amend the someproperty field in myArray[currentIndex], but my dispatch causes endless rerenders.
const handleAmendSomeproperty= (itemIndex, field, value) => {
dispatch({ type: changeItem, itemIndex: itemIndex});
}
My reducer switch cases are like so:
case initialiseMyArray:
console.log("SRSTATE: ", state);
let _initialArray = []
for (let i=0; i<action.myArrayLength; i++) {
_initialArray .push(
{
itemIndex: i,
someproperty: "" }
)
}
return {
...state,
someproperty: [..._initialArray ]
};
case changeArray:
// I want to leave the state untouched at this stage until I stop infinite rerenders
return {
...state
};
What is happening to cause infinite rerenders? How come I can't amend someproperty, but initialising the new array in the state works fine?
innerComponent has lots of versions of this:
<div>
<label>{label}</label>
<div onClick={() => handleAmendSomeproperty(itemIndex, "fieldName", "fieldValue")}>
{someproperty === "booleanValue" ? filled : empty}
</div>
</div>
and lots like this:
<input
type="text"
placeholder=""
value={somepropertyvalue}
onChange={(event) => handleAmendSomeproperty(itemIndex, "fieldName", event.target.value)}
/>

useEffect is not running for every change in dependency array

When I update an expense, it doesn't trigger the useEffect to store the the data in local storage.
codesandbox
import React, { useState, useEffect } from 'react';
export const ExpenseContext = React.createContext();
const ExpenseState = (props) => {
const [state, setState] = useState(
() => JSON.parse(localStorage.getItem('expenses')) || []
);
useEffect(() => {
console.log('didUpdate', state)
state.length !== 0 && localStorage.setItem('expenses', JSON.stringify(state));
}, [state])
function addExpense({id, name, category, value, note, date}){
const expense = state.find(exp => exp.id === id);
if(!expense){
setState(state => ([...state, {id, name, category, value, note, date}]))
}else{
const updatedExpense = state.map(exp => {
if(exp.id === expense.id){
return {
...exp,
...{id, name, category, value, note, date}
}
}else{
return exp
}
})
setState(updatedExpense)
}
console.log(state)
}
return (
<ExpenseContext.Provider
value={{
expenses: state,
addExpense: addExpense,
}}
>
{props.children}
</ExpenseContext.Provider>
)
}
I am calling the addExpense for every elements of the array. Assume we have 4 expenses stored in storage. Now we have updated one expense and then running the below code.
for(const expense of expenses){
expenseContext.addExpense(expense);
}
Now the use effect only get triggered for the last element and the expense also not getting updated in context.
You code is working, I'm not sure if you just missed some stuff?
1. export default ExpenseState is missing?
2. Wrap your App with ExpenseState context
Assuming you have a default CRA setup:
// index.js
import React from "react"
import ReactDOM from "react-dom"
import ExpenseState from "./AppContext" // or where your Context
import App from "./App"
const rootElement = document.getElementById("root")
ReactDOM.render(
<ExpenseState>
<App />
</ExpenseState>,
rootElement
)
And your App.js
// App.js
import React, { useContext } from "react"
import { ExpenseContext } from "./AppContext"
export default () => {
const { expenses, addExpense } = useContext(ExpenseContext)
const handleExpense = () => {
addExpense({
id: 1, // Change ID to add another object
name: "some name",
category: "some category", // keep the id and change some values for update
value: "some value",
note: "some note",
date: "some date"
})
}
return (
<div className="App">
<button onClick={handleExpense}>Add Expenses</button>
<pre>
<code>{JSON.stringify(expenses, null, 2)}</code>
</pre>
</div>
)
}
Working codeSandBox example.
The problem is that useState is working differently than you think it does. Inside the consumer you're calling api.addExpense(...) three times in a row synchronously with the expectation that calling setState will update state in-place - it doesn't, the state is only updated on the next render call and not at line 40 as your console.log there suggests you're expecting it to.
So instead of the three complete state updates you're expecting (one for each expense) you only get the state from the last update for {id: "d6109eb5-9d7b-4cd3-8daa-ee485b08361b", name: "book", category: "personal care", value: 200}, which has no changes and is still based on the original state not one where the first item's value has been set to 800.
I'd suggest modifying your ExpenseState api to take an array of expenses so it can handle multiple internal updates appropriately.
function addOrUpdateExpense(state, { id, name, category, value }) {
const expense = state.find(exp => exp.id === id);
if (!expense) {
return [...state, { id, name, category, value }];
} else {
const updatedExpense = state.map(exp => {
if (exp.id === expense.id) {
return {
...exp,
...{ id, name, category, value }
};
} else {
return exp;
}
});
return updatedExpense;
}
}
function addExpenses(expenses) {
let newState = [...state];
for (const expense of expenses) {
newState = addOrUpdateExpense(newState, expense);
}
setState(newState);
}
// ...usage
function doIt() {
api.addExpenses(expenses);
}
This is a common misconception, just remember that the next state is only available asynchronously in the next render phase, treat state from useState as immutable within a render pass.

Resources