Cant use values from (fetched) context(provider) as input to my useState - reactjs

Short version:
I have a useEffect in a ContextProvider where I fetch some data from server. In my component I
use this Context and want to init another useState()-hook with the "incoming" data. The value in
[value, setValue] = useState(dataFromContext) does not get set/inited with dataFromContext. Why, oh why?
The very long version:
Im trying to follow (and expand on) this example of how to use context/useContext-hook in react
So, I have an AppContext and a ContextProvider, I wrap my in in index.js, everything in the example works nice.
Now I want to load data in useEffect inside my ContextProvider, so my context provider now looks like this
const AppContextProvider = ({ children }) => {
const profileInfoUrl = BASE_URL + `users/GetCurrentUsersProfileInfo/`
const {loading, getTokenSilently} = useAuth0();
const [profileInfo, setProfileInfo] = useState( {})
useEffect(() => {
if (!loading && profileInfo.user.username === '' ) {
Communication.get(profileInfoUrl, getTokenSilently, setProfileInfo);
}
},[loading, getTokenSilently])
const context = {
profileInfo,
setProfileInfo
};
return (
<AppContext.Provider value={ context }>
{children}
</AppContext.Provider>
);
}
Now, in my component, I want to display this data. One of them is users selected categories, a list of an id and isSelected, which should feed a checkboxlist.
const TsProfileSettingsPage = ( ) => {
//..
const { profileInfo, setProfileInfo } = useContext(AppContext);
const userCategories = Object.assign({}, ...profileInfo.userDetails.categories.map((c) => ({[c.id]: c})));
const [checkedCategories, setCheckedCategories] = useState(userCategories);
console.log("profileInfo.userDetails.categories : " + JSON.stringify(profileInfo.userDetails.categories));
console.log("userCategories : " + JSON.stringify(userCategories));//correct dictionary
console.log("checkedCategories : " + JSON.stringify(checkedCategories)); //empty object!
//...rest of code
So I load users selection and converts is to a dictionary that ends up in "userCategories". I use that "userCategories" to init checkedCategories. BUT, from the console.logs below, userCategories is set correctly, but checkedCategories is not, its becomes an empty object!
So my question is. Am I thinking wrong here? (obviously I am), I just want to have an external state.
Everything loads ok, but when I use it to init a useState() in my component it does not get set. I have tested to have another useEffect() in my component, but I just get the feeling that Im doing something more basic error.
Thanks for any help

Related

React Js - On change all select are changing

I'm trying to create a list of select with one option but when changing any one they all changed, i want to handle them one by one. Im using map in order to get the data and display it using useState.
Here is the code below :
const positions = patients.data.map((element) => {
return element.first_name + " " + element.last_name;
});
const [Position, setPosition] = useState(positions[0]);
const [serviceList, setServiceList] = useState([{service:""}]);
const handleServiceAdd = () =>{
setServiceList([...serviceList,{service:""}])
}
const handleServiceRemove = (index) => {
const list = [...serviceList];
list.splice(index,1);
setServiceList(list);
}
If you are using styled components, place them before writing the function that renders your component.
For example, your component name is "Component", your code shouldn't look like:
const Component => {
const YourStyledComponent = styled.div``
return(...)
}
But it should look like:
const YourStyledComponent = styled.div``
const Component => {
return(...)
}
Why ? Because everytime you will change one state, everything inside your component will rerender

Stop Mapbox.js from re-mounting on every state change?

I am running into a particularly painful issue utilising react/redux/mapbox and was hoping to get some advice. I have a container component which houses both a Map component and a destination bar component which shows information about the route. I also need to expose the mapbox instance to the window object when it is available.
The problem I seem to be having is that when I render the Map component, i need to wait for the mapbox.load event, at which point I can then set the map instance to either a useState or useRef for later use in the code. If I set it as state then the map enters into a endless loop where it sets the state, re-loads the map and tries to set the state again. If I set it as a ref, then it wont re-render any of the details on the other component. Also if I were to use the window.map instance at any point it also re-mounts the Map component and starts this whole process off again.
function App() {
const { data, isError } = useConfigEndpointQuery();
const { route, to, from, routingDestinationData, routingOverviewData } =
useAppSelector((state) => state.routing);
const modals = useAppSelector((state) => state.ui.modals);
const mapInstance = useRef<LivingMap>();
const [isUserActive, setIsUserActive] = useState(false);
const [mode, setMode] = useState(ComponentMode.OVERVIEW);
const [isStaticUserLocationChevron, setIsStaticUserLocationChevron] =
useState(false);
const [countdownTimeInSeconds, setCountdownTimeInSeconds] = useState(
INITIAL_COUNTDOWN_TIME / 1000
);
const countdownInterval = useRef<NodeJS.Timer | undefined>();
const userActiveInterval = useRef<NodeJS.Timer | undefined>();
const setChevronInterval = useRef<NodeJS.Timer | undefined>();
const userLocationControl = useRef<UserLocationControl>();
const routingControl = useRef<RoutingControl>();
const geofenceControl = useRef<GeofenceControl>();
const floorControl = useRef<FloorControl>();
return data ? (
<div className={styles.container}>
<TopBar distance={totalLength} time={totalTime} />
<Map
bearing={data.areas[0].view.bearing}
zoom={data.areas[0].view.zoom}
maxZoom={data.areas[0].view.maxZoom}
minZoom={data.areas[0].view.minZoom}
center={data.areas[0].view.center}
extent={data.areas[0].view.extent}
floor={data.floors.find((floor) => !!floor.default)!.universal_id}
floors={data.floors}
mapStyle={`${getUrl(URLTypes.STYLES)}/styles.json`}
accessToken={process.env.REACT_APP_MAPBOX_TOKEN as string}
onMapReady={(map) => {
mapInstance.current = map;
userLocationControl.current = map.getPluginById<UserLocationControl>(
PLUGIN_IDS.USER_LOCATION
);
routingControl.current = map.getPluginById<RoutingControl>(
PLUGIN_IDS.ROUTING
);
geofenceControl.current = map.getPluginById<GeofenceControl>(
PLUGIN_IDS.GEOFENCE
);
floorControl.current = map.getPluginById<FloorControl>(
PLUGIN_IDS.FLOOR
);
}}
/>
</div>
) : isError ? (
<div>Error loading config</div>
) : null;
}
Is there a way by which I can keep a single instance of a mapbox map while also reading/writing data to the redux store and interacting with the map instance without causing the component to re-mount the map component each time?
It appears it was re-rendering needlessly as I was passing a function into the map component without wrapping it in useCallback()

Sorting data from an API (redux-toolkit)

I'm building a crypto app with react and redux-toolkit.
I'm trying to find a way to manage the data from the API. More specifically i want to be able to sort by value, volume, etc. and add an "isFavourite" property for each coin but i think (correct me if i'm wrong) that the only way to do this is by copying the data to another state. What i've tried so far was adding another state where i passed the data like this:
const [list, setList] = useState()
useEffect(() => {
setList(data)
}, [data])
//"const coinData = list?.data?.coins" instead of "const coinData = data?.data?.coins"
but then an error occured because the data on the "list" were "undefined".
The code bellow is the one that is running without any problems. How can i manage the API data? Am i on the right path or is there a more slick way to do what i want? Thank you!
function Main () {
const { data, error, isFetching } = useGetCryptosQuery()
if(isFetching) return 'Loading...'
const globalStats = data?.data?.stats
const coinData = data?.data?.coins
const coinList = coinData.map(coin => {
return (
<Coin
price = {coin.price}
key = {coin.uuid}
id = {coin.uuid}
name = {coin.name}
icon = {coin.iconUrl}
/>)
})
return (
<div>
<h2>Main</h2>
{coinList}
</div>
)
}
export default Main
You are on the right track - I set up something similar and added a check for null trying to map the data, and that avoids the error you probably got.
const coinList = coinData ? coinData.map((coin) => {
///...coin component
}) : <div></div>;
Then, instead of an error for undefined data, it will return an empty div - until the data is there, then it will render ok.

