I am having trouble accessing 'initialReduxState' when setting the hook with 'setReduxStore' in getinitialProps and cant seem to figure out why. Below is a snippet of the code.
export default (App) => {
return function Redux() {
let [reduxStore, setReduxStore] = useState();
let [persistor, setPersistor] = useState();
Redux.getInitialProps = async (appContext) => {
const reduxStore = getOrCreateStore();
// Provide the store to getInitialProps of pages
appContext.ctx.reduxStore = reduxStore;
let appProps = {};
if (App.getInitialProps) {
appProps = await App.getInitialProps(appContext);
}
return {
...appProps,
initialReduxState: reduxStore.getState(),
};
};
setReduxStore(getOrCreateStore(initialReduxState));
setPersistor(persistStore(reduxStore));
return <App {...props} reduxStore={reduxStore} persistor={persistor} />;
};
};
Does anyone have an idea as to why I am getting the following error, 'initialReduxState is not defined'? Thanks!
Related
UPDATE: its working. problem was from useEffect
When I try to 'submit' a specific task, it is shown in the local storage that the status attribute is not changed. I wonder where I am going wrong. The data i am trying to use to update the local storage is initialState.stuff .
Here is my code for reducer.js:
const reducer = (state,action)=>{
if(action.type ==="completed"){
//the relevant attributes to be changed
let temp = state.stuff.map((task)=>{
if(task.id === action.payload){
return {...task,status:status,enddate:enddate}
}
return task;
});
// the rest of the parts of this code is irrelevant
here is my code for context.js:
import React, {useState,useContext,useEffect,useReducer} from "react"
import reducer from "./reducer";
const AppContext = React.createContext()
const initialState = {
stuff: null
}
const AppProvider = ({children}) =>{
const [tasks,setTasks] = useState([])
const [status,changeStatus] = useState("");
const [state,dispatch] = useReducer(reducer,initialState)
const complete = (id) =>{
dispatch({type:"completed",payload:id})
console.log("done", initialState.stuff)
setTasks(initialState.stuff)
}
const deleted = (id) =>{
dispatch({type:"deleted",payload:id})
}
const getLocalStorage = () =>{
state.stuff = localStorage.getItem("tasks")
if(state.stuff){
state.stuff = JSON.parse(state.stuff)
return JSON.parse(localStorage.getItem("tasks"))
}
else{
return []
}
}
const fetchData = async()=>{
setLoading(true)
setTasks(getLocalStorage());
setLoading(false);
}
useEffect(()=>{
fetchData()
},[])
useEffect(()=>{
localStorage.setItem("tasks",JSON.stringify(tasks))
},[tasks])
return <AppContext.Provider
value = {{
loading,
tasks,setTasks
}}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () =>{
return useContext(AppContext)
}
export {AppContext,AppProvider}
I think there might be (I have not read your code completely) variable collision in your code. You can try writing window.localStorage instead of localStorage and check whether it solves your problem.
My goal is to use custom hooks created from Context to pass and modify stored values
The final goal is to use something like useFeedContext() to get or modify the context values
What I am actually getting is either the functions that I call are undefined or some other problem ( I tried multiple approaches)
I tried following this video basics of react context in conjunction with this thread How to change Context value while using React Hook of useContext but I am clearly getting something wrong.
Here is what I tried :
return part of App.js
<FeedProvider mf={/* what do i put here */}>
<Navigation>
<HomeScreen />
<ParsedFeed />
<FavScreen />
</Navigation>
</FeedProvider>
Main provider logic
import React, { useState, useEffect, useContext, useCallback } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
const FeedContext = React.createContext();
const defaultFeed = [];
const getData = async (keyName) => {
try {
const jsonValue = await AsyncStorage.getItem(keyName);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
const storeData = async (value, keyName) => {
console.log(value, keyName);
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(keyName, jsonValue);
} catch (e) {
console.log(e);
}
};
export const FeedProvider = ({ children, mf }) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
const [feedLoaded, setFeedLoaded] = useState(false);
let load = async () => {
let temp = await AsyncStorage.getItem("mainUserFeed");
temp != null
? getData("mainUserFeed").then((loadedFeed) => setMainFeed(loadedFeed))
: setMainFeed(defaultFeed);
setFeedLoaded(true);
};
useEffect(() => {
load();
}, []);
useCallback(async () => {
if (!feedLoaded) {
return await load();
}
}, [mainFeed]);
const setFeed = (obj) => {
setMainFeed(obj);
storeData(mainFeed, "mainUserFeed");
};
return (
<FeedContext.Provider value={{ getFeed: mainFeed, setFeed }}>
{children}
</FeedContext.Provider>
);
};
//export const FeedConsumer = FeedContext.Consumer;
export default FeedContext;
The custom hook
import { useContext } from "react";
import FeedContext from "./feedProviderContext";
export default function useFeedContext() {
const context = useContext(FeedContext);
return context;
}
What I would hope for is the ability to call the useFeedContext hook anywhere in the app after import like:
let myhook = useFeedContext()
console.log(myhook.getFeed) /// returns the context of the mainFeed from the provider
myhook.setFeed([{test:1},{test:2}]) /// would update the mainFeed from the provider so that mainFeed is set to the passed array with two objects.
I hope this all makes sense, I have spend way longer that I am comfortable to admit so any help is much appreciated.
If you want to keep using your useFeedContext function, I suggest to move it into the your 'Provider Logic' or I'd call it as 'FeedContext.tsx'
FeedContext.tsx
const FeedContext = createContext({});
export const useFeedContext = () => {
return useContext(FeedContext);
}
export const AuthProvider = ({children}) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
...
return (
<FeedContext.Provider value={{mainFeed, setMainFeed}}>
{children}
</FeedContext.Provider>
);
};
YourScreen.tsx
const YourScreen = () => {
const {mainFeed, setMainFeed} = useFeedContext();
useEffect(() => {
// You have to wait until mainFeed is defined, because it's asynchronous.
if (!mainFeed || !mainFeed.length) {
return;
}
// Do something here
...
}, [mainFeed]);
...
return (
...
);
};
export default YourScreen;
I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};
I am trying to do a withCache HoC component but having some problems...
Thats the HoC:
// HOC for cached images
const withCache = (Component) => {
const Wrapped = (props) => {
console.log(props);
const [uri, setUri] = useState(null);
useEffect(() => {
(async () => {
const { uri } = props;
const name = shorthash.unique(uri);
const path = `${FileSystem.cacheDirectory}${name}`;
const image = await FileSystem.getInfoAsync(path);
if (image.exists) {
console.log("Read image from cache");
setUri(image.uri);
return;
} else {
console.log("Downloading image to cache");
const newImage = await FileSystem.downloadAsync(uri, path);
setUri(newImage.uri);
}
})();
}, []);
return <Component {...props} uri={uri} />;
};
Wrapped.propTypes = Component.propTypes;
return Wrapped;
};
export default withCache;
The thing is that "Component" is a custom Image component with specific propTypes and defaultProps.
How do I use this component? I have tried:
const CachedImage = withCache(<MyCustomImage uri={"https://..."} height={100} ripple />)
...
return (<CachedImage />)
but not working :( What I want is to pass a boolean prop to my custom image component named "cached", and if true return the custom image component wrapped in the HOC
In order to use the HOC, you would create the instance outside of the functional component like
const CachedImage = withCache(MyCustomImage)
and use it like
const MyComp = () => {
...
return (<CachedImage uri={"https://..."} height={100} ripple />)
}
Final implementation of the HoC, in case someone finds it useful in the future.
import React, { useState, useEffect } from "react";
import shorthash from "shorthash";
import * as FileSystem from "expo-file-system";
// HOC for cached images
const withCache = (Component) => {
const Wrapped = (props) => {
console.log(props);
const [uri, setUri] = useState(null);
useEffect(() => {
(async () => {
const { uri } = props;
const name = shorthash.unique(uri);
const path = `${FileSystem.cacheDirectory}${name}`;
const image = await FileSystem.getInfoAsync(path);
if (image.exists) {
console.log("Read image from cache");
setUri(image.uri);
return;
} else {
console.log("Downloading image to cache");
const newImage = await FileSystem.downloadAsync(uri, path);
setUri(newImage.uri);
}
})();
}, []);
// Needs to have the final uri before render the image
return uri && <Component {...props} uri={uri} />;
};
Wrapped.propTypes = Component.propTypes;
return Wrapped;
};
export default withCache;
I updated react-navigation for a react-native project from v2.x to v3.x. For the v2.x I had this rendered at root:
const AppNavigator = createStackNavigator({...})
const App = () => <AppNavigator persistenceKey={"NavigationState"} />;
export default App;
I need to persist the state, thats why I used persistenceKey
For the v3.x of react-navigation an app container is required, but I'm having problems figuring out how to implement the same state persistence.
This is my new code with the v3.x
const AppNavigator = createStackNavigator({...})
const AppContainer = createAppContainer(AppNavigator)
const App = () => <AppContainer />;
export default App;
How do I persist the state this way?
Thanks
EDIT:
I've tried this:
const AppNavigator = createStackNavigator({...})
const persistenceKey = "persistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState))
} catch(err) {
// handle the error according to your needs
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey)
return JSON.parse(jsonString)
}
const AppNavigationPersists = () => <AppNavigator
persistNavigationState={persistNavigationState}
loadNavigationState={loadNavigationState}
/>
const AppContainer = createAppContainer(AppNavigationPersists)
export default AppContainer;
but I get this error:
Cannot read property 'getStateForAction' of undefined
You may need to update react-navigation to >= 3.10.0.
Per the react-navigation changelog, they only now only support persistNavigationState and loadNavigationState on react-navigation#^3.10.
You can still use persistenceKey on versions lower than 3.10.
---EDIT---
An example of a version <3.10.0:
const AppNavigator = createStackNavigator({...})
const App = () => <AppNavigator persistenceKey={"NavigationState"} />;
export default App;
An example implementation for a version >= 3.10.0:
const AppNavigator = createStackNavigator({...});
const persistenceKey = "persistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState))
} catch(err) {
// handle the error according to your needs
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey)
return JSON.parse(jsonString)
}
const App = () => <AppNavigator persistNavigationState={persistNavigationState} loadNavigationState={loadNavigationState} />;
You can check the docs example for v3.x.
const AppNavigator = createStackNavigator({...});
const persistenceKey = "persistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState))
} catch(err) {
// handle the error according to your needs
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey)
return JSON.parse(jsonString)
}
const App = () => <AppNavigator persistNavigationState={persistNavigationState} loadNavigationState={loadNavigationState} />;
To solve the problem in const AppContainer = createAppContainer(AppNavigator) what you can do is create another component that return AppNavigator with the persistence.
const AppNavigationPersists = () => <AppNavigator
persistNavigationState={persistNavigationState}
loadNavigationState={loadNavigationState}
/>
const AppContainer = createAppContainer(AppNavigationPersists)
Just implement it the same way as in the example. In my case I forgot to update react-navigation to version ^3.11.0 and I also forgot to import AsyncStorage. Somehow react native wasn't complaining about AsyncStorage not being there. This is why the state persistence didn't seem to work.
import {
AsyncStorage
} from "react-native";
const AppNavigator = createStackNavigator({...});
const AppContainer = createAppContainer(AppNavigator);
const persistenceKey = "persistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState))
} catch(err) {
// handle the error according to your needs
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey)
return JSON.parse(jsonString)
}
const App = () => <AppContainer persistNavigationState={persistNavigationState} loadNavigationState={loadNavigationState} renderLoadingExperimental={() => <ActivityIndicator />}/>;
The doc is not clear but you actually need to pass the props persistNavigationState and loadNavigationState directly to the AppContainer component:
<AppContainer
persistNavigationState={persistNavigationState}
loadNavigationState={loadNavigationState}
/>
If you are using createAppContainer with react navigation 4, here is the solution which worked for me.
const App: () => React$Node = () => {
const persistenceKey = "persistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState))
} catch(err) {
// handle error
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey)
return JSON.parse(jsonString)
}
return(
<View style={{flex: 1, backgroundColor: '#000000'}}>
<AppContainer
persistNavigationState={persistNavigationState}
loadNavigationState={loadNavigationState}
/>
</View>
);
};