React context isn't available from function inside component - javascript

Hello I have a problem with React Context. I'm using React-DnD and I'm trying to get context in feed function but it's empty. In this function I'm getting context default value but outside that function I'm getting correct values and I don't know why.
const Dog = () => {
const needsCtx = useContext(NeedsContext);
const invCtx = useContext(InventoryContext);
console.log(invCtx);
const feed = () => {
console.log(invCtx);
};
needsCtx.saveCurrentContextToDatabase();
const [{ isOver }, drop] = useDrop(
() => ({
accept: "food",
drop: () => feed(),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}),
[]
);
return (
<div className={styles["wrapper-dog"]}>
<img ref={drop} className={styles.dog} alt="dog" src={dog}></img>
</div>
);
};
context:
import React, { useState } from "react";
export const InventoryContext = React.createContext({
items: {},
setItems: () => {},
});
const InventoryContextProvider = (props) => {
const [items, setItems] = useState({});
return (
<InventoryContext.Provider value={{ items: items, setItems: setItems }}>
{props.children}
</InventoryContext.Provider>
);
};
export default InventoryContextProvider;

I'm going to make suggestions here, and continue to edit this answer.
<InventoryContext.Provider value={{ items: items, setItems: setItems }}>
This line confuses me most. Mostly because, you're using state inside of the Provider, so why do you also need to declare items and setItems inside the context itself?
Since you're using the state inside the Provider, couldn't you remove the items and setItems inside the context itself, and then just pass as:
<InvetoryContext.Provider value={{ items, setItems }}>

Related

Rerendering component and useReducer initialization