React context state not being updated

I have a basic react context, similar to below:
function IdProvider({ children }: any) {
const [id, setId] = useState("DEFAULT_ID")
return (
<IdContext.Provider value={{ id, setId }}>
{children}
</IdContext.Provider>
)
}
I'm wrapping all of my routes with this provider, and have one component as below which I want to use to update the Id:
function UpdateForm() {
const { id, setId } = useId() // wrapper for the IdContext
const moveToDisplay = (newId: string) => {
setId(newId)
window.location.href = "/display/" + id
}
return (
<>
<span onClick={() => moveToDisplay("NEW_ID")}>
Update and redirect
</span>
</>
)
}
Upon redirecting, this component is used:
function DisplayId(): JSX.Element {
const { id } = useId()
useEffect(() => {
document.title = id
}, [id])
return (
<>
{id}
</>
)
}
The issue is, the initial setId(newId) doesn't update the state, so when window.location.href = "/display/" + id is called it redirects to /display/DEFAULT_ID, and the DisplayId component still uses the DEFAULT_ID value when rendering. From my understanding the useState is asynchronous, so I'm not entirely sure how I should approach this problem. I've tried adding a 5 second timeout after setId is called, but the value of id is still the default value.
EDIT - SOLVED
Figured out the issue and it was fairly unrelated. I was using a single constant (e.g. DEFAULT_ID) to initialise state in various places, without realising that React checks for referential equality when updating / re-rendering.
useContext provides the ability to pass properties through the react chain of components. So change your provider to this & make sure it's exporting
<IdContext.Provider value={id}>
then in your child you can update that by importing the Context:
import IdContext from './IdProvider'
and useContext:
const [context, setContext] = useContext(IdContext)
then set it with the new value:
const moveToDisplay = (newId: string) => {
setContext(newId)
window.location.href = "/display/" + newId
}
After that you import it in the same fashion through your DisplayId and just use the value

