I am trying to make a connect button when the user clicks on it and connect he store the value inside a global state that i can use inside the whole application but this code doesn't work at all , why is it wrong ?
import React, { useState, useEffect } from 'react';
export const UserContexts = React.createContext();
const UserContext = ({children}) => {
const [getUser, setGetUser] = useState(null);
function connect() {
ethereum.request({ method : 'eth_requestAccounts'}).then(accounts => {
const account = accounts[0];
setGetUser(account)
})
}
useEffect(() => {
getUser ? null : connect();
},[])
const { Provider } = UserContexts;
return (
getUser ? <Provider value={getUser} >
{children}
</Provider>: null
)
}
export default UserContext
// navbar
import UserContext from './userContext'
<button onClick={UserContext.connect()} > connect </button>
when a user clicks on navbar connect button he login then when he login the _app saves the state globally so I can use it everywhere inside the app , I know this is a wrong syntax but how can I make it work ?
I solved this problem combined useContext and useReducer.
import React, {createContext,useContext,useEffect,useReducer} from "react";
const UserContext = createContext();
export function useBlockchainContext() {
return useContext(BlockchainContext);
}
function reducer(state, { type, payload }) {
return {
...state,
[type]: payload,
};
};
const init_state = {
user: ""
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, init_state);
return (
<BlockchainContext.Provider value={[state, dispatch]}>
{children}
</BlockchainContext.Provider>
)
}
// navbar
import { useBlockchainContext } from "../userContext";
export default function NavBar() {
const [state,dispatch] = useBlockchainContext();
const connect = ()=> {
ethereum.request({ method : 'eth_requestAccounts'}).then(accounts => {
const account = accounts[0];
dispatch({
type: "user",
payload: {
account
}
});
})
};
return(
<button onClick={()=>connect()} >{state.user !==""?state.user.slice(0, 4) + "..." + state.user.slice(-4):connect}</button>
)
}
Related
Tech Stack
react - 17.0.2
react-dom - 17.0.2
next - 11.1.1
Contexts
userContext.js
import React from "react";
const UserContext = React.createContext(null);
export default UserContext;
utilContext.js
import React from "react";
const UtilContext = React.createContext(null);
export default UtilContext;
_app.js - docs
import { useState, useEffect } from "react";
import { ChakraProvider } from "#chakra-ui/react";
import { Provider as ReduxProvider } from "react-redux";
import { useStore } from "../store";
import UserContext from "#src/context/userContext";
import UtilContext from "#src/context/utilContext";
function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState);
const [user, setUser] = useState(null);
const [showNav, setShowNav] = useState(true);
const [showToTop, setShowToTop] = useState(false);
useEffect(() => {
const user = getLocalStorage("user");
if (user) {
setUser(JSON.parse(user));
}
// show and hide navbar
let prevScrollpos = window.pageYOffset;
window.onscroll = function () {
let currentScrollPos = window.pageYOffset;
if (prevScrollpos > currentScrollPos) {
setShowNav(true);
} else {
setShowNav(false);
}
prevScrollpos = currentScrollPos;
// scroll to top button
if (
document.body.scrollTop > 20 ||
document.documentElement.scrollTop > 20
) {
setShowToTop(true);
} else {
setShowNav(true);
setShowToTop(false);
}
};
}, []);
const updateUser = (data) => {
let localUser = getLocalStorage("user");
if (localUser) {
localUser = JSON.parse(localUser);
} else {
localUser = {};
}
const mergeUser = { ...localUser, ...data };
setUser(mergeUser);
setLocalStorage("user", JSON.stringify(mergeUser));
};
return (
<>
<ChakraProvider>
<ReduxProvider store={store}>
<UserContext.Provider value={{ data: user, setUser: updateUser }}>
<UtilContext.Provider value={{ showNav, showToTop }}>
<Component {...pageProps} />
</UtilContext.Provider>
</UserContext.Provider>
</ReduxProvider>
</ChakraProvider>
</>
);
}
export default MyApp;
component.js
import UserContext from "#src/context/userContext";
const Component=()=>{
const user = useContext(UserContext);
const [posts, setPosts] = useState({});
useEffect(() => {
console.log("User changed..")
if (!user.data?._id) return;
setPosts({ loading: true });
GET(`/api/post/fetch/all/by-author/${user.data._id}?private=true`)
.then((res) => setPosts({ data: res.data, loading: false }))
.catch((err) => setPosts({ err: true, loading: false }));
}, [user]);
// render posts which is a long list
}
The problem
You can see in the _app.js file, I am updating the utilContext on the window.scroll event.
But updating utilContext is also triggering the hook of Component.
And, whenever I scroll the page, then I got this message logged on the console.
I don't see this anywhere that a context update will update the rest of the contexts in the application, please let me know if I am doing something wrong.
User changed..
User changed..
User changed..
...
I have a custom hook
import { useState } from "react";
import { useDispatch } from "react-redux";
import axios from "axios";
import getDevices from "../actions/devicesAtions";
import { isPositiveInteger, FORM_FIELDS } from "../helper";
export default function useDevice(value) {
const dispatch = useDispatch();
const [device, setDevice] = useState(value);
const [msg, setMsg] = useState("");
const saveDeviceChange = ({ target }) => {
const { name, value } = target;
setDevice({
...device,
[name]: value
});
setMsg("");
};
const saveDeviceSubmit = async (
e,
axiosMethod,
selectedUrl,
newDevice = device
) => {
e.preventDefault();
const { system_name, type, hdd_capacity } = device;
if (!system_name || !type || !hdd_capacity) {
setMsg(
`Please fill out ${!system_name ? FORM_FIELDS.SYS_NAME : ""} ${
!type ? FORM_FIELDS.DEVICE_TYPE : ""
} ${!hdd_capacity ? FORM_FIELDS.HDD_CAPACITY : ""}!`
);
return false;
}
if (!isPositiveInteger(hdd_capacity)) {
setMsg(
"Please enter a positive number or round it to the nearst whole number!"
);
return false;
}
try {
await axios({
method: axiosMethod,
url: selectedUrl,
data: device,
headers: {
"Content-Type": "application/json"
}
});
dispatch(getDevices());
} catch (err) {
console.log(err);
}
setMsg("Changes have been made!");
setDevice(newDevice);
};
return {
device,
setDevice,
msg,
setMsg,
saveDeviceChange,
saveDeviceSubmit
};
}
The EditDeviceWrapper component uses the states and functions from the custom hook. When this component renders, the selectedDevice is assigned as the value for the device that's from the custom hook. When first rendered, the values are displayed correctly on the from. However, after I clicked refresh, the device state from the custom hook disappear while the selectedDevice state from the redux still exits. How to maintain the device state from the custom hook after refreshing the the component?
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import ReusedForm from "./ReusedForm";
import useDevice from "./useDevice";
import { getDeviceDetails } from "../actions/devicesAtions";
export default function EditDeviceWrapper() {
const dispatch = useDispatch();
const { selectedDevice } = useSelector((state) => state.allDevices);
const { id } = useParams();
useEffect(() => {
dispatch(getDeviceDetails(id));
}, [id, dispatch]);
const { device, msg, saveDeviceChange, saveDeviceSubmit } = useDevice(
selectedDevice
);
console.log(device, selectedDevice);
return (
<>
<p className="form-msg">{msg}</p>
<ReusedForm
saveDeviceSubmit={(e) =>
saveDeviceSubmit(e, "put", `http://localhost:3000/devices/${id}`)
}
selectDeviceValChange={saveDeviceChange}
heading="Update Device"
system_name={device.system_name}
type={device.type}
hdd_capacity={device.hdd_capacity}
/>
<p>{id}</p>
</>
);
}
May be persist the state after page refresh this link helps you to resolve your problem.
I found different already answered questions to my question, but the don't help.
I use a custom context to call the firebase.auth().onAuthStateChanged() and set the currentUser.
import React, { useState, useEffect } from "react";
import app from "../firebase";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
};
In my component I call the AuthContext and the currentUser:
import React, { useContext, useEffect, useState } from "react";
import app from "./firebase";
import { AuthContext } from "./Auth/Auth";
function MyComponent() {
const [invoices, setInvoices] = useState([]);
const { currentUser } = useContext(AuthContext);
const getInvoices = () => {
const database = app.firestore();
const unsubscribe = database
.collection("invoices")
.where("uid", "==", currentUser.uid) // HERE currentUser IS NULL
.orderBy("date", "desc")
.onSnapshot((snapshot) => {
setInvoices(
snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))
);
});
return () => {
unsubscribe();
};
};
useEffect(() => {
getInvoices();
}, []);
return (<> ... </>);
}
export default MyComponent;
I believe my issue has something to do with promises and the user is not yet loaded. But still I don't know what to do here.
The potential issue could be the value of currentUser returns a bit later so you need to add an extra check in your MyComponent component.
I would add null check for currentUser and extend the dependency array as:
useEffect(() => {
if (currentUser) {
getInvoices();
}
}, [currentUser]);
Probably in the first round the useEffect callback was running once currentUser was still null.
Getting a weird error where 'map' is undefined. I'm not sure if my functions are firing at the wrong time and that's resulting in no data being received.
I'm adding Redux into my simple little application that just pulls data from an API and displays it. It's a list of a bunch of Heroes. Like I said before, I think that the error is coming from different times in the ansyc API call and when Redux is firing. But then again I'm a novice so any help is much appreciated.
import React, {useEffect} from 'react'
import { connect } from 'react-redux'
import { fetchHeroes } from '../actions/heroesActions'
import { Hero } from '../components/Hero'
const HeroesPage = ({ dispatch, loading, heroes, hasErrors }) => {
useEffect(() => {
dispatch(fetchHeroes())
}, [dispatch])
const renderHeroes = () => {
if (loading) return <p>Loading posts...</p>
if (hasErrors) return <p>Unable to display posts.</p>
return heroes.map(hero => <Hero key={hero.id} hero={hero} />)
}
return (
<section>
<h1>Heroes</h1>
{renderHeroes()}
</section>
)
}
// Map Redux state to React component props
const mapStateToProps = state => ({
loading: state.heroes.loading,
heroes: state.heroes.heroes,
hasErrors: state.heroes.hasErrors,
})
export default connect(mapStateToProps)(HeroesPage)
export const GET_HEROES = 'GET HEROES'
export const GET_HEROES_SUCCESS = 'GET_HEROES_SUCCESS'
export const GET_HEROES_FAILURE = 'GET_HEROES_FAILURE'
export const getHeroes = () => ({
type: GET_HEROES,
})
export const getHeroesSuccess = heroes => ({
type: GET_HEROES_SUCCESS,
payload: heroes,
})
export const getHeroesFailure = () => ({
type: GET_HEROES_FAILURE,
})
export function fetchHeroes() {
return async dispatch => {
dispatch(getHeroes())
try {
const response = await fetch('https://api.opendota.com/api/heroStats')
console.log(response)
const data = await response.json()
dispatch(getHeroesSuccess(data))
} catch (error) {
dispatch(getHeroesFailure())
}
}
}
index.js where I created the store
// External imports
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
// Local imports
import App from './App'
import rootReducer from './reducers'
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
import React, {useEffect} from 'react'
import { useSelector } from 'react-redux'
import { fetchHeroes } from '../actions/heroesActions'
import { Hero } from '../components/Hero'
const HeroesPage = () => {
const data = useSelector(state => state.heroes);
useEffect(() => {
fetchHeroes();
}, [])
const renderHeroes = () => {
if (data.loading) return <p>Loading posts...</p>
if (data.hasErrors) return <p>Unable to display posts.</p>
return data.heroes.map(hero => <Hero key={hero.id} hero={hero} />)
}
return (
<section>
<h1>Heroes</h1>
{renderHeroes()}
</section>
)
}
export default HeroesPage
action file
// import store from your createStore file and access dispatch from it
dispatch = store.dispatch
export const GET_HEROES = 'GET HEROES'
export const GET_HEROES_SUCCESS = 'GET_HEROES_SUCCESS'
export const GET_HEROES_FAILURE = 'GET_HEROES_FAILURE'
export const getHeroes = () => ({
type: GET_HEROES,
})
export const getHeroesSuccess = heroes => ({
type: GET_HEROES_SUCCESS,
payload: heroes,
})
export const getHeroesFailure = () => ({
type: GET_HEROES_FAILURE,
})
export const fetchHeroes = () => {
dispatch(getHeroes())
try {
const response = await fetch('https://api.opendota.com/api/heroStats')
console.log(response)
const data = await response.json()
dispatch(getHeroesSuccess(data))
} catch (error) {
dispatch(getHeroesFailure())
}
}
reducer file
import * as actions from '../actions/heroesActions'
export const initialState = {
heroes: [],
loading: false,
hasErrors: false,
}
export default function heroesReducer(state = initialState, action) {
switch (action.type) {
case actions.GET_HEROES:
return { ...state, loading: true }
case actions.GET_HEROES_SUCCESS:
return { heroes: action.payload, loading: false, hasErrors: false }
case actions.GET_HEROES_FAILURE:
return { ...state, loading: false, hasErrors: true }
default:
return state
}
}
Need help, just started to learn React. I'm trying to pass variables with json data to a component for further use, but catching the errors. what should I change to use variables with json() data from Store.js in the product.js component? THanks for your time!
https://jsfiddle.net/constant101/xu7zdn26/3/ for better visibility
//Store export(receiving data from the server and assigning them to variables)
import React, {useState, useEffect} from 'react'
import axios from 'axios'
export const ListContext = React.createContext([]);
export const ItemContext = React.createContext([]);
function Store() {
const [storeProducts, setStoreProducts] = useState([]);
const [detailProduct, setDetailProduct] = useState([]);
useEffect(() => {
axios.get('/products/')
.then(res => {
console.log(res)
setStoreProducts(res.data)
})
},[])
console.log('storeProducts:', storeProducts)
useEffect(() => {
axios.get('/products/:productId')
.then(res => {
console.log(res)
setDetailProduct(res.data)
})
},[])
console.log('detail product:', detailProduct)
return (
<ListContext.Provider value={[storeProducts, setStoreProducts]}>
<ItemContext.Provider value={[detailProduct, setDetailProduct]}>
<product/>
</ItemContext.Provider>
</ListContext.Provider>
);
}
export const detailProduct
//product.js ( file that uses data from the fetch)
import React, { useReducer, createContext, useContext, useState } from 'react';
import {ListContext, ItemContext } from '../Store';
import { useProductActions } from '../actions';
import { SET_PRODUCT_DETAILS } from '../actions/types';
const [storeProducts] = useContext(ListContext);
const [detailProduct] = useContext(ItemContext);
let tempProducts = [];
storeProducts.forEach(item => tempProducts.push({ ...item })
);
const initialState = {
products: tempProducts,
productDetails: { ...detailProduct }
};
console.log(storeProducts)
const productReducer = (state, action) => {
switch (action.type) {
case SET_PRODUCT_DETAILS:
return {
...state,
productDetails: action.payload
};
default:
throw new Error('Invalid action type');
}
};
export const ProductContext = createContext(initialState);
export const useProductState = () => {
return useContext(ProductContext);
};
export const ProductProvider = ({ children }) => {
const [state, dispatch] = useReducer(productReducer, initialState);
const productActions = useProductActions(state, dispatch);
return (
<ProductContext.Provider value={{ productState: state, productActions }}>
{children}
</ProductContext.Provider>
);
};
Well, assuming your request is right, i saw a syntax mistake. You should pass
<ListContext.Provider value={{storeProducts, setStoreProducts}}> instead of
<ListContext.Provider value={[storeProducts, setStoreProducts]}>
The reason:
a provider requires a prop called value with an Object inside.
In that case, you were passing an array.
it would be the same if you did:
<ListContext.Provider
value={{
storeProducts: storeProducts,
setStoreProducts: setStoreProducts
}}
>
but to follow the DRY principle, it's recommended to do that way described earlier