EDIT: below the line is the initial problem where I asked if my whole architecture was fine. I later edited the topic (and title) to go straight to what was my issue in order to help the community. Therefore, you might find what you want by jumping directly to the answer while skipping the main post
I am new to react and I am encountering issues back to back. I suspect that something is wrong in my pattern and would really love to have it criticized by someone a bit stronger.
Component A:
It renders (among others) a Component B
Component A has a useState hook on an array of C_obj it renders, called C_obj_arr
const [C_obj_arr, ASetter] = useState<C_obj[]>()
It provides this C_obj_arr and ASetter to Component B as properties to allow the data to go back up.
Component B
It renders each C_obj of the C_obj_arr in a list of Component C.
It has a useReducer hook that controls the states of C_obj_arr
const [C_obj_array, dispatch] = useReducer(reducer, props.C_obj_array);
It has a useEffect hook such that if C_obj_arr changes, data goes back up to Compoennt A
useEffect(() => {
ASetter(C_obj_array);
}, [C_obj_array, ASetter]);
Question: Is it fine so far to use the prop like this to init the reducer?
it also uses a useCallback hook to wrap a function that will allow getting the data back from Component C
const fn_callback = useCallback(
(c: C_obj) =>
dispatch({
kind: Kind.AN_ACTION,
payload: { wells_plan: c },
}),
[]
);
Component C
It has another useReducer that controls the states of C_obj
const [C_obj, dispatch] = useReducer(reducer, props.C_obj);
To send the information back up to Component B, it uses the function fn_callback, created in B thanks to a useEffect hook with dep on C_obj
useEffect(() => {
props.fn_callback(C_obj);
}, [C_obj, props.fn_callback]);
I hope it is not a total brain schmuck to read, I am very new to all of that so I understand I can be doing something totally broken by design.
Many thanks for help
EDIT: as requested, here is a block of code to synthetize
const A = (): JSX.Element => {
const [C_obj_arr, ASetter] = useState<C_obj[]>();
return (
<>
<B>C_obj_arr=C_obj_arr ASetter=ASetter</B>
</>
);
};
const B = (C_obj_arr_init: C_obj[], ASetter: () => void): JSX.Element => {
const [C_obj_array, dispatch] = useReducer(reducer, C_obj_arr_init);
useEffect(() => {
ASetter(C_obj_array);
}, [C_obj_array, ASetter]);
const fn_callback = useCallback(
(c_obj: C_obj) =>
dispatch({
kind: Kind.UPDATE_OBJ,
payload: { wells_plan: c_obj },
}),
[]
);
return C_obj_array.map(C_obj => (
<C C_obj={C_obj} fn_callback={fn_callback}></C>
));
};
const C = (C_obj_init, fn_callback): JSX.Element => {
const [C_obj, dispatch] = useReducer(reducer, C_obj_init);
useEffect(() => {
fn_callback(C_obj);
}, [C_obj, fn_callback]);
return <div>{C.toString()}</div>;
};
I assume, that you mean
import { useState, useEffect, useReducer, useCallback } from "react"
type SomeObj = {
name: string
key: string
}
const A = (): JSX.Element => {
const [items, setitems] = useState<SomeObj[]>([
{
key: "a",
name: "hello"
}
])
return (
<>
<B items={items} setitems={setitems} />
</>
)
}
const B = ({ items, setitems }: { items: SomeObj[]; setitems: (x: SomeObj[]) => void }): JSX.Element => {
const [items2, dispatch] = useReducer(
(
x: SomeObj[],
a: {
kind: string
payload: {}
}
) => {
return x
},
items
)
useEffect(() => {
setitems(items2)
}, [items2, setitems])
const fn_callback = useCallback(
(item: SomeObj) =>
dispatch({
kind: "update",
payload: { wells_plan: item },
}),
[]
)
return (
<div>
{items2.map((item) => (
<C itemInit={item} fn_callback={fn_callback} key={item.key}></C>
))}
</div>
)
}
const C = ({ itemInit, fn_callback }: { itemInit: SomeObj; fn_callback: (c_obj: SomeObj) => void }): JSX.Element => {
const [item, dispatch] = useReducer((x: SomeObj) => x, itemInit)
useEffect(() => {
fn_callback(item)
}, [item, fn_callback])
return <div>{item.name}</div>
}
function App() {
return (
<main>
<A></A>
</main>
)
}
export default App
And, I think, it's basically the reason for using global/atom state managers to react like: redux, recoil, jotai, etc.
In your scenario, you have a couple of prop passing layers through components, and only if I understand correctly, you try to take up changes from deeply nested component.
Using global state, you can have only one array and source of truth, and only one handler in each child component. That probably will remove all unnecessary hooks
The missing bolt for this thing to work was to handle the rerendering of component C. The problem was that useReducer initial state is applied only once, then not when the component is rendered. Therefore, there is a need to force the update of the obj_c when re-rendering it. To do that, I added an extra dispatch action in the Component C body.
useEffect(() => {
dispatch({
kind: Kind.UPDATE,
payload: {
obj_C: C_obj_init,
},
});
}, [C_obj_init]);
which associated case in the reducer is
switch (action.kind) {
case Kind.UPDATE:
return action.payload.obj_C;
An important thing point here is to not return a recreated obj_C, this wouldlead to an infinite loop to this other hook seen earlier:
useEffect(() => {
fn_callback(C_obj);
}, [C_obj, fn_callback]);

How can I render only once an Alert component in react native?

How can I render only one time an Alert component in react native if my dashboardScreen is being render many times?
I'm using Context api, if I dispatch an action it causes another render, if I use a useState it causes another render/renders, any idea will be highly appreciate it.
**AppContext.js**
export const AppContext = createContext();
export const AppProvider = ({ children })=> {
const [ state, dispatch ] = useReducer( appReducer, appInicialState);
return (
<AppProvider.Provider value={{
...state,
}}>
{ children }
</AppProvider.Provider>
)
}
**Dashboard.js**
export const Dashboard = () => {
const { state, dispatch } = useContext( AppContext );
const [showAlert, setShowAlert] = useState(true);
const handleCancelPressed = () => {
dispatch({
type: 'cancelPressed',
payload: {
userInfo: false,
}
});
}
const handleAcceptPressed = () => {
dispatch({
type: 'acceptPressed',
payload: {
userInfo: true,
}
});
}
return (
<View style={styles.container}>
{showAlert && (
Alert.alert(
"Alert Title",
"My Alert Msg",
[
{
text: "Cancel",
onPress: handleCancelPressed,
},
{
text: "Ok",
onPress: handleAcceptPressed ,
},
],
{
cancelable: true,
}
);
)}
</View>
)}
useEffect is the way which can solve your problem for rendering it only once.
const [showAlert, setShowAlert] = useState(null);
useEffect(()=>{setShowAlert(true)},[])
The empty bracket denotes that, value for 'showAlert' will be set only once.
You should read useEffect in detail for implementing this properly.
I am not trying this example in any coding environment, I have only shared a proof of concept for you to work upon.
Hope this helps,
Regards,
Shameel Uddin

OnClick toggles all items

I want to toggle each of the item that I clicked on but Its keeps toggling all the Items. Using the useContext api
import React, { useState, useEffect } from "react";
const MyContext = React.createContext({
addToFavorites: () => {},
likeHandler: () => {},
fetchRequest: () => {},
});
export const MyContextProvider = (props) => {
const [favorites, setfavorites] = useState([]);
const [toggle, setToggle] = useState(false);
const [items, setItems] = useState([]);
const fetchRequest = async () => {
const api_Key = "oLfD9P45t23L5bwYmF2sib88WW5yZ8Xd7mkmhGSy";
const response = await fetch(
`https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?
sol=50&api_key=${api_Key}`
);
const data = await response.json();
const allItems = data.photos.map((item) => {
return {
id: item.id,
title: item.camera.full_name,
img: item.img_src,
date: item.rover.launch_date,
like: false,
};
});
setItems(allItems);
};
const likeHandler = (item) => {
const found = items.find((x) => x.id === item.id);
setToggle((found.like = !found.like));
console.log(found); //this logs the particular item that is clicked on
};
return (
<MyContext.Provider
value={{
likeHandler,
fetchRequest,
toggleLike: toggle,
data: items,
}}
>
{props.children}
</MyContext.Provider>
);
};
export default MyContext;
I also have a NasaCard component where I call the likeHandler function and the toggle state, onClick of the FavoriteIcon from my context.And I pass in the toggle state to a liked props in my styled component to set the color of the favorite Icon
import {
Container,
Image,
Name,
InnerContainer,
Titlecontainer,
Date,
FavouriteContainer,
FavouriteIcon,
ImageContainer,
SocialContainer,
} from "./index";
import MyContext from "../../Context/store";
import React, { useState, useEffect, useContext } from "react";
const NasaCards = (props) => {
const { likeHandler, toggleLike } = useContext(MyContext);
return (
<Container>
<InnerContainer>
<ImageContainer>
<Image src={props.Image} alt="" />
</ImageContainer>
<Titlecontainer>
<Name>{props.title}</Name>
<Date>{props.date}</Date>
<FavouriteContainer>
<FavouriteIcon
liked={toggleLike}
onClick={() => {
likeHandler({
id: props.id,
title: props.title,
Image: props.Image,
});
}}
/>
</InnerContainer>
</Container>
);
};
export default NasaCards;
I think you're making this a little more complicated than it needs to be. For starters you are using a single boolean toggle state for all the context consumers, and then I think you're mixing your like property on the items state array with the toggle state.
The items array objects have a like property, so you can simply toggle that in the context, and then also use that property when mapping that array.
MyContextProvider - Map the items state to a new array, updating the like property of the matching item.
const likeHandler = (item) => {
setItems(items => items.map(
el => el.id === item.id
? { ...el, like: !el.like }
: el
));
console.log(item); // this logs the particular item that is clicked on
};
NasaCards - Use item.like property for the liked prop on FavouriteIcon and pass the entire props object to the likeHandler callback.
const NasaCards = (props) => {
const { likeHandler } = useContext(MyContext);
return (
<Container>
<InnerContainer>
<ImageContainer>
<Image src={props.Image} alt="" />
</ImageContainer>
<Titlecontainer>
<Name>{props.title}</Name>
<Date>{props.date}</Date>
<FavouriteContainer>
<FavouriteIcon
liked={props.like} // <-- use like property
onClick={() => {
likeHandler(props); // <-- props has id property
}}
/>
</FavouriteContainer>
</Titlecontainer?
</InnerContainer>
</Container>
);
};

How to "wait" for a React context provider that takes some time to build the value provided?

I have a global context in a Next.js application, which takes five seconds compute the value provided:
import React, { useContext, useEffect, useState } from 'react';
const GlobalContext = React.createContext();
export const GlobalContextProvider = ({ children }) => {
const [state, setState] = useState();
useEffect(() => {
setTimeout(() => setState(state => ({ ...state, foo: 'bar' })), 5000);
}, []);
return (
<GlobalContext.Provider value={state}>
{children}
</GlobalContext.Provider>
);
};
export const useGlobalContext = () =>
useContext(GlobalContext);
I use the above <GlobalContextProvider> component in my custom _app.js (code omitted for brevity). In my homepage, I use the value provided:
import { useGlobalContext } from '../context/global';
export default function Home() {
const context = useGlobalContext();
return (
<div>
{context.foo}
</div>
);
}
How can I "wait" the value provided, showing a "loading indicator"?
Maybe I can const [isLoading, setIsLoading] = useState(true) inside the provider and setIsLoading(true) when data is ready, but I'm confused if it's the right way to do it (or use something like React.lazy and Suspense).
There's a couple of ways you can do this. You can add a loading param to your state that defaults to true.
const { state, setState } = useState({ foo: undefined, loading: true });
Then when you set the foo value also set loading to false. It'll only get set once foo is finished being set.
useEffect(() => {
setTimeout(() => setState(state => ({ ...state, foo: 'bar', loading: false })), 5000);
}, []);
Then inside your render component you pick out both values:
const { foo, loading } = useGlobalContext();
return (
<div>
{loading ? <LoadingIcon \> : foo}
</div>
);
Or you could just check whether foo has been set to anything yet:
const { foo } = useGlobalContext();
return (
<div>
{foo || <LoadingIcon \>}
</div>
);
The first way is probably better.

React context api returning undefined when setting the state

I've recently started learning react and i'm using the context api to store my global state.
Here in MyProvider.js file i define my provider and its simply stores 2 arrays of json obj
import {MyContext} from "./MyContext";
import React, {useState} from "react";
export const MyProvider = (props) => {
const intialState = {
featuredListings: [],
setFeaturedListings: (val) => setState({...state, featuredListings: val}),
featuredVendors: [],
setFeaturedVendors: (val) => setState({...state, featuredVendors: val})
}
const [state, setState] = useState(intialState)
return (
<MyContext.Provider
value={state}
>
{props.children}
</MyContext.Provider>
);
}
I'm wrapping all my components in my App.js in the Provider by doing this , side not using ReachRouter to handle routing,
<MyProvider>
<div className="content">
<Header/>
<Router>
<Home path="/"/>
</Router>
</div>
<Footer />
</MyProvider>
In my Home.js file I make a network call in the useEffect hook which successfully returns the json which i expect and with that json response i update the state of the context so that it can be visible globally.
My code for that is
export const Home = () => {
let state = useContext(MyContext)
async function apiCalls() {
const featuredVendors = await getFeaturedVendors()
console.log("featuredVendors - ", featuredVendors) // the correct response is returned
state.setFeaturedVendors(featuredVendors)
const featuredListings = await getFeaturedListing()
console.log("featuredListings - ", featuredListings) // the correct response is returned
state.setFeaturedListings(featuredListings)
}
useEffect(() => {
apiCalls()
}, []);
return (
<div>
{console.log(state.featuredVendors)} // empty array
{console.log(state.featuredListings)} // contains correct values
</div>
)
}
]
To remove any ambiguity my Context is created in a separate file which is Called MyContext.js
and I create the Context like so
export const MyContext = React.createContext()
Why is the state.featuredVendors not updating when I set it?
Also another strange thing i noticed is if I rearrange the orders of the calls , i.e call the
getFeaturedListing first followed by the getFeaturedVendors then my state only updates for featuredVendors and featuredListings will be an empty array.
When you call useState the initialValue is only set once. When your MyProvider is first mounted, the state is initialised with your setFeaturedListings and setFeaturedVendors methods but these are not updated whenever the value of state changes. Therefore the value of state when you spread the values will always be its initial value.
setState can also be called with a function that always receives the current value as an argument, so you could rewrite these methods to spread that value like so:
const intialState = {
featuredListings: [],
setFeaturedListings: (val) => setState(state => ({...state, featuredListings: val})),
featuredVendors: [],
setFeaturedVendors: (val) => setState(state => ({...state, featuredVendors: val}))
}
Or, alternatively, you could move these functions outside of your state altogether.
export const MyProvider = (props) => {
const intialState = {
featuredListings: [],
featuredVendors: [],
}
const [state, setState] = useState(intialState)
return (
<MyContext.Provider
value={{
...state,
setFeaturedListings: (val) => setState({...state, featuredListings: val}),
setFeaturedVendors: (val) => setState({...state, featuredVendors: val})
}}
>
{props.children}
</MyContext.Provider>
);
}

Categories

Resources