How to use useReducer to determine which useState variable to use?

This is probably a basic question but,
I have two useState variables:
const [varOne, setVarOne] = useState(null);
const [varTwo, setVarTwo] = useState(null);
And a third variable that tells me which variables I need to use, varOne or varTwo:
const [whichVar, setWhichVar] = useState(0);
And I have a third variable curVar which will be either varOne or varTwo based on the value of whichVar:
const [curVar, setCurVar] = useState(null);
if (whichVar === 0) {
curVar = varOne;
setCurVar = setVarOne;
} else {
curVar = varTwo;
setCurVar = setVarTwo;
}
I realize this is probably wrong, but in another post I was told I could use useReducer to achieve this, what is the most elegant way to achieve this with useReducer?
Well a simple way would be to use a custom hook that stores all the state/logic and returns the currently active value. For instance:
import React, { useState } from 'react';
const useSwitchValue = () => {
const [varOne, setVarOne] = useState('A');
const [varTwo, setVarTwo] = useState('B');
const [whichVar, setWhichVar] = useState(0);
if (whichVar === 0) return {
value: varOne,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
else return {
value: varTwo,
setVarOne,
setVarTwo,
switchVars: () => setWhichVar(whichVar ? 0 : 1)
};
};
export default function App() {
const { value, switchVars, setVarOne, setVarTwo } = useSwitchValue();
return (
<div>
<button onClick={switchVars}>
SWITCH VALUES
</button>
<div>{value}</div>
</div>
);
}
With this hook, you can change the state values and switch between them, but when you want to use the currently selected value, you just refer to {value} without having to do any conditional checking (cos that's done once, inside the custom hook).
Sandbox
Yes useReducer can help achieve similar functionality. useReducer will emit your current state and a dispatch function based on a given reducer and initial state. You then use this dispatch method to dispatch types of actions that are specified in your reducer function to manipulate your state.
Check out the first example in the React docs, they’re very helpful https://reactjs.org/docs/hooks-reference.html#usereducer

Resources