How to save multiple data in localStorage with zustand? - javascript

I build a stepper and I want save my data in each step and access all my last data`s
export const useInformation = create(
persist(
(set) => ({
personInfo: [],
setPersonInfo: (callBack) => {
set((state) => ({
personInfo: callBack,
}));
},
}),
{
name: 'Person_setting',
getStorage: () => localStorage,
}
)
);
this my store with zustand and it will update in my component with onclick but when i update it, its return just last data steps
but

Related

Zustand state with javascript Map is not updating

I am trying to use map to store state, while the state is updating, the components are not re-rendering with the new state
const store = (set, get) => ({
items: new Map(),
addItem: (key, item) => {
set((state) => state.items.set(key, item));
// this prints the latest state
console.log(get().items)
},
removeItem: (key) => {
set((state) => state.items.delete(key));
},
)}
const useStore = create(store);
export default useStore
useEffect doesn't prints anything
const items = useStore((state) => state.items))
useEffect(() => {
console.log(items)
}, [items]);
You should create a new instance of Map when you update like so:
const store = (set, get) => ({
items: new Map(),
addItem: (key, item) => {
set((state) => {
const updatedItems = new Map(state.items)
updatedItems.set(key, item)
return { items: updatedItems }
});
// this prints the latest state
console.log(get().items)
},
)}
Note: you'll have to do something similar when you remove items.
The Map instance is always the same. So Zustand cannot detect any changes. You can use an array:
const store = (set, get) => ({
items: [],
addItem: (key, item) => {
set((state) => ({
items: [...state.items, { key, item }]
}));
},
removeItem: (key) => {
set((state) => ({
items: state.items.filter((item) => item.key !== key)
}));
}
});
If you don't want to create a new instance of a map so that zustand registers state change then change you can use immer.
import create from 'zustand'
import { immer } from 'zustand/middleware/immer'
const store =immer( (set, get) => ({
items: new Map(),
addItem: (key, item) => {
set((state) => state.items.set(key, item));
// this prints the latest state
console.log(get().items)
},
removeItem: (key) => {
set((state) => state.items.delete(key));
}),
)}
const useStore = create(store);
export default useStore

Component keeps re-rendering after state change

I use handleMouseOver and handleMouseOut functions where I change the value of a state count. However, every time the state is changed the component re-renders instead of just the state. What am I missing here? Thanks.
function foo() {
const [state, setState] = useState({ count: 0, data: {}});
useEffect(() => {
const getData = async () => {
const response = await fetch(url);
const data = await response.json();
setState(prevState => ({
...prevState,
data: data,
}));
};
return ()=>{
getData();
}
}, []);
function handleMouseOver(e) {
setState(prevState => ({
...prevState,
count: e,
}));
};
function handleMouseLeave() {
setState(prevState => ({
...prevState,
count: null,
}));
};
const { count, data } = state;
const BlockComponent = () => {
const data = data.arr;
return (
<Wrapper >
{data.map((value, index) =>
value.map((value, index) => {
return (
<Block
key={index}
val={value}
onMouseEnter={e => handleMouseOver(value)}
onMouseOut={handleMouseLeave}
></Block>
);
})
)}
</Wrapper>
);
};
return (
<Wrapper>
<BlockComponent />
</Wrapper>
);
}
export default foo;
The Issue is with your handleMouseOver function. It is getting executed everytime there is a state Update and the same value is assigned to "count".
All you have to do is place setState inside the condition that will compare the value of event received by the function and the current value of sate.
It should be something like this.
function handleMouseOver(e) {
if (count !== e) {
setState((prevState) => ({
...prevState,
count: e,
}));
}
}
React updates component if component state is changed. That's correct behaviour.
I recommend you to learn react documentation, because component state is a basic concept.
That's one of the main points of state -> component is rerendered when you change state.

My response data is not stored into the variable in react js

I am trying to store my response data into the activityType variable, inside the useEffect I am looping an API based on tabs value, the API will return is the value true or false, and I am able to get the values on my console but when I try to store the data I am getting empty array.
const tabs = [
// {value: 'All', label: `${i18n.t('AllActivity')}`},
{value: 'ActSendGift', label: `${i18n.t('Gifts')}`},
{value: 'ActSubscribe', label: `${i18n.t('Subscribers')}`},
{value: 'ActFollow', label: `${i18n.t('Followers')}`},
];
const activityType: any = [];
useEffect(() => {
async function fetchData() {
return tabs.map(async (item: any) => {
return await Api.user
.getSettingValues(item.value)
.then((response: any) => {
// console.log(response.settingValue); // true
// console.log(item.value); "ActSendGift"
return activityType.push(response.settingValue);
});
});
}
fetchData();
}, [activityFeedData.activityList, activityType]);
you can use 'useState' hook instead of declaring a variable in react.
first you need to import the hook
import {useState, useEffect} from 'react'
and inside your functional component you can update the state after fetching the data.
const [activityType, setActivityType] = useState<any>([])
useEffect(() => {
async function fetchData() {
return tabs.map(async (item: any) => {
return await Api.user
.getSettingValues(item.value)
.then((response: any) => {
// console.log(response.settingValue); // true
// console.log(item.value); "ActSendGift"
return setActivityType(response.settingValue);
});
});
fetchData();
}, [activityFeedData.activityList, activityType]);
The useState hook returns two values, One is the state variable and the second one is the state update function. The component will re-render automatically as the state changes.
Try putting this array into a state and ensure that the variables that are in the usefct dependency array are being updated so that this block is executed.
const tabs = [
{value: 'ActSendGift', label: `${i18n.t('Gifts')}`},
{value: 'ActSubscribe', label: `${i18n.t('Subscribers')}`},
{value: 'ActFollow', label: `${i18n.t('Followers')}`},
];
const [activeType, setActiveType] = useState([])
useEffect(() => {
async function fetchData() {
return tabs.map(async (item: any) => {
return await Api.user
.getSettingValues(item.value)
.then((response: any) => setActiveType(response.settingValue));
});
}
fetchData();
}, [activityFeedData.activityList]);

I have a problem in using mapStateToProps

I am practicing the react-redux.
I wonder what is the difference between these two codes.
I think these have same meaning, but the first code is not working well.
The first one cannot deliver the props of the state.
I removed the "import" part in this post
const TodosContainer = ({
input,
todos,
changeInput,
insert,
toggle,
remove
}) => {
return (<Todos
input={input}
todos={todos}
onChangeInput={changeInput}
onInsert={insert}
onToggle={toggle}
onRemove={remove} />);
};
// from this point, I changed this code into below one
const mapStateToProps = state => ({ // in this part, I think it cannot send props
input: state.todos.input,
todos: state.todos.todos
});
const mapDispatchToProps = dispatch => ({
changeInput: () => {
dispatch(changeInput());
},
insert: () => {
dispatch(insert());
},
toggle: () => {
dispatch(toggle());
},
remove: () => {
dispatch(remove());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps)(TodosContainer);
export default connect(
({ todos }) => ({
input: todos.input,
todos: todos.todos,
}),
{
changeInput,
insert,
toggle,
remove,
}
)(TodosContainer);

React hooks, component is reading old data from Redux

My component is set to display a list of books, as card thumbnails. Each item from the list of books is generated by this component.
Each Card has a favorites icon, when clicking it adds the book to favoriteTitles array. By pressing again on the favorites icon it removes it from the list.
const Card = ({ title, history }) => {
const dispatch = useDispatch();
const { favoriteTitles } = useSelector(({ titles }) => titles);
const { id, name, thumbnail } = title;
const [favorite, setFavorite] = useState(favoriteTitles?.some(item => item.titleId === title.id));
const handleFavoriteClick = () => {
const isFavorite = favoriteTitles?.some(item => item.titleId === title.id);
if (isFavorite) {
dispatch(removeFavoriteTitle(title));
setFavorite(false);
} else {
dispatch(addFavoriteTitle(title));
setFavorite(true);
}
};
return (
<CardContainer>
<Thumbnail thumbnail={thumbnail} />
{name}
<FavesIcon isActive={favorite} onClick={handleFavoriteClick} />
</CardContainer>
);
};
The issue with this component is when you press once on FavesIcon to add, and if you changed your mind and want to remove it and press right away again, the favoritesTitles array still has the old value.
Let's suppose our current favorites list looks like this:
const favoritesTitles = [{titleId: 'book-1'}];
After pressing on favorites icon, the list in Redux gets updated:
const favoritesTitles = [{titleId: 'book-1'}, {titleId: 'book-2'}];
And if I press again to remove it, the favoritesTitles array inside the component is still the old array with 1 item in it. But if I look in Redux the list updated and correct.
How component should get the updated Redux value?
Update
I have specific endpoints for each action, where I add or remove from favorites:
GET: /users/{userId}/favorites - response list eg [{titleId: 'book-1'}, {titleId: 'book-2'}]
POST: /users/me/favorites/{titleId} - empty response
DELETE: /users/me/favorites/{titleId} - empty response
For each action when I add or remove items, on success request I dispatch the GET action. Bellow are my actions:
export const getFavoriteTitles = userId =>
apiDefaultAction({
url: GET_FAVORITE_TITLES_URL(userId),
onSuccess: data => {
return {
type: 'GET_FAVORITE_TITLES_SUCCESS',
payload: data,
};
},
});
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
return dispatch(
apiDefaultAction({
method: 'POST',
url: SET_FAVORITE_TITLES_URL,
data: {
titleId: id,
},
onSuccess: () => {
dispatch(getFavoriteTitles(userId));
return { type: 'SET_FAVORITE_TITLE_SUCCESS' };
},
})
);
};
My reducers are pretty straight forward, I'm not mutating any arrays. Since only GET request is returning the list of array, I don't do any mutating in my reducers:
case 'GET_FAVORITE_TITLES_SUCCESS':
return {
...state,
favoriteTitles: action.payload,
};
case 'SET_FAVORITE_TITLE_SUCCESS':
return state;
case 'DELETE_FAVORITE_TITLE_SUCCESS':
return state;
It seems that by the time you click FavesIcon second time after adding to favourites, GET: /users/{userId}/favorites request is still pending and favoriteTitles list is not updated yet. That's why the component still contains an old value.
You need to update favoriteTitles list right away after triggering addFavoriteTitle or removeFavoriteTitle actions, without waiting GET_FAVORITE_TITLES_SUCCESS action to be dispatched. This pattern is called 'Optimistic UI':
export const toggleFavorite = itemId => {
return {
type: 'TOGGLE_FAVORITE',
payload: { itemId },
};
}
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
export const removeFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
And your reducer can look something like this:
case 'TOGGLE_FAVORITE':
return {
...state,
favoriteTitles: state.favoriteTitles.map(item => item.titleId).includes(action.payload.itemId)
? state.favoriteTitles.filter(item => item.titleId !== action.payload.itemId)
: [...state.favoriteTitles, { titleId: action.payload.itemId }],
};
UPD. Please, check out a minimal working sandbox example

Categories