delay on getting property, dispatch react-redux - javascript

Delay when updating the board component, my dispatch (checkResult (board)); does not work correctly. Tic-tac-toe game, here's an example of a problem:
set 3 crosses but no victory, but when I do 1 more action (set a cross or zero), then the victory is counted:
My code in:
const mapStateToProps = ({board, players}) => ({board, players});
const mapDispatchToProps = dispatch => ({
draw: (board, players, squareIndex) => {
if (!board[squareIndex]) {
if (players[players.turn] === 'X') {
dispatch(drawXAction(squareIndex));
} else {
dispatch(drawOAction(squareIndex));
}
console.log(dispatch(checkResult(board)))
dispatch(checkResult(board));
dispatch(toggleTurnAction());
}
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Square);
And checkResult func:
export function checkResult(board) {
if (checkVictory(board, 'X')) {
return {
type: X_WINS
}
} else if (checkVictory(board, '0')) {
return {
type: O_WINS
}
} else {
const check = board.filter(symbol=>symbol===null);
if(check.length===1) {
return {
type: TIE
}
}else {
return {
type: 'RANDOM'
}
}
}
}

You are passing the current board to the checkResult function, this means when your checkResult function is executing, it is not receiving the latest board -- what you have updated in the one of the previous lines.
One of the redux principle states that - it enable single source of truth. Your code is violating this principle resulting in this inconsistency. What you need to do is - get the latest state of the application in checkResult function rather than passing the board as argument.
e.g.
import store from "/path/to/store";
export function checkResult() {
// or something like this
// based on what you have in your store.
const board = store.getState().board;
// your function body
}

Related

How to call a composition function from another composition in VueJS

I've been experimenting with the new composition-api in VueJS and am not sure how to solve a problem. I'm looking for some advice on how to properly implement a solution. This wasn't a problem when everything was vuex-based since you can dispatch an action to another module without a problem. However, I'm struggling to find a solution for the composition implementation.
Problem:
Component calls a CompositionA's function.
CompositionA triggers a login function.
On CompositionA's login success/failure response I would like to call a CompositionB function. (CompositionB contains data and logic for showing a snackbar that's used across the site)
The problem is that it is necessary to inject the snackbar dependency in every component rather than have it be instantiated/mounted from CompositionA. Current solution is to this effect:
Component.vue:
// template calls login(credentials) method
import { useCompositionA } from '#/compositions/compositionA'
import { useCompositionB } from '#/compositions/compositionB'
export default {
name: 'Component',
setup(props, context) {
const { login } = useCompositionA(props, context, useCompositionB(props, context))
return {
login
}
},
}
compositionA.js:
export const useAuth = (props, context, snack) => {
const login = async (credentials) => {
try {
return await loginWithEmailPassword(credentials)
snack.show({text: 'Welcome back!'})
} catch (err) {
snack.show({text: 'Failed to login'})
}
}
return { login }
}
compositionB.js:
export const useSnack = (props, context) => {
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Would be nice if something like below existed, but I'm finding that the properties aren't reactive in CompositionB if it's used from CompositionA (method gets called but snackbar doesn't show up). My understanding is that Vue isn't injecting CompositionB into the Component, so I'm just running another instance of CompositionB inside CompositionA. What am I doing something wrong? What's the proper solution here?
compositionA.js (not working):
import { useCompositionB } from '#/compositions/compositionB'
export const useAuth = (props, context) => {
const login = async (credentials) => {
const { show } = useCompositionB()
try {
return await loginWithEmailPassword(credentials)
show({text: 'Welcome back!'})
} catch (err) {
show({text: 'Failed to login'})
}
}
return { login }
}
Thanks in advance,
As expected it was due to the Component referencing its own local copy of CompositionB*. Solution is actually to bring the state of your compositions into the global scope according to:
https://vueschool.io/articles/vuejs-tutorials/state-management-with-composition-api/
Something like this:
compositionB.js:
const snack = reactive({
color: 'success',
text: null,
timeout: 6000,
visible: true,
})
export const useSnack = (props, context) => {
const snackRefs = toRefs(snack)
const show = ({ text, timeout, color }) => {
snackRefs.text.value = text
snackRefs.timeout.value = timeout || 6000
snackRefs.color.value = color || 'success'
snackRefs.visible.value = true
}
return {
...snackRefs,
show
}
}
Works like a charm.
Only caveat I found initially was a composition-api error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
This was easily solved by mounting the composition-api first thing in main.js as per solution here:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function
I think this won't be a problem with vue3 comes out. Hope this helps someone.

Redux updateElementSaga has been cancelled. Why?

I just implemented a drag and drop feature with react-dnd and when the user drops the SkyElement item in my app, I update top and left on the server which in turn updates the redux store
However, the update call works occasionally, not every time. And in my console, I see a warning; updateElementSaga has been cancelled
In my SlotView.js, in a function, I have:
this.props.dispatch(requestUpdateElement({ id, top, left }));
In my elements/actions.js:
export function requestUpdateElement(element) {
return { type: 'requestUpdateElement', element };
}
In my elements/sagas.js:
export function *updateElementSaga(action) {
const response = yield call(api.updateElement, action.element);
if (response.element) {
// debugger; // this hits, saga was cancelled will have appeared in the console at this point
yield put(actions.receiveElement(response.element));
} else if (response.error) {
console.log('error receiving element');
}
}
export default [
takeLatest('requestUpdateElement', updateElementSaga),
];
In api.js:
export function updateElement(element) {
const userId = JSON.parse(localStorage.cookies).userId;
element.userId = userId;
if (userId) {
return apiHelper.put(
`${apiHelper.getBaseUrl()}/users/${element.userId}/elements/${element.id}`,
{element},
{headers: apiHelper.getHeaders()}
).catch((error) => {
return {error};
});
} else {
console.log('user ID could not be found for request');
}
}
And my elements/reducer.js:
const defaultState = {
elementsMap: {},
visibleElements: [],
unplacedElements: [],
};
export default function(state = defaultState, action) {
switch (action.type) {
case 'receiveElement':
let element = null;
let unplacedElement = null;
if (action.element.sectorId === undefined) {
unplacedElement = `${action.element.id}`;
} else {
element = `${action.element.id}`;
// don't add, duplicate
const newState = {...state}; // copy old state
delete newState[`${action.element.id}`]; // delete the item from the object
const newVisibleElements = newState.visibleElements.filter(e => e !== `${action.element.id}`); // remove item from visible elements
const newUnplacedElements = newState.unplacedElements.filter(e => e !== `${action.element.id}`);
return {
...newState,
elementsMap: {
...newState.elementsMap,
[element]: action.element,
},
visibleElements: [...newVisibleElements, element],
unplacedElements: [...newUnplacedElements],
};
}
return {
...state,
elementsMap: {
...state.elementsMap,
[action.element.id]: action.element,
},
visibleElements: [...state.visibleElements, element],
unplacedElements: [...state.unplacedElements, unplacedElement],
};
default:
return state;
}
}
Like I mentioned before, sometimes the update works, but not every time. I've narrowed the problem down to the client. Server seems to be acting fine. Any idea what I'm doing wrong here? Thanks!
If you are using takeLatest the redux saga documentation does mention:
https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html
Unlike takeEvery, takeLatest allows only one fetchData task to run at
any moment. And it will be the latest started task. If a previous
task is still running when another fetchData task is started, the
previous task will be automatically cancelled.
Where fetchData is the generator function that is being served using takeLatest or takeEvery
And when your UI keeps invoking the same action, before it gets completed, it will keep cancelling
the last invoked action, and hence you would keep getting the message intermittently:
updateElementSaga has been cancelled
Which by nature takeLatest is doing the right thing. Which is:
Always take the latest invoked action
In case you want every action to be caught and processed, do use takeEvery, as:
export default [
takeEvery('requestUpdateElement', updateElementSaga),
];

How to handle events in redux? (Specifically async ones)

Hello guys so I'm trying to handle events from async redux actions but i'm not sure how to do it correctly? In my code my AddTopping() handler calls the action creator and then the proccess cycles through. I logged everything to console.log --- based on the code below this is what the console.log will print out in order assuming the function executes fine:
success is 'pending'
success is true
It prints the initial state first which is what I don't want. I would have to call the action creator twice to actually get the 'true' for the object. I can do a setTimeout to 50 millisec and it will work but I don't want to make setTimeout's everytime. How will I handle this the right way?
Component that calls the action creator:
class Milk extends Component {
componentWillMount() {
this.AddTopping.bind(this);
}
AddTopping() {
this.props.addChocolate({TableName: 'Dark', Item: {title: 'someCake'}});
if (this.props.birthdayCake.send_success === true) {
console.log('Congrats you have added chocolate to the cake');
} else {
console.log('You have failed to add chocolate to the cake.');
}
}
render() {
return (
<div>
<h1>Chocolate is great.</h1>
</div>
);
}
}
the action creator that will then call the reducer:
export const addChocolate = (darkOrLight) => {
return ( dispatch ) => {
someAsyncFunction(darkOrLight, (err,data) => {
if (err) {
dispatch({ type: 'ADD_CHOCOLATE', success: false });
} else {
dispatch({ type: 'ADD_CHOCOLATE', success: true });
}
});
}
}
the reducer which will now update the state in the application:
const initialState = { send_success: 'pending' }
export function birthdayCake( state=initialState, action) {
switch ( action.type ) {
case ADD_CHOCOLATE:
if (action.success === true) {
return Object.assign({}, state, { send_success: true });
} else {
return Object.assign({}, state, { send_success: false });
}
default:
return state;
}
}

Unsubscribe from Redux store when condition is true?

I'm employing the suggestion from #gaearon to setup a listener on my redux store. I'm using this format:
function observeStore(store, select, onChange) {
let currentState;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
let unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
I'm using this in an onEnter handler for a react-router route:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
// I'm done: how do I dispose the store subscription???
}
});
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Basically this helps gate the progression of the router while actions are finishing dispatching (async).
My problem is that I can't figure out where to call disposeRouteHandler(). If I call it right after the definition, my onChange function never gets a chance to do it's thing, and I can't put it inside the onChange function because it's not defined yet.
Appears to me to be a chicken-egg problem. Would really appreciate any help/guidance/insight.
How about:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
let shouldDispose = false;
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
if (disposeRouteHandler) {
disposeRouteHandler();
} else {
shouldDispose = true;
}
}
});
if (shouldDispose) {
disposeRouteHandler();
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Even though using the observable pattern leads to some buy-in, you can work around any difficulties with normal js code. Alternatively you can modify your observable to suit your needs better.
For instance:
function observeStore(store, select, onChange) {
let currentState, unsubscribe;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState, unsubscribe);
}
}
unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
and
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state, disposeRouteHandler) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
disposeRouteHandler();
}
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
It does add a strange argument to onChange but it's just one of many ways to do it.
The core problem is that handleChange gets called synchronously immediately when nothing has changed yet and asynchronously later. It's known as Zalgo.
Inspired by the suggestion from #DDS, I came up with the following alteration to the other pattern mentioned in #gaearon's comment:
export function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = this.dispose = store.subscribe(() => {
onNext.bind(this)(store.getState())
});
onNext.bind(this)(store.getState());
return { dispose };
},
dispose: function() {},
}
}
This allows me to invoke like:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
toObservable(store).subscribe({
onNext: function onNext(state) {
const conditions = [/* many conditions */];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
this.dispose(); // remove the store subscription
}
},
});
store.dispatch(/* action */);
};
};
The key difference is that I'm passing a regular function in for onNext so as not to interfere with my bind(this) in toObservable; I couldn't figure out how to force the binding to use the context I wanted.
This solution avoids
add[ing] a strange argument to onChange
... and in my opinion also conveys a bit more intent: this.dispose() is called from within onNext, so it kinda reads like onNext.dispose(), which is exactly what I want to do.

How to write state changing routine, similar to Redux?

How can I write a really super, simple state changing routine? I need something like Redux, but way simpler, don't need all the bells & whistles.
I was thinking of a global object i.e. myState = {}, that is changed via setMyState() / getMyState().
I'm using JavaScript, and wondering if this would be done via a timer that polls say every 10ms, or so.
So in my JavaScript client app (I'm using ReactJS), a call to my getMyState("show-menu") inside a render() would update the Component's state just like using this.state..
The reason I want this is:
1) Wanna know how to write it for learning purposes.
2) Need something simpler that Redux, simple like Meteor's Session vars, so don't have to pass this.Refs. down to child compnents which setState on parent components.
3) Redux is a mouthful, there is still lots to digest and learn to use Redux.
Seems like you could do this pretty simply with a constructor.
function State () {
this._state = {};
...
}
State.prototype.get = function () {
return this._state;
};
State.prototype.set = function (state) {
return this._state = state;
};
var STATE = new State();
But then you have to do the polling you mentioned in your post. Alternatively, you can look at eventEmitter libraries for javascript, for example https://github.com/facebook/emitter, and turn the State object into an event emitter.
Update
Not sure if this is what you're looking for, at all, but it's simpler.
function makeStore () {
var state = { };
return {
set (key, value) { state[key] = value; },
get (key) { return state[key]; }
};
}
const store = makeStore();
store.set("counter", 1);
store.get("counter"); // 1
Believe it or not, there's really not a lot to Redux.
There's, perhaps, a lot to think about, and it's extra work to keep everything untied from your store...
But have a quick look:
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
This is the code I intend to use.
Looking at it, I'm pretty much just creating a store (with a function to run every time there's an event), there's the subscription to have a listener run, after the store has updated, there's a line where I fire an action, and... ...well, that's it.
function createStore (reduce, initialState) {
var state = initialState;
var listeners = [];
function notifyAll () {
listeners.forEach(update => update());
}
function dispatch (event) {
const newState = reduce(state, event);
state = newState;
notifyAll();
return store;
}
function subscribe (listener) {
listeners.push(listener);
return store;
}
function getState () {
return state;
}
const store = {
getState, subscribe, dispatch
};
return store;
}
// THIS IS MY APPLICATION CODE
function reducer (state, action) {
state = state || { count: 0 };
const direction = (action.type === "INCREASE") ? 1 : (action.type === "DECREASE") ? -1 : 0;
return {
count: (state.count + direction)
};
}
function announceState () {
console.log(store.getState());
}
function updateView () {
const count = store.getState().count;
document.querySelector("#Output").value = count || 0;
}
function increase () {
store.dispatch({ type: "INCREASE" });
}
function decrease () {
store.dispatch({ type: "DECREASE" });
}
const store = createStore(reducer, { count: 0 });
store.subscribe(announceState)
.subscribe(updateView);
document.querySelector("#Increment").onclick = increase;
document.querySelector("#Decrement").onclick = decrease;
updateView();
<button id="Decrement">-</button>
<output id="Output"></output>
<button id="Increment">+</button>
The very tiny, very easy implementation of a store (note that the real thing is more complex) is above. dispatch and subscribe are very useful, here.

Categories