When I dispatch my delete action to Redux I am getting the error unidentified is not an object evaluating selectedUser.imageUri Everything is being loaded from a server and I know the delete action works as it deletes the object from the server however I get this error and the screen only updates when I reload the application. Please can someone help me I really need your help. Thank you so much in advance!!!I am even checking to see if there is no object in the selecetedUser array then render an image called nothing.png
This is my code where I am seeing the error
const Viewer = (props) => {
const userID = props.navigation.getParam('id')
//Nothing is just a picture when there are no images
import nothing from './Images/nothing.png'
const selectedUser = useSelector(state => state.user.user.find(user => user.id === userID))
const cBimageUri = {uri: selectedUser.imageUri }
const checkImage = cBimageUri.length === 0? nothing : cBimageUri
const cBimageUri = {uri: selectedUser.imageUri }
const deleteCb = useCallback(() =>{
dispatch(deleteUser(userID))
props.navigation.goBack()
},[userID])
useEffect(() => {
props.navigation.setParams({deleteCb: deleteCb})
},[deleteCb])
return (
<ScrollView style={{backgroundColor: 'white'}}>
<Image source={checkImage} style={styles.image}/>
<Text style={styles.name}>{selectedCookBook.name}</Text>
</ScrollView>
)
}
export default Viewer
Redux reducer
import { CREATE_USER, DELETE_USER } from '../actions/account'
const initialState = {
account: [],
}
const USerReducer = (state=initialState, action) =>{
switch(action.type){
case CREATE_USER:
const newUser = new MyUser(
action.userData.id,
action.userData.name,
action.userData.image,
)
return { ...state, user: state.account.concat(newUser)}
case DELETE_USER:
const filteredItems = state.account.filter(cb => cb.id !== action.deleteCb)
return {account: filteredItems }
default:
return state
}
}
export default USerReducer
Redux action
export const DELETE_COOKBOOK = 'CLEAR'
export const deleteCookbook = (deleteCb) => {
return {type: DELETE_COOKBOOK, deleteCb: deleteCb}
}
console logging selectedUser
[
Object {
"id": 1595444079901,
"val": "Veveve",
},
name: John Snow,
imageUri: 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Frn-first-app-e648c632-2715-4169-abf3-e0cdbe2ac7d5/ImagePicker/461b63af-a908-47e9-8841-d5d8f2c4eb67.jpg
file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Frn-first-app-e648c632-2715-4169-abf3-e0cdbe2ac7d5/ImagePicker/461b63af-a908-47e9-8841-d5d8f2c4eb67.jpg'
}
]
Try to change follow
const Viewer = (props) => {
const userID = props.navigation.getParam('id')
//Nothing is just a picture when there are no images
import nothing from './Images/nothing.png'
const selectedUser = useSelector(state => state.user.user.find(user => user.id === userID))
const cBimageUri = selectedUser.imageUri // --> changed it
const checkImage = cBimageUri.length === 0? nothing : cBimageUri
//const cBimageUri = {uri: selectedUser.imageUri } // is it a right row? just repeat previous
const deleteCb = useCallback(() =>{
dispatch(deleteUser(userID))
props.navigation.goBack()
},[userID])
useEffect(() => {
props.navigation.setParams({deleteCb: deleteCb})
},[deleteCb])
return (
<ScrollView style={{backgroundColor: 'white'}}>
<Image source={{uri: checkImage}} style={styles.image}/> // --> changed it. Not sure about it, if it's not working check below link
<Text style={styles.name}>{selectedCookBook.name}</Text>
</ScrollView>
)
}
export default Viewer
this link https://stackoverflow.com/questions/50249353/uri-vs-url-in-react-native
Related
I'm learning redux and i'm making a sort of pokedex app where i fetch 20 pokemons from pokeapi.co at a time. When the page changes a new list of 20 pokemons is fetched. The problem is that while state changes to the new pokemons, they don't actually render.
App.js
const App = () => {
const dispatch = useDispatch();
const offset = useSelector(state => state.offset);
const limit = useSelector(state => state.limit);
useEffect(() => {
//FETCHES 20 OBJECTS THAT CONTAIN AN URL TO AN INDIVIDUAL POKEMON
dispatch(fetchPokemons(limit, offset));
}, [limit, offset, dispatch]);
...
return (
<div style={{ backgroundColor: '#222222' }}>
<Notification />
<AppBarPokemon />
<Switch>
<Route path="/pokemons">
<PokemonsDisplay CapsFirstLetter={CapsFirstLetter}/>
</Route>
...
PokemonsDisplay.js
const PokemonsDisplay = ({ CapsFirstLetter }) => {
const dispatch = useDispatch();
const classes = useStyles();
const pokemons = useSelector(state => state.pokemons);
console.log(pokemons);
const pageSize = 20;
const totalCount = 898;
const handleClick = (p) => {
dispatch(getOnePokemon(p));
};
return (
<div className={classes.root}>
{pokemons && (
<Grid container spacing={3}>
{pokemons.map(p => (
<Grid item xs={3} key={p.name} className={classes.gridItem} component={Link} onClick={() => handleClick(p)} to={`/pokemons/${p.name}`} data-cy={`pokemon-button-${p.name}`}>
<Paper className={classes.paper && classes.color} elevation={10}>
<p className={classes.p}>#{p.id}</p>
<p className={classes.p}>{CapsFirstLetter(p.name)}</p>
{p.sprites.other["dream_world"]["front_default"] !== null ?
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["dream_world"]["front_default"]}/> :
<img className={classes.image} alt={`${p.name}'s sprite`} src={p.sprites.other["official-artwork"]["front_default"]}/>}
</Paper>
</Grid>
))}
</Grid>
)}
<Pagination
totalCount={totalCount}
pageSize={pageSize}
/>
</div>
);
};
pokemonsReducer.js
import getPokemons from '../services/pokemons';
import axios from 'axios';
import { loadPokemonsFromLS, savePokemonsList } from '../utils/localStoragePokemons';
const pokemonsReducer = (state = [], action) => {
console.log('state is:', state)
switch(action.type){
case 'INIT_POKEMONS':
return action.data;
default:
return state;
};
};
export const fetchPokemons = (limit, offset) => {
return async dispatch => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
dispatch({
type: 'INIT_POKEMONS',
data: pokemons
});
} catch (error) {
const pokemons = await getPokemons.getPokemons(limit, offset);
let pokemonsArray = [];
let pokemonsObject = {};
pokemons.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsArray.push(pokemonNow.data);
pokemonsObject[i] = pokemonNow.data
});
savePokemonsList(limit, offset, pokemonsObject);
dispatch({
type: 'INIT_POKEMONS',
data: pokemonsArray
});
}
};
};
export default pokemonsReducer;
I have tried to dispatch({ data: [...pokemons] })
But it doesnt work.
Also i forgot to add. When i go to my component that is routed to '/' and then back to '/pokemons' they render.
Edit: I think i'm getting there.
i changed the reducer function so that it gets called independently from the dispatch, the problem is that now the action doesn't get fired xD.
export const fetchEverything = async (limit, offset) => {
try {
const pokemons = loadPokemonsFromLS(limit, offset);
initPokemons(pokemons);
} catch (error) {
const pokemonsData = await getPokemons.getPokemons(limit, offset);
let pokemons = [];
let pokemonsObject = {};
console.log(pokemonsData)
pokemonsData.results.forEach(async (r, i) => {
//FETCHES EACH POKEMON URL AND STORES ITS DATA ON pokemons STATE
const pokemonNow = await axios.get(r.url);
pokemonsObject[i] = pokemonNow.data;
//console.log([pokemonNow.data][0]);
pokemons.push(pokemonNow.data);
});
console.log(pokemons);
console.log(pokemonsObject);
savePokemonsList(limit, offset, pokemonsObject);
initPokemons(pokemons);
};
};
export const initPokemons = (pokemons) => {
return dispatch => dispatch({ type: 'INIT_POKEMONS', pokemons: pokemons })
};
const pokemonsReducer = (state = [], action) => {
switch(action.type){
case 'INIT_POKEMONS':
console.log(action);
const newState = action.pokemons
return newState;
default:
return state;
};
};
It happens due to redux state mutation, you can resolve this issue using immer as stated in redux documentation as well. https://www.npmjs.com/package/immer
I have this context:
interface AlertContextProps {
show: (message: string, duration: number) => void;
}
export const AlertContext = createContext<AlertContextProps>({
show: (message: string, duration: number) => {
return;
},
});
export const AlertProvider: FC<IProps> = ({ children }: IProps) => {
const [alerts, setAlerts] = useState<JSX.Element[]>([]);
const show = (message: string, duration = 6000) => {
let alertKey = Math.random() * 100000;
setAlerts([...alerts, <Alert message={message} duration={duration} color={''} key={alertKey} />]);
setTimeout(() => {
setAlerts(alerts.filter((i) => i.key !== alertKey));
}, duration + 2000);
};
return (
<>
{alerts}
<AlertContext.Provider value={{ show }}>{children}</AlertContext.Provider>
</>
);
};
which I need to "translate" into a redux slice. I got a hang of everything, apart from the show method. What would be the correct way to treat it? I was thinking about a thunk, but it's not really a thunk. Making it a reducer with setTimeout also seems like an ugly thing to do. So how would you guys do it?
My code so far:
type Alert = [];
const initialState: Alert = [];
export const alertSlice = createSlice({
name: 'alert',
initialState,
reducers: {
setAlertState(state, { payload }: PayloadAction<Alert>) {
return payload;
},
},
});
export const { setAlertState } = alertSlice.actions;
export const alertReducer = alertSlice.reducer;
The timeout is a side effect so you could implement that in a thunk.
You have an action that shows an alert message that has a payload of message, id and time to display, when that time runs out then the alert message needs to be removed so you need a remove alert message action as well that is dispatched from the thunk with a payload of the id of the alert message.
I am not sure why you add 2 seconds to the time to hide the message duration + 2000 since the caller can decide how long the message should show I don't think it should half ignore that value and randomly add 2 seconds.
Here is a redux example of the alert message:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = {
messages: [],
};
//action types
const ADD_MESSAGE = 'ADD_MESSAGE';
const REMOVE_MESSAGE = 'REMOVE_MESSAGE';
//action creators
const addMessage = (id, text, time = 2000) => ({
type: ADD_MESSAGE,
payload: { id, text, time },
});
const removeMessage = (id) => ({
type: REMOVE_MESSAGE,
payload: id,
});
//id generating function
const getId = (
(id) => () =>
id++
)(1);
const addMessageThunk = (message, time) => (dispatch) => {
const id = getId();
dispatch(addMessage(id, message, time));
setTimeout(() => dispatch(removeMessage(id)), time);
};
const reducer = (state, { type, payload }) => {
if (type === ADD_MESSAGE) {
return {
...state,
messages: state.messages.concat(payload),
};
}
if (type === REMOVE_MESSAGE) {
return {
...state,
messages: state.messages.filter(
({ id }) => id !== payload
),
};
}
return state;
};
//selectors
const selectMessages = (state) => state.messages;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
//simple implementation of thunk (not official redux-thunk)
({ dispatch }) =>
(next) =>
(action) =>
typeof action === 'function'
? action(dispatch)
: next(action)
)
)
);
const App = () => {
const messages = useSelector(selectMessages);
const dispatch = useDispatch();
return (
<div>
<button
onClick={() =>
dispatch(addMessageThunk('hello world', 1000))
}
>
Add message
</button>
<ul>
{messages.map((message) => (
<li key={message.id}>{message.text}</li>
))}
</ul>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
#HMR's use of a thunk is fine, but I don't like what they've done to your reducer. You're already using redux-toolkit which is great! redux-toolkit actually includes and exports a nanoid function which they use behind the scenes to create unique ids for thunks. You can use that instead of Math.random() * 100000.
I always start by thinking about types. What is an Alert? You don't want to store the <Alert/> because a JSX.Element is not serializable. Instead you should just store the props. You'll definitely store the message and key/id. If you handle expiration on the front-end then you would also store the duration, but if the expiration is handled by a thunk then I don't think you need it in the redux state or component props.
It seems like you want to allow multiple alerts at one time, so return payload is not going to cut it for your reducer. You'll need to store an array or a keyed object will all of your active alerts.
You absolute should not use setTimeout in a reducer because that is a side effect. You can use it either in a thunk or in a useEffect in the Alert component. My inclination is towards the component because it seems like the alert should probably be dismissible as well? So you can use the same function for handling dismiss clicks and automated timeouts.
We can define the info that we want to store for each alert.
type AlertData = {
message: string;
id: string;
duration: number;
}
And the info that we need to create that alert, which is the same but without the id because we will generate the id in the reducer.
type AlertPayload = Omit<AlertData, 'id'>
Our state can be an array of alerts:
const initialState: AlertData[] = [];
We need actions to add a new alert and to remove an alert once it has expired.
import { createSlice, PayloadAction, nanoid } from "#reduxjs/toolkit";
...
export const alertSlice = createSlice({
name: "alert",
initialState,
reducers: {
addAlert: (state, { payload }: PayloadAction<AlertPayload>) => {
const id = nanoid(); // create unique id
state.push({ ...payload, id }); // add to the state
},
removeAlert: (state, { payload }: PayloadAction<string>) => {
// filter the array -- payload is the id
return state.filter((alert) => alert.id !== payload);
}
}
});
export const { addAlert, removeAlert } = alertSlice.actions;
export const alertReducer = alertSlice.reducer;
So now to the components. What I have in mind is that you would use a selector to select all of the alerts and then each alert will be responsible for its own expiration.
export const AlertComponent = ({ message, duration, id }: AlertData) => {
const dispatch = useDispatch();
// function called when dismissed, either by click or by timeout
// useCallback is just so this can be a useEffect dependency and won't get recreated
const remove = useCallback(() => {
dispatch(removeAlert(id));
}, [dispatch, id]);
// automatically expire after the duration, or if this component unmounts
useEffect(() => {
setTimeout(remove, duration);
return remove;
}, [remove, duration]);
return (
<Alert
onClose={remove} // can call remove directly by clicking the X
dismissible
>
<Alert.Heading>Alert!</Alert.Heading>
<p>{message}</p>
</Alert>
);
};
export const ActiveAlerts = () => {
const alerts = useSelector((state) => state.alerts);
return (
<>
{alerts.map((props) => (
<AlertComponent {...props} key={props.id} />
))}
</>
);
};
I also made a component to create alerts to test this out and make sure that it works!
export const AlertCreator = () => {
const dispatch = useDispatch();
const [message, setMessage] = useState("");
const [duration, setDuration] = useState(8000);
return (
<div>
<h1>Create Alert</h1>
<label>
Message
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</label>
<label>
Duration
<input
type="number"
step="1000"
value={duration}
onChange={(e) => setDuration(parseInt(e.target.value, 10))}
/>
</label>
<button
onClick={() => {
dispatch(addAlert({ message, duration }));
setMessage("");
}}
>
Create
</button>
</div>
);
};
const App = () => (
<div>
<AlertCreator />
<ActiveAlerts />
</div>
);
export default App;
Code Sandbox Link
I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. The page redirects but doesn't load. All of the data from the first component I want is passed in but it wont set state when I use setProfile(p). All the other console.log()s in the member component show all the data I expect but it won't set the state with this data.
import {useLocation} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
const [user, setUser] = useState({});
const { state } = useLocation();
const [profile, setProfile] = useState({});
const dispatch = useDispatch();
const [list, setList] = useState([]);
const [posts, setPosts] = useState([]);
const [snInstance, setsnInstance] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
// const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
// const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
if (state && state.user) {
setUser(state.user);
}
const accounts = await MyWeb3.getInstance().getAccounts();
setAccounts(accounts);
console.log(accounts)
const incidents = MyWeb3.getInstance().getContract(Incidents)
const incidentsInstance = await MyWeb3.getInstance().deployContract(incidents);
const sn = MyWeb3.getInstance().getContract(SocialNet)
const snInstance = await MyWeb3.getInstance().deployContract(sn);
setsnInstance(snInstance);
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
This is the reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
It doesn't make any sense that you would dispatch({ type: 'ADD_MEMBER', response: member }) with a member object that came from the publisher property of a post. That info is already in your state. You probably need to be normalizing your state better so that you can select it where you need it.
You want to use the Link component from react-router-dom to navigate to a member's profile page. Your Route should render the correct profile based on an id or username property in the URL. Don't pass through the data when you redirect, just go to the correct URL. On that Member page you can get the user from the state by looking up the id.
In Posts:
<Link to={`/member/${p.publisher.id}`}><button>Profile</button></Link>
In Routes:
<Route path="/member/:id" component={Member} />
In Member:
const Member = () => {
const { id } = useParams();
const profile = useSelector((state) =>
state.socialNetworkContract.members.find((user) => user.id === id)
);
const dispatch = useDispatch();
useEffect(() => {
const doEffects = async () => {
if ( ! profile ) {
dispatch(loadUser(id));
}
};
doEffects();
}, [dispatch, profile, id]);
I have a form for users to enter their details and press submit. This is supposed to dispatch an action and update the state by .concat() a class to it. Unfortunately the state isn't updating and I don't know why. If I take out useCallBack() or useEffect() from the code , the emulator freezes and I suspect infinite loops.
Redux Reducer
// Initialised class
import newAccount from '../../models/newAccount'
import { CREATE_ACCOUNT } from '../actions/meals'
const initialState = {
account: [],
}
const addPerson = (state=initialState, action) =>{
switch(action.type){
case CREATE_ACCOUNT:
const newAccount = new newAccount(
Date.now().toString(),
action.accountData.name,
action.accountData.image,
action.accountData.email,
action.accountData.password
)
return { ...state, account: state.account.concat(newAccount) }
default:
return state
}
}
export default addPerson
Redux action
export const CREATE_ACCOUNT = 'CREATE_ACCOUNT'
export const newAccount = (Id,name,image, email, password) => {
return {type: CREATE_ACCOUNT, accountData:{
Id: Date.now().toString(),
name: name,
image: image,
email: email,
password: password
}
}
}
The class
class newAccount {
constructor(
id,
name,
image,
email,
password
){
this.id = id;
this.name = name;
this.image = image;
this.email = email;
this.password = password;
}
}
export default newAccount
The Component
import React, { useState, useCallback, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {newAccount} from '../Store/actions/accounts'
import ImagePicker from '../Components/ImagePicker'
const AddScreen = (props) => {
const dispatch = useDispatch()
const [name, setName] = useState('')
const [selectedImage, setSelectedImage] = useState('')
const email = useSelector(state => state.account.email)
const password = useSelector(state => state.account.password)
const handleSubmit = useCallback(() => {
dispatch(newAccount(Date.now(),name,selectedImage,email,password))
},[dispatch, name, selectedImage, email, password])
useEffect(() => { handleSubmit
props.navigation.setParams({handleSubmit: handleSubmit})
},[handleSubmit])
return (
<View style={styles.container}>
<View style={styles.card}>
<ImagePicker onImageSelected={selectedImage} />
<AddForm email={email} password={password}/>
<TextInput
onChangeText={name => setName(name)}
value={name}
/>
</View>
</View>
)
}
export default AddScreen
AddScreen.navigationOptions = (navigationData) => {
const submit = navigationData.navigation.getParam('handleSubmit')
return {
headerTitle: 'Create Account',
headerRight: () => (
<TouchableOpacity onPress={submit}>
<Text style={styles.createOrange}>Create</Text>
</TouchableOpacity>
)
}
}
I really don't know why it's not updating .
first of all, you shouldn't store classes in the redux store, the store should only exists of plain objects. but if you really want to store the class:
The real problem seams to be return { ...state, account: state.account.concat(newAccount) }. here you concat the existing array with the new class, but that doesn't work.
your store looks like this if you do so:
{
account: [{
email: "..."
id: "..."
image: "..."
name: "..."
password: "...
}],
}
so your selector (state.account.email) will return undefined. you can use (state.account[0].email)
or you can fix it by fixing the real problem:
return { ...state, account: newAccount }
also your initialState shouldn't be a an array for account as it will never be an array, it will be an Account class (this is why you don't get an error by what you are doing). set it to null.
const initialState = {
account: null,
}
I really don't know why this doesn't work. Just want to give you an advice to make it more simple and clearer (from my point of view):
You can drop side effects like useEffect. To achieve this just move local state to redux state and then you will be able to just dispatch the action from your navigationOptions component. It could look like:
const AddScreen = () => {
const name = useSelector(...);
...
const password = useSelector(...);
// trigger action on something changes, for instance like that:
const onChange = (key, value) = dispatch(newAccountChange({[key]: value}))
// return tree of components
}
export const submitNewAccount = () => {
return (dispatch, getState) => {
const { id, name, ... } = getState().account;
dispatch(newAccount(id, name, ...));
};
}
AddScreen.navigationOptions = (navigationData) => {
const dispatch = useDispatch();
const submit = dispatch(submitNewAccount());
...
}
I used redux-thunk in this example.
I believe, this approach will give you more flexible way to debug and extend your business logic.
Maybe that's a stupid question, but I have a problem. My state looks like this:
const initialState: PhotoState = {
photos: [],
};
The reducer code looks like this:
const initialState: PhotoState = {
photos: [],
};
export default function photoReducer(state = initialState, action: PhotoActionTypes): PhotoState {
switch (action.type) {
case SET_PHOTOS:
const photos: any[] = action.payload;
return {...state, photos};
}
return state;
};
I get photos from API and then set them this way:
export function setPhotos(payload: any[]): PhotoActionTypes {
return {type: SET_PHOTOS, payload};
}
export function getPhotos() {
return (dispatch: Dispatch<PhotoActionTypes>, getState: () => RootState): void => {
const profile_id = getState().auth.profile_id;
ax().post('pictures/api/pictures/list', {profile_id}).then((response) => {
const photos: any[] = response.data.pictures || [];
dispatch(setPhotos(photos));
})
}
}
Also I have an action that sends a new photo to the server and saves it in history. Then I get photos in component:
useEffect(() => {
dispatch(getPhotos());
}, []);
const handleSendPhoto = (): void => {
dispatch(sendPhoto(image?.base64));
dispatch(getPhotos());
}
The whole component:
const PhotoScreen = () => {
const [photoFlag, setPhotoFlag] = useState(false);
const [image, setImage] = useState<TakePictureResponse | null>(null);
const [height, setHeight] = useState(0);
const width = Dimensions.get('screen').width / 5;
const dispatch = useDispatch();
const handleSendPhoto = (): void => {
dispatch(sendPhoto(image?.base64, location));
dispatch(getPhotos());
}
const PhotoView = () => (
<View>
<FastImage
style={{width: width, height: height}}
source={{
uri: `data:image/jpeg;base64, ${image?.base64}`,
priority: FastImage.priority.normal,
}}
resizeMode={FastImage.resizeMode.contain}
onLoad={evt => {
setHeight(evt.nativeEvent.height / evt.nativeEvent.width * width)
}}
/>
<Button
mode="contained"
onPress={handleSendPhoto}
disabled={!image}
color={constants.buttonColor}>
Add photo
</Button>
</View>
);
return (
<SafeAreaView style={{...mainStyles.screen, ...styles.container}}>
<StatusBarDark />
{!photoFlag && (<View>
<Button
mode="contained"
onPress={() => setPhotoFlag(true)}
color={constants.buttonColor}>
Make photo
</Button>
</View>)}
{photoFlag && <CameraComponent setImage={setImage} setPhotoFlag={setPhotoFlag}/>}
{image !== null && <PhotoView />}
</SafeAreaView>
);
};
export default PhotoScreen;
But state updates only from the second time. I press the button 'Add photo', photo appends to history, but doesn't shows up in it. Then I press the button again, and previous photo shows up in history, but current photo doesn't.
How can I fix it?
UPD: Problem was solved. The question may be closed.
You shouldn't do this, because both calls will be send at the same time:
const handleSendPhoto = (): void => {
dispatch(sendPhoto(image?.base64));
dispatch(getPhotos()); // this will be called before the upload is finished. so the old data will be returned.
}
you may need to use redux-thrunk (https://github.com/reduxjs/redux-thunk) like this:
const handleSendPhoto = (): void => {
dispatch(sendPhoto(image?.base64)).then(() => dispatch(getPhotos()));
}