How to update Recoil JS Atom Object - javascript

I have an Atom object defined like this:
export const walletInfo = atom({
key: "walletInfo",
default: {
connected: false,
someString: "",
someInt: 0,
someOtherInfo: "",
},
});
The question is, how do I change only single value in Atom object without overwriting everything else? For example, how do I set connected: true and keep the rest of the information?
The code below will "erase" the rest of the object
import { walletInfo } from "../src/atoms";
const [wallet, setWallet] = useRecoilState(walletInfo);
setWallet({
connected: true,
});

You need to combine the previous state and updated state like this
setWallet((prevState) => ({
...prevState,
connected: true,
}));

Related

New Object created with JS returns empty values

I want my function calculateDistance to return a new array based on the array in state.
so once a user presses a button, handle method is activated:
handleSearch(result){
this.setState({
teams: this.calculateDistance(result),
})
}
calculateDistance(result){
let map = L.map('route-map').setView([52.237049, 21.017532], 11);
const newTeams = []
this.state.teams.forEach((team) => {
const newTeam = {}
let routeControl = L.Routing.control({
waypoints: [
L.latLng(team.lat, team.long),
L.latLng(result.y, result.x)
],
show: true,
}).addTo(map)
routeControl.on('routesfound', function(e) {
let routes = e.routes;
let dist = routes[0].summary.totalDistance;
Object.defineProperty(newTeam, 'distance', {
value: dist,
writable: false
})
Object.defineProperty(newTeam, 'id', {
value: team.id,
writable: false
})
})
newTeams.push(newTeam)
})
return newTeams
this.state.teams is a array retrieved from axios GET method.
Firstly I tried to add a new value, for each team. It worked for the object but not with stringify - and that resulted in no change.
Then I figured I will create a new Object, and that is the code mentioned in this question.
I tried using async.
And it did indeed return a promise with the result (I checked in using console.log(promise) but state wasn't updated anyway (after using .then(), there wasn't any value).
The object you create only appears to be empty. By default defineProperty creates a property that is not enumerable.
You can to change your code to make them enumerable:
Object.defineProperty(newTeam, 'distance', {
value: dist,
writable: false,
enumerable: true
})
Object.defineProperty(newTeam, 'id', {
value: team.id,
writable: false,
enumberable: true
})

Adding an array and an object to an array react hooks

I'm trying to add an array and an object to an array.
This is my constructor
const [dashboard, setDashboard] = useState({ loading: true, data: [], address: '' })
and this is how I wanted the data to end up
{
loading: true,
data: [{id: 0, name: 'A'}, {id: 1, name: 'B'}],
address: 'xxx'
}
I can't seem to figure it out and was only able to manage to add the arrays but not the other objects like loading and address with something like this but this is not what I need and I'm just giving an example of what I tried doing:
the constructor of this one in the bottom is different from what I want to use, I used something like this
const [dashboard, setDashboard] = useState([])
setDashboard((prev) => {
return [...prev, newData]
})
If I understand your problem correctly you are trying to do something like this:
setDashbaord(prevDashboard => ({ ...prevDashboard, address: "xxx", data: [...prevDashboard.data, newData] }));
Is that what you are looking for?
const [dashboard, setDashboard] = useState({ loading: true, data: [], address: '' })
The line above looks great, no problems there.
setDashboard((prev) => {
return [...prev, newData]
})
Doing this will set your dashboard variable to be an array, and is definitely not what you said you wanted.
setDashboard((prev) => {
return {
...prev,
data: [...prev.data, ...newData] // <-- assuming new data is an array
}
})
Depending on what newData looks like you may need to manually merge the data into your previous data object. For instance, if you want to ensure you have no duplicate values in the array, or you need to sort them.

xstate: how can I initialize the state to a specific node?

I have a multi step form that basically has these basic steps: select services -> contact -> billing. I display a progress bar and emit events when the user changes the step they're on, and this is my current basic pattern with xstate:
const formMachine = new Machine({
id: 'form-machine',
initial: 'selectService',
context: {
progress: 0,
pathname: '/select-service',
},
states: {
selectService: {
entry: assign({
progress: 0,
pathname: '/select-service',
}),
on: {
NEXT: 'contact',
}
},
contact: {
entry: assign({
progress: 1 / 3,
pathname: '/billing'
}),
on: {
PREVIOUS: 'selectService',
NEXT: 'billing',
}
},
// ... there's more actions but that's the gist of it
}
});
Here's the visualization:
In my react component, I watch this service for changes in pathname so I can push to the history
function SignupFormWizard() {
const history = useHistory();
const [state, send, service] = useMachine(formMachine);
useEffect(() => {
const subscription = service.subscribe((state) => {
history.push(state.context.pathname);
});
return subscription.unsubscribe;
}, [service, history]);
// ...
}
However, here's the problem: whenever I revisit a route (say, I directly navigate to /billing), it will immediately bring me back to /select-service. This makes sense with my code because of the initial state, and the subscription, that it will do that.
How would I go about initializing the state machine at a specific node?
useMachine hook accepts second parameter which is a configuration object. In this object you can set state using state key, but you will have to construct state yourself, and it would look something like this:
let createdState = State.create(stateDefinition);
let resolvedState = formMachine.resolve(state);
let [state, send, service] = useMachine(formMachine, {state: resolvedState});
In my opinion it works well when you need to restore persisting state, but creating stateDefinition manually from scratch is just too much hustle.
What you can do, is create initial state and choose where you want to actually start:
initial: {
always: [
{
target: "selectService",
cond: "isSelectServicePage",
},
{
target: "contact",
cons: "isContactPage",
},
{
target: "billing",
cond: "isBillingPage",
},
],
}
Then, when you are starting your machine, all you have to do is set initial context value:
let location = useLocation();
let [state, send, service] = useMachine(formMachine, {
context: { pathname: location.pathname },
});
The other answer is almost correct, to be more precise, you can move to first state which will decide the next step dynamically, based on context assigned.
createMachine(
{
id: "Inspection Machine",
initial:
"decideIfNewOrExisting",
states: {
"decideIfNewOrExisting": {
always: [
{
target: "initialiseJob",
cond: "isNewJob"
},
{
target: "loadExistingJob"
}
]
},
...

redux state of application dissapears temporarily when called on?

I am trying to call the current state of my application in the redux reducer. In the first case, I fire my action off with this code–
store.dispatch(addNotificationID(item, 'start', notificationID));
console.log(item.id, 'start', notificationID);
console.log(store.getState().StorageReducer.IDs); //shows accurate state
This successfully fires the first case of my redux reducer and then the console log of console.log(store.getState().StorageReducer.IDs) shows the newly added ID in the state. Then, I have the second case that I am going to eventually use to remove the ID object from the state. If I reload the application and then this action fires of type CANCEL_NOTIFICATION it will log the correct store. But if I fire that action in the same session that an ID was added, that will not log to the store.
here is my reducer:
const initialState = {
IDs: []
};
const storage = (state = initialState, action) => {
switch (action.type) {
case ADD_NOTIFICATION_ID:
return {
...state,
IDs:
[
...state.IDs,
{
itemID: action.item.id,
reminderType: action.reminderType,
notificationID: action.notificationID
}
]
};
case CANCEL_NOTIFICATION:
console.log(state.IDs);
return { ...state };
For example, say IDs was this when the application booted up:
Array [
Object {
"itemID": "_UE9dINF",
"notificationID": "F7AFB9F9-45C3-4A90-AD70-1A2334CE282A",
"reminderType": "start",
},
]
Then my addNotifications action fires. During that session when the console.log(store.getState().StorageReducer.IDs) gets called it would look like this:
Array [
Object {
"itemID": "_UE9dINF",
"notificationID": "F7AFB9F9-45C3-4A90-AD70-1A2334CE282A",
"reminderType": "start",
},
Object { //this is the new object that was just added to state
"itemID": "z4ACCSZR",
"notificationID": "3850E623-CFA7-483C-BF22-DB07C746CE37",
"reminderType": "start",
},
]
If I reload the session and then fire the cancelNotification action the reducer case will also log that ouptut. However, if I run that action in the same session I fired the addNotifications action, instead of looking like above it will only look like this:
Array [
Object {
"itemID": "_UE9dINF",
"notificationID": "F7AFB9F9-45C3-4A90-AD70-1A2334CE282A",
"reminderType": "start",
},
]
Then if I reload the session it will have the two objects as it is supposed to. I really don't understand why or how this is possible. I am using redux-persist and think the bug may be coming somewhere from that.
My redux store:
const todoPersistConfig = {
key: 'TodoReducer',
storage: AsyncStorage,
whitelist: ['todos'],
stateReconciler: autoMergeLevel2
};
const storageConfig = {
key: 'StorageReducer',
storage: AsyncStorage,
whitelist: ['IDs'],
stateReconciler: autoMergeLevel2
};
const remindersPersistConfig = {
key: 'remindersreducer',
storage: AsyncStorage,
whitelist: ['notificationIDs'],
stateReconciler: autoMergeLevel2
};
const reducers = combineReducers({
ModalReducer,
RemindersReducer: persistReducer(remindersPersistConfig, RemindersReducer),
StorageReducer: persistReducer(storageConfig, StorageReducer),
TodoReducer: persistReducer(todoPersistConfig, TodoReducer),
AuthReducer
});
export default function storeConfiguration() {
const store = createStore(
reducers,
{},
composeEnhancers(
applyMiddleware(ReduxThunk)
)
);
const persistor = persistStore(store);
return { persistor, store };
}
There are no problems with the todo reducer, but I recreated an extra reminders reducer to duplicate and see if the issue persisted and it had the same results.

React Immutability-helper - update many similar objects in array

I have state in Redux Store and I want to change all isPlaying to false. I do not have idea how I can do that in better way. Here is what I did so far.
const INITIAL_STATE = {
isPlaying: true,
allLangs: [
{
shortName: 'es',
fullName: "spanish",
order: 0,
isPlaying: false
}, {
shortName: 'pt',
fullName: "portuguese",
order: 0,
isPlaying: false
},
{
shortName: 'gb',
fullName: "english",
order: 0,
isPlaying: true
}
]
}
return update(state,
{
$merge: {isPlaying: false},
allLangs: {
[0]: {
$merge: {isPlaying: false}
},
[1]: {
$merge: {isPlaying: false}
},
[2]: {
$merge: {isPlaying: false}
}
}
});
So my question: Is it any way to do this in better way than call to all index separatly ?
Given your current data structure, if you want to set every instance of isPlaying to false, then what you have is correct. Depending on your use case, here are some other suggestions:
Reset to default
Let's say that whenever you set isPlaying to false, the game is over and you are starting a new game. Then, the easiest thing to do might be to just set the state to initialState directly.
Change data structure
This is dependent on your use case, but my first instinct is that your data structure has duplication. Can you really be playing a game in English and Spanish at the same time? If not, consider doing something like this:
const allLangs = {
es: {
fullName: "spanish",
order: 0,
}
}
Then, in yours state, you would point to the active language:
const state = {
isPlaying: false,
activeLanguage: "es"
}
Refactoring in this way gives you one single source of truth.
I would suggest you to take the 'allLangs' array in separate array variable lets say 'newAllLangs' and use map() to change the 'isPlaying' and in the last set this 'newAllLangs' to state. Something like this.
I am considering you are doing this manipulation in userAction.js. So lets say you got allLangs via mapStateToProps and sent this data to userAction.js by using mapDispatchToProps. In userAction.js you can manupulate allLangs and send them back to reducer. And reducer will update store based on what you send from userAction.
This was theoretical part If you want more exposure. You can make a fiddle and can share here.

Categories

Resources