I'm working with an app that was handed to us that is fairly big, and im very new to React Native.
I'm not sure that resetting will solve it. But I think I somehow polluted my state. Everything worked fine but then I wanted to reset my state, so I found this in app.js
componentWillMount() {
let persistore = persistStore(store, null, () => {
this.setState({rehydrated: true})
});
// persistore.purge();
}
So I uncommented persistore.purge();. But now when I try to start it I get:
Unhandled promise rejection: TypeError: null is not an object (evaluating 'instanceInfo.pcb')
And it wont stop stageing. Any ideas on what I can try to do. To "reset" what I have done? I have already reseted to a stable commit in git.
I don't understand your question resetting state means the whole store i.e. app state?
If so then
const appReducer = combineReducers({
nav,
listReducer,
movieDetail
});
const initialState = appReducer({}, {});
const rootReducer = (state, action) => {
if(action.type == "RESET_APP_STATE") {
state = undefined
}
return appReducer(state, action);
}
If you want particular state i.e reducer state same thing but only for that particular reducer in switch mostly what we use, return undefined
Related
I have an expo app that loads a provider:
export const AppProvider = ({ children }: { children: ReactNode }) => {
console.log('Hello from app provider!!');
const alertsBottomSheetRef = useRef<BottomSheetModal>(null);
const dismissAlert = useCallback(() => {
alertsBottomSheetRef.current?.close();
}, []);
const values = {
alertsBottomSheetRef,
dismissAlert,
};
// >>>>> return <AppContext.Provider value={values}>{children}</AppContext.Provider>;
};
If I load the app with the last line commented, I can see the console.log. I can also see any changes I made to the console.log.
However, When I uncomment that last line, I don't get any console.logs.
Any thoughts why?
What I think why its not working because the AppContext.Provider is a context provider, and it does not log anything to the console. The purpose of a context provider is to provide data to components that are descendants of the provider. It does not render anything to the screen and does not log anything to the console.
The issue was that I started the server with expo start and not expo start --dev-client. console logs now appear as expected.
I've been banging my head against this, hopefully getting another pair of eyes on it will help.
I'm using Redux + Redux Toolkit in a React Native App, in a pretty simple way. I can tell (through a log statement) that my action is being called and the state is getting set, but my useSelector on the state never updates. I've tried it with shallowEqual as well, but that shouldn't be needed, since Redux Toolkit uses Immer and the object shouldn't pass an equality check after updating (most of the other similar issues I researched were due to that)
Here's my main slice, followed by all the related code. Pardon the code dump, but I want to give a full picture:
export interface Metadata {
title: string
author: string
firstLines: string
id: string
}
type MetadataState = Record<string, Metadata>
export const metadataSlice = createSlice({
name: "metadata",
initialState: {} as MetadataState,
reducers: {
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
state = action.payload
console.log("new metadata: ", state)
},
addMetadata: (state: MetadataState, action: PayloadAction<Metadata>) => {
state[action.payload.id] = action.payload
}
}
});
I have an async action to load the metadata from AsyncStorage (like LocalStorage on mobile), as follows:
export function loadMetadata() {
return async (dispatch: AppDispatch, getState: () => RootState) => {
const maybeMetadata = await AsyncStorage.getItem("metadata");
if(maybeMetadata) {
dispatch(metadataSlice.actions.setMetadata(JSON.parse(maybeMetadata)))
return true
} else {
return false
}
}
}
And I dispatch that in my main component as follows:
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(loadMetadata())
}, [])
In another component, I'm trying to access the state simply by doing:
const metadata = useAppSelector(state => state.metadata)
Any idea what's going on? The state just never seems to update, even though I see my action being called and update the state within it. Is it not being dispatched correctly? I tried directly accessing the state with store.getState() and the state seems empty, is it somehow just not being set?
I'm honestly pretty lost, any help is appreciated.
The issue had to do with how Immer (which Redux Toolkit leverages for allowing mutable operations) works.
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
state = action.payload
console.log("new metadata: ", state)
}
Instead of mutating state, I reassigned it, which messed up the way Immer keep track of draft states. The console.log statement returned the new state, but it didn't work with Immer. Instead, I needed to do this:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
// simply return the new state, since I'm changing the whole state
return action.payload
}
And it works fine now. I'm kind of surprised I didn't see this documented (it may be somewhere) or get some sort of warning, but good to know for the future!
An addition to Nathan's answer, to avoid linters flooding your code and for proper readability, instead of:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
return action.payload
}
Do it like this:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
return {...state, ...action.payload}
}
By so doing, first parameter of the action state, is put to use as it should
I am trying to logout and purge the store at the same time, so on click I dispatch this:
dispatch({type: PURGE, key: 'root', result: () => { } });
Redux persist catches it, and reports purging the store. Great.
In another reducer I catch that dispatch, and remove my access token like this:
import { PURGE } from 'redux-persist/es/constants';
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAccessToken(state: AuthState, action: PayloadAction<Auth>): void {
state.accessToken = action.payload.accessToken;
state.expiresIn = action.payload.expiresIn;
},
},
extraReducers: {
[PURGE]: (state: AuthState, action: string): void => {
state.accessToken = initialState.accessToken;
state.expiresIn = initialState.expiresIn;
},
},
});
The PURGE reducer actually is called, and modifies the state, but still no re-rendering happens. so redux must not pick that up. But according to the docs the Redux toolkit uses a Proxy object for the state and does a comparison to see if it's modified.
Things I tried:
state = initialState;
and
state = { ...initialState };
Didn't work. The store works, and holds data, other actions work. How do I proceed?
EDIT: Further debugging revealed that my own reducer was called BEFORE the redux-persist reducer, and redux-logger reported that my reducer did not change the state at all.
I'm facing a similar issue (not re-rendering) and came by this thread today:
Seems like you can't replace state objects entirely.
From: https://redux-toolkit.js.org/usage/immer-reducers
Sometimes you may want to replace the
entire existing state, either because you've loaded some new data, or
you want to reset the state back to its initial value.
WARNING A common mistake is to try assigning state = someValue
directly. This will not work! This only points the local state
variable to a different reference. That is neither mutating the
existing state object/array in memory, nor returning an entirely new
value, so Immer does not make any actual changes.
const initialState = []
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
brokenTodosLoadedReducer(state, action) {
// ❌ ERROR: does not actually mutate or return anything new!
state = action.payload
},
fixedTodosLoadedReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return action.payload
},
correctResetTodosReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return initialState
},
},
})
So
state = initialState;
would be
return initialState;
This turned out to be the solution:
extraReducers: {
[PURGE]: (state: UserState, action: string): UserState => ({
...state,
...initialState,
}),
},
I don't understand why, as modifying the state object should work too, according to the documentation:
To make things easier, createReducer uses immer to let you write
reducers as if they were mutating the state directly. In reality, the
reducer receives a proxy state that translates all mutations into
equivalent copy operations.
I tried to get a value from AsyncStorage in my react native application, but it comes out as an empty string first.
Lets say I have 2 components, Home and About.
The About screen is where I'm getting my AsyncStorage values.
I was able to use createBottomTabNavigator to create navigation tabs at the bottom of my screen so I can toggle between Home and About.
When I press into my About screen, I tried to console log the value, but its only an empty array. If I use hooks (or useEffect) like so:
const [data, setData] = useState([])
const asyncFunctionData = async () => {
try {
const storageData = await AsyncStorage.getItem('key_data');
setData(JSON.parse(storageData));
console.log(data);
} catch (e) {}
}
useEffect(() => {
asyncFunctionData();
}, [data]);
The component will continuously execute for some reason, but after a couple of execution, I can finally get the value of AsyncStorage. But the issue is why is it executing multiple times and why do I get an empty array at first? I thought having [] will only execute the useEffect once or when there's an update.
Does AsyncStorage have some type of effect on the continuous execution? Also, does AsyncStorage not get any value on app load?
Your hook doesn't depend on data that you set after getting values from AsyncStorage, also you should define async function inside useEffect
const [data, setData] = useState([])
useEffect(() => {
const asyncFunctionData = async () => {
try {
const storageData = await AsyncStorage.getItem('key_data');
setData(JSON.parse(storageData));
} catch (e) {}
}
asyncFunctionData();
}, [setData]);`
Have looked at others solutions, but they don't seem to be good in my case.
I have a Utilities.js file:
const setItem = async (value) => {
if (!value) return;
AsyncStorage.setItem('#my_key', value);
};
const getItem = async () => {
var val = await AsyncStorage.getItem('#my_key');
return val;
};
All the users' input are being saved in the AsyncStorage via code on Screen1:
Utilities.setItem('value')
Once data is saved we can go to Screen2 to read up the AsyncStorage via the getItem() method put in ComponentDidMount method:
componentDidMount = async () => {
let asyncValue = await Utilities.getItem();
let objFromAsyncValue = JSON.parse(asyncValue);
this.setState({
storage: objFromAsyncValue
})
}
All works well if I open Screen2 for the 1st time - all saved data is being shown, but going back and adding additional values for AsyncStorage obj is not being updated on Screen2 - but asyncstorage has more items added.
So far have tried triggering method:
this.forceUpdate()
and checking if the event onDidFocus has been triggered on load:
<NavigationEvents onDidFocus={ alert('Scren refreshed')} />
I know component rendering is state based, but in my instance I have no states to be updated, only AsyncStorage stateless object.
How can I refresh the screen and/or just read the updated content of AsyncStorage object?
I think you're assuming that Screen2 mounts every time you focus it. This may not be necessarily true. What you should do is move your getItem call inside another method and call it onWillFocus.
Like this,
onFocus = async () => {
let asyncValue = await Utilities.getItem();
let objFromAsyncValue = JSON.parse(asyncValue);
this.setState({
storage: objFromAsyncValue
})
}
And then,
<NavigationEvents onDidFocus={ alert('Scren refreshed')} onWillFocus={this.onFocus}/>
In your case, I would use a context, where your provider is the the content the user type and gets saved to the asyncstorage, the consumer would be the screen 2. That way you only need to access the asyncstorage on screen 1 and screen 2 will always be up to date to whatever has been typed and saved on screen 1
See: https://reactjs.org/docs/context.html