I need a help with updating user state.I register a user with name, email and password. Then in the profile page i want to give a chance to update(or create new) values like City and Country. And now im confused. My Redux action
export const updateUser=(profileId, updatedUser)=>async(dispatch)=>{
try {
const {data}= await api.updateUser(profileId,updatedUser)
dispatch({type: UPDATE_USER, payload: data})
} catch (error) {
console.log(error.message);
}
Reducer:
const initialState = {
users: [
{
city: "", country: "", email: "", name: "",
password: "",
_id: "",
},
],
};
const user = (state = initialState, action) => {
switch (action.type) {
case GET_ONE_USER:
return {
...state,
users: action.payload,
};
case UPDATE_USER:
return { ...state, users: action.payload };
default:
return state;
}
};
API:
export const updateUser=(profileId, updatedUser)=>API.patch(`/user/${profileId}`, updatedUser)
route:
router.patch('/:profileId',updateUser)
controller:
export const updateUser = async (req,res)=>{
const {id} = req.params
const {city, country} = req.body
const updatedUser={city, country}
try {
await User.findByIdAndUpdate(id,updatedUser, {new: true} )
res.status(200).json(updatedUser)
} catch (error) {
res.status(400).json({message: 'Blad'})
}
}
In my component:
const{ users} = useSelector((state)=>state.users)
and submit handler const handleSubmit =(e) =>{ e.preventDefault() dispatch(updateUser(users._id, data)) }
When i click button and dispatch an action, it only changes new values, all other are removed. I think that has something to do with my return state from reducer?
EDIT:
Ok, somehow i fixed this, although i think i could simplify the code?
case UPDATE_USER:
return { ...state, users: {...state.users, city:action.payload.city, country:action.payload.country}};
default:
return state;
You are updating state in a wrong way. You are replacing state with the new payload only. What you have to do is you have to keep previous state data and then add new payload that you are getting.
switch (action.type) {
.....
case UPDATE_USER:
return { ...state, users: [...state.users, action.payload] };
default:
return state;
}
};
Related
hi thank you for reading this. I am working a github finder react app that uses useReducer and i am try to set the initialstate when onload to load some users instead of an empty array. if i hard code the api data into the array, it will display as i wanted, but i want to make a GET to the api and pass the data into the array. I am very new to react, thank you all for the help
const GithubState = (props) => {
const initialState = {
users: [],
user: {},
repos: [],
loading: false,
};
//dispatcher
const [state, dispatch] = useReducer(GithubReducer, initialState);
//search Github users
const searchUsers = async (text) => {
setLoading();
const res = await axios.get(
`https://api.github.com/search/users?q=${text}&client_id=${githubClientId}&client_secret=${githubClientaSecret}`
);
//dispatch to reducer object
dispatch({
type: SEARCH_USERS,
payload: res.data.items,
});
};
//Reducer
import {
SEARCH_USERS,
SET_LOADING,
CLEAR_USERS,
GET_USER,
GET_REPOS,
} from "../types";
//action contains type and payload
// export default (state, action) => {
const Reducer = (state, action) => {
switch (action.type) {
case SEARCH_USERS:
return {
...state,
users: action.payload,
loading: false
}
case GET_USER:
return {
...state,
user: action.payload,
loading: false
}
case GET_REPOS:
return {
...state,
repos: action.payload,
loading: false
}
case SET_LOADING:
return {
...state,
loading: true
}
case CLEAR_USERS:
return {
...state,
users: [],
loading: false
}
default:
return state;
}
};
export default Reducer;
You can just call the searchUsers() function in useEffect() to get some users and set the state.
if you want to get initial users with some other logic you should probably write a different function and then call it when setting up the component.
const getInitialUsers = async () => {
setLoading();
let text = "blabla"; // or whatever your initial user query should look like modify the url below accordingly
const res = await axios.get(
`https://api.github.com/search/users?q=${text}&client_id=${githubClientId}&client_secret=${githubClientaSecret}`
);
//dispatch to reducer object
dispatch({
type: SEARCH_USERS,
payload: res.data.items,
});
};
useEffect(()=>{
getInitialUsers();
},[]);
The reasonable and suggest place to get your first async data is componentDidMount which in hook world it is translated to use Effect with an empty array as its dependencies
const [state, dispatch] = useReducer(githubReducer, initialState);
useEffect(() => {
searchUsers('initalTextSearchFromPropsOrAnyWhere')
}, [])
for more enhancement, you can call .then to show snack bar and .catch on to retry in case it fails for some reason and .finally to set loading to false in both cases.
I'm trying to render the data from the following object of data which is coming from an API.
{
"code": 0,
"c": "verified",
"d": "verified",
"leaseInfo": {
"infoId": 6
},
"cpfPrice": "500.00",
"carCurrentLocation": {
"id": 1,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
"n": "verified",
"p": "false",
"ownerCarInfo": {
"brand": "Ferrari",
"model": "0"
},
"serviceFeeRate": 0.10,
"depositPrice": "100.00",
"pics": [
{
"picid": 49,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
},
],
"items": {
"itemid": 5,
"carId": "df47a56a395a49b1a5d06a58cc42ffc4"
}
}
I'm using react-redux to dispatch an action, where I will be provided with the data under a state named 'carDetails'.
However, when I try to access the data, if my component is refreshed, carDetails becomes undefined and hence gives "Cannot read property ownerCarInfo of undefined."
I'm obtaining and de-structuring the data of carDetails like this in my React component:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(match.params.id));
}, [dispatch, match]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
);
)
}
As long as the component or the React application is not refreshed, it retrieves data and displays it correctly. The carDetails becomes an empty array as soon as the page is refreshed.
This is the getCarDetails() action:
export const getCarDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: CAR_DETAILS_REQUEST,
});
const { userLogin } = getState();
const { userInfo } = userLogin;
const config = {
headers: {
Authorization: userInfo.token,
'Content-Type': 'application/json',
},
};
const { data } = await axios.get(
`${BASE_API}/car/info/getDetails/${id}/${userInfo.bscId}`,
config
);
dispatch({
type: CAR_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CAR_DETAILS_FAIL,
payload:
error.response && error.response.data.msg
? error.response.data.msg
: error.msg,
});
}
};
This is my reducer:
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { loading: true };
case CAR_DETAILS_SUCCESS:
return { loading: false, carDetails: action.payload };
case CAR_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
This is how I declare carDetails in the redux store.
const reducer = combineReducers({
carDetails: carsDetailsReducer,
});
What is the cause for carDetails becoming undefined and the useEffect not running on page refresh?
If you are using axios your action should look like this with async function and await while you are calling API.
If you are passing API car id in the api link then pass the id in the parameters:
import axios from "axios";
export const loadData = (id) => async (dispatch) => {
dispatch({
type: "CAR_DETAILS_REQUEST",
});
const detailData = await axios.get("http:\\****/id");
dispatch({
type: "CAR_DETAILS_SUCCESS",
payload: {
success: detailData.data,
},
});
};
Reducer:
const initailState = { carDetails: [], loading: true };
export const carsDetailsReducer = (state = initailState, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return { ...state,
loading: true
};
case CAR_DETAILS_SUCCESS:
return {...state,
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return { ...state,
loading: false,
error: action.payload };
default:
return ...state;
}
};
Your useEffect should only work when data is fetched:
import React, {useEffect} from 'react';
import { useDispatch, useSelector } from 'react-redux';
const CarInfo = ({ match }) => {
const dispatch = useDispatch();
const details = useSelector((state) => state.carDetails);
const { loading, carDetails } = details;
const {pics, carCurrentLocation, items, ownerCarInfo} = carDetails;
useEffect(() => {
dispatch(getCarDetails(id));
}, [dispatch]);
return (
<div>
{loading ? (
<Loader></Loader>
) : (
<>
<p>{d.depositPrice}</p>
<p>{ownerCarInfo.brand}</p>
</>
)}
</div>
You can also use it without a useEffect by making an onclick() function like this:
const loadDetailHandler = () => {
dispatch(getCarDetails(id));
};
return (
<div onClick={loadDetailHandler} >
</div>
If carDetails initial state is an array, then why are you destructuring object properties from it in your UI? Question for another time...
If after reloading the page the state reverts back to the initial state, an empty array is still a defined object. You need to track down what is causing your state.carDetails.carDetails to become undefined. If you examine your reducer notice that your CAR_DETAILS_REQUEST case wipes the carDetails state out and it becomes undefined. Honestly I'm surprised you aren't seeing this issue when your code runs normally without a page reload.
You need to hold on to that state. For good measure, you should always shallow copy the existing state when computing the next state object unless you've good reason to omit parts of state.
export const carsDetailsReducer = (state = { carDetails: [] }, action) => {
switch (action.type) {
case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
case CAR_DETAILS_SUCCESS:
return {
...state, // <-- shallow copy existing state
loading: false,
carDetails: action.payload
};
case CAR_DETAILS_FAIL:
return {
...state, // <-- shallow copy existing state
loading: false,
error: action.payload,
};
default:
return state;
}
};
for me, I think you should save the state in the
`case CAR_DETAILS_REQUEST:
return {
...state, // <-- shallow copy existing state
loading: true,
};
`
to be able to use it before o when you want to use a reducer you should each case
have the old state the reducer return the same sharp of initial state that put it you also used is loading and that not found in the initial state
so try to make the shape of the state
state={
isloading:false,
carDetails: []
}
also try each time to same the state by {...state ,is loading:true}
The problem is in CAR_DETAILS_REQUEST. You only return { loading: true }; so carDetails will be lost and become undefined.
Just update your reducer like this:
case CAR_DETAILS_REQUEST:
return { ...state, loading: true };
My user structure is:
user = {
email: 'email',
flashcards: []
}
And i would like to add data into user's flashcards array (using redux)
My user-reducer
import { UserActionTypes } from './user.types';
const INITIAL_STATE = {
currentUser: null,
};
// GETS STATES OBJ AND RECIVES AN ACTION
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case UserActionTypes.SET_CURRENT_USER:
return {
...state,
currentUser: action.payload,
};
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: action.payload,
};
default:
return state;
}
};
export default userReducer;
user-actions
export const addFlashCard = user => ({
type: UserActionTypes.ADD_FLASHCARD,
payload: user.flashcards,
});
And when i'm doing so my payload is undefined.
Could you give me some hints?
You are currently overwriting currentUser with the value of user.flashcards from the redux action. To add new flashcards, the ADD_FLASHCARD branch of your reducer should look more like this:
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: {
...state.currentUser,
flashcards: [
...state.currentUser.flashcards,
...action.payload
]
}
};
I'm trying to implement an isFetching flag that indicates when my data is ready for rendering. But even if the flag works, i.e. jumps from isFetching = true to isFetching = false after the data has been successfully requested, there is still an error when I try to access data: cannot read property 'username' of null
Profile Component
class Profile extends React.Component {
render() {
const (isFetching, profile) = this.props.profile
console.log (isFetching)
console.log (profile)
return <h1>Hello, {isFetching = "false"? profile[0].username : null}</h1>;
}
}
function mapStateToProps(state, ownProps) {
const profile= state.profile
return { profile }
};
export default connect(
mapStateToProps,
{ logout }
)(Profile);
Action
export const getProfile = () => (dispatch, getState) => {
// Profile Loading
dispatch({ type: GET_PROFILE_REQUEST });
axios
.get(apiBase + "/profile/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_PROFILE_SUCCESS,
payload: res.data
});
})
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: GET_PROFILE_FAILURE,
});
});
};
Reducer
const initialState = {
isFetching: false,
profile: null
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_PROFILE_REQUEST:
return {
...state,
isFetching: true
};
case GET_PROFILE_SUCCESS:
return {
...state,
profile: action.payload,
isFetching: false
};
case GET_PROFILE_FAILURE:
return {
...state,
profile: action.payload,
isFetching: false
};
default:
return state;
}
}
Redux log for GET_PROFILE_SUCCESS
profile
isFetching: false
profile[
{
"username": "Daniel",
"id": 1,
"profile": {
"image": "Test",
"bio": "Test"
}
}
]
I'm happy for every clarification.
You have a small error in your code.
return <h1>Hello, {isFetching = "false"? profile.username : null}</h1>;
You are not checking for the value of isFetching but rather setting it again. Also, since profile is an array, you need to get the first element.Replace it with
return <h1>Hello, {!isFetching? profile[0].username : null}</h1>;
and it should work.
I want to use Redux in my registration page so I created a user reducer:
const user = (state = initialState, action) => {
switch (action.type) {
case 'TYPE_FIRSTNAME':
console.log('typed first name ' + action.text);
return { ...state, firstName: action.text };
case 'TYPE_LASTNAME':
return { ...state, lastName: action.text };
case 'TYPE_EMAIL':
return { ...state, email: action.text };
case 'TYPE_PASSWORD':
return { ...state, password: action.text };
default:
return state;
}
}
it is created like this:
const AppReducer = combineReducers({
nav,
user
});
export default AppReducer;
the nav reducer is for the navigation (used with react-navigation and it works fine). After that I created a container:
const mapStateToProps = state => {
return {
firstName: state.firstName,
lastName: state.lastName,
}
};
const mapDispatchToProps = (dispatch, ownProps) => ({
typeFirstName: (text) => {console.log('typed firstname');
dispatch({type: 'TYPE_FIRSTNAME', text})},
typeLastName: (text) => dispatch({type: 'TYPE_LASTNAME', text}),
registerUser: () => {
//register("mamad");
console.log('called register user : ');
dispatch({type: 'MAINSCREEN'})
}
});
export default connect(mapStateToProps,
mapDispatchToProps)(RegisterScene)
But it is never called, why?
The only problem I found is the mapStateToProps. I think it should be
const mapStateToProps = state => {
return {
firstName: state.user.firstName,
lastName: state.user.lastName,
}
};
It would be helpful if you put the error log here.
When you combine reducers the state gets put into the specified state branch.
In your case you need state.user.
so your mapStateToProps function should look like so:
const mapStateToProps = state => {
return {
firstName: state.user.firstName,
lastName: state.user.lastName,
}
};