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.
Related
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>
)
}
I really seem to have trouble with react and hooks. I know that the problem could be do to react-dom. I am watching a tutorial that does it with 5 and im using 6. I looked at the documentation, but I honestly can't find the issue. It worked for my homepage, but now the product page doesn't work and I honestly couldn't find anything on past answers about this problem. Does anybody have an idea?? Thanks!
import React, {useState, useEffect} from 'react'
import { Link } from 'react-router-dom'
import { Row, Col, Image, ListGroup, Button, Card } from 'react-bootstrap'
import Rating from '../components/Rating'
import Loader from '../components/Loader'
import Message from '../components/Message'
import { useParams } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { listProductDetails } from '../actions/productActions'
function ProductScreen({match}) {
const dispatch = useDispatch()
const productDetails = useSelector(state => state.productDetails)
const {loading, error, product} = productDetails
useEffect(() =>{
dispatch(listProductDetails(match.params.id))
},[])
ProductActions.js:
export const listProductDetails = (id) => async(dispatch) => {
try {
dispatch({type: PRODUCT_DETAILS_REQUEST})
const { id } = useParams();
const {data} = await axios.get(`/api/products/${id}`)
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload: error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
productReducer.js:
export const productDetailsReducer = (state={product:{reviews:[]}},action) => {
switch(action.type){
case PRODUCT_DETAILS_REQUEST:
return {loading:true, ...state}
case PRODUCT_DETAILS_SUCCESS:
return {loading:false, product:action.payload}
case PRODUCT_DETAILS_FAIL:
return {loading:false, error: action.payload}
default:
return state
}
}
store.js:
import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import {productListReducer,productDetailsReducer} from './reducers/productReducers'
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer,
})
const initialState = {}
const middleware = [thunk]
const store = createStore(reducer, initialState,
composeWithDevTools(applyMiddleware(...middleware)))
export default store
export const listProductDetails = (id) => async(dispatch) => {
try {
dispatch({type: PRODUCT_DETAILS_REQUEST})
const { id } = useParams();
[...]
You are passing id as argument of listProductDetails then you are redefining it inside the function.
Move const { id } = useParams(); into ProductScreen and pass it as argument to function instead of using match.
Also listProductDetails returns an async function that takes dispatch as argument. So don't call dispatch(listProductDetails(id)) but instead call listProductDetails(id)(dispatch).
Also calling dispatch within the api is not a good idea. You generally want action function to return something to be dispatched within a React Component.
function ProductScreen() {
const dispatch = useDispatch();
const { id } = useParams();
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
useEffect(() => {
const fetchData = async (id) => {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
dispatch(await listProductDetails(id));
};
fetchData(id);
}, [dispatch, id]);
return <div>APP</div>;
}
In ProductActions.js
export const listProductDetails = async (id) => {
try {
const { data } = await axios.get(`/api/products/${id}`);
return {
type: PRODUCT_DETAILS_SUCCESS,
payload: data,
};
} catch (error) {
return {
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
};
}
};
I'm in the process of building a merch e-commerce website for a client utilizing the commerce.js API however I've run into a problem. When passing the "cart" object as a prop to the checkout file it returns as an empty object which breaks the website. The web application passes the "cart" object as a prop in other parts of the code and works just fine. Is there something I'm doing wrong?
Code for reference:
import React, { useState, useEffect } from 'react';
import {Paper, Stepper, Step, StepLabel, Typography, CircularProgress, Divider, Button} from '#material-ui/core';
import { commerce } from '../../../lib/commerce';
import Addressform from '../Addressform';
import Paymentform from '../Paymentform';
const steps =['Shipping Address', 'Payment details'];
const Checkout = ({ cart }) => {
const [activeStep, setActiveStep] = useState(0);
const [checkoutToken, setCheckoutToken] = useState(null);
useEffect (() => {
const generateToken = async () => {
console.log(cart.id);
// returns as undefined
try {
const token = await commerce.checkout.generateToken(cart.id, { type: 'cart' });
console.log(token);
setCheckoutToken(token);
console.log("Success!")
} catch (error) {
console.log(error); //Returns 404 Error Obv
console.log("Didnt work")
}
}
generateToken();
}, []);
const Confirmation = () => (
<>
Confirmation
</>
);
const Form = () => activeStep === 0
? <Addressform />
: < Paymentform />
return(
<>
...
</>
);
};
export default Checkout;
I'm currently using Redux, Redux Thunk with NextJS and been trying to figure out how to access the updated redux state inside a function of a functional component.
As you can see in my code below, in the handleSubmit function, I want to update the redux state and then check the state value and decided which route it should take the user to.
Previously in my old project, using mapStateToProps with a Class component, I was able to access the updated redux state inside my handleSubmit function however when using a functional component both options (useSelector hook or mapStateToProps with connect()) doesn't seem to work.
At first I thought the component wasn't re-rendering however when checking the state in useEffect(), I can see that the state is getting updated and the component is able to view the updated values.
Is there something I'm clearly missing or is this way not possible with functional components?
loginPage.tsx
import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ApplicationState } from 'redux/store';
const LoginPage = () => {
const router = useRouter();
const dispatch = useDispatch();
const { auth } = useSelector((state: ApplicationState) => ({ auth: state.auth }));
const handleSubmit = async (values: FormData) => {
if (values && values.username && values.password) {
try {
// Updates redux store
await storeAuthToken(dispatch, values.username, values.password);
} catch (error) {
if (error === StatusCodes.BadRequest) {
return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
} else {
return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
}
}
// Can't see updated values
console.log('Auth: ', auth);
if (auth.parsedJwt && auth.parsedJwt.changePassword) {
router.push({ pathname: '/update-password' });
return;
}
router.push('/dashboard');
}
};
useEffect(() => {
// Can see the updated values
console.log('New Auth: ', auth);
}, [auth]);
return (
<Layout hideProfileMenu title="Login">
<LoginForm onSubmit={handleSubmit} />
</Layout>
);
};
export default LoginPage;
I've attached the store and reducer incase I've set it up wrong.
store.tsx
import Auth from 'lib/interfaces/auth';
import { Context, createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper';
import { AnyAction, applyMiddleware, CombinedState, combineReducers, createStore, Dispatch, Reducer } from 'redux';
import thunkMiddleware, { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk';
import authReducer from './auth/reducer';
export interface ApplicationState {
auth: Auth
}
const isDebug = process.env.NODE_ENV !== 'production';
const bindMiddleware = (middleware: ThunkMiddleware) => {
if (isDebug) {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(middleware));
}
return applyMiddleware(middleware);
};
const combinedReducer: Reducer<ApplicationState> = combineReducers<ApplicationState>({
auth: authReducer
});
const reducer = (state: ApplicationState, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: CombinedState<ApplicationState> = {
...state,
...action.payload
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
const makeStore: MakeStore<ApplicationState> = (_context: Context) => createStore(reducer as Reducer<ApplicationState, AnyAction>, bindMiddleware(thunkMiddleware));
export const wrapper = createWrapper<ApplicationState>(makeStore, { debug: isDebug });
reducer.tsx
import Auth from 'lib/interfaces/auth';
import { Reducer } from 'redux';
import { ActionTypes, AuthAction } from './actions';
const reducer: Reducer<Auth, AuthAction> = (state: Auth = {} as Auth, action: AuthAction): Auth => {
switch (action.type) {
case ActionTypes.UpdateToken:
return Object.assign({}, state, { token: action.token });
case ActionTypes.UpdateRefreshToken:
return Object.assign({}, state, { refreshToken: action.refreshToken });
case ActionTypes.UpdateParsedJwt:
return Object.assign({}, state, { parsedJwt: action.parsedJwt });
case ActionTypes.UpdateUuid:
return Object.assign({}, state, { uuid: action.uuid });
default:
return state;
}
};
export default reducer;
I think the simplest solution is to return the auth object from 'storeAuthToken' as it keeps the error handling and result in the same logical flow, and it fixes the asynchronous issue identified in the comments.
import LoginForm, { FormData } from 'components/Forms/LoginForm';
import Layout from 'components/Layout';
import { FORM_ERROR } from 'final-form';
import StatusCodes from 'lib/enums/statusCodes';
import { storeAuthToken } from 'lib/helpers/auth';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { ApplicationState } from 'redux/store';
const LoginPage = () => {
const router = useRouter();
const dispatch = useDispatch();
const handleSubmit = async (values: FormData) => {
if (values && values.username && values.password) {
try {
// Updates redux store
const authResult = await storeAuthToken(dispatch, values.username, values.password);
if (authResult.parsedJwt && authResult.parsedJwt.changePassword) {
router.push({ pathname: '/update-password' });
return;
}
} catch (error) {
if (error === StatusCodes.BadRequest) {
return { [FORM_ERROR]: 'Sorry, you have entered incorrect details. Please try again' };
} else {
return { [FORM_ERROR]: 'Sorry, there was an issue trying to log you in' };
}
}
router.push('/dashboard');
}
};
return (
<Layout hideProfileMenu title="Login">
<LoginForm onSubmit={handleSubmit} />
</Layout>
);
};
export default LoginPage;
Very simple app, I'm trying to display content from my API using Mobx and Axios, here's my Axios agent.ts:
import { ITutorialUnit } from './../model/unit';
import axios, { AxiosResponse } from "axios";
//set the base URL
axios.defaults.baseURL = "http://localhost:5000/api";
//store our request in a const
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
get: (url: string) => axios.get(url).then(responseBody),
};
//create a const for our activty's feature,all our activities' request are go inside our Activities object
const TutorialUnits = {
list: ():Promise<ITutorialUnit[]> => requests.get("/tutorialunits"),
};
export default{
TutorialUnits
}
then I call this agent.s in a store:
import { ITutorialUnit } from "./../model/unit";
import { action, observable } from "mobx";
import { createContext } from "react";
import agent from "../api/agent";
class UnitStore {
#observable units: ITutorialUnit[] = [];
//observable for loading indicator
#observable loadingInitial = false;
#action loadUnits = async () => {
//start the loading indicator
this.loadingInitial = true;
try {
//we use await to block anything block anything below list() method
const units = await agent.TutorialUnits.list();
units.forEach((unit) => {
this.units.push(unit);
// console.log(units);
});
this.loadingInitial = false;
} catch (error) {
console.log(error);
this.loadingInitial = false;
}
};
}
export default createContext(new UnitStore());
then I call this in my App component:
import React, { Fragment, useContext, useEffect } from "react";
import { Container } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import NavBar from "../../features/nav/NavBar";
import { ActivityDashboard } from "../../features/Units/dashboard/tutorialUnitDashboard";
import UnitStore from "../stores/unitStore";
import { observer } from "mobx-react-lite";
import { LoadingComponent } from "./LoadingComponent";
const App = () => {
const unitStore = useContext(UnitStore);
useEffect(() => {
unitStore.loadUnits();
//need to specify the dependencies in dependenciy array below
}, [unitStore]);
//we are also observing loading initial below
if (unitStore.loadingInitial) {
return <LoadingComponent content="Loading contents..." />;
}
return (
<Fragment>
<NavBar />
<Container style={{ marginTop: "7em" }}>
<ActivityDashboard />
</Container>
</Fragment>
);
};
export default observer(App);
Finally, I want to use this component to display my content:
import { observer } from "mobx-react-lite";
import React, { Fragment, useContext } from "react";
import { Button, Item, Label, Segment } from "semantic-ui-react";
import UnitStore from "../../../app/stores/unitStore";
const UnitList: React.FC = () => {
const unitStore = useContext(UnitStore);
const { units } = unitStore;
console.log(units)
return (
<Fragment>
{units.map((unit) => (
<h2>{unit.content}</h2>
))}
</Fragment>
);
};
export default observer(UnitList);
I can't see the units..
Where's the problem? My API is working, I tested with Postman.
Thanks!!
If you were using MobX 6 then you now need to use makeObservable method inside constructor to achieve same functionality with decorators as before:
class UnitStore {
#observable units: ITutorialUnit[] = [];
#observable loadingInitial = false;
constructor() {
// Just call it here
makeObservable(this);
}
// other code
}
Although there is new thing that will probably allow you to drop decorators altogether, makeAutoObservable:
class UnitStore {
// Don't need decorators now anywhere
units: ITutorialUnit[] = [];
loadingInitial = false;
constructor() {
// Just call it here
makeAutoObservable(this);
}
// other code
}
More info here: https://mobx.js.org/react-integration.html
the problem seems to be the version, I downgraded my Mobx to 5.10.1 and my mobx-react-lite to 1.4.1 then Boom everything's fine